You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

record.inc.php 68KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122
  1. <?php
  2. /* Poweradmin, a friendly web-based admin tool for PowerDNS.
  3. * See <http://www.poweradmin.org> for more details.
  4. *
  5. * Copyright 2007-2009 Rejo Zenger <rejo@zenger.nl>
  6. * Copyright 2010-2014 Poweradmin Development Team
  7. * <http://www.poweradmin.org/credits.html>
  8. *
  9. * This program is free software: you can redistribute it and/or modify
  10. * it under the terms of the GNU General Public License as published by
  11. * the Free Software Foundation, either version 3 of the License, or
  12. * (at your option) any later version.
  13. *
  14. * This program is distributed in the hope that it will be useful,
  15. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  17. * GNU General Public License for more details.
  18. *
  19. * You should have received a copy of the GNU General Public License
  20. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  21. */
  22. require_once('templates.inc.php');
  23. /**
  24. * DNS record functions
  25. *
  26. * @package Poweradmin
  27. * @copyright 2007-2010 Rejo Zenger <rejo@zenger.nl>
  28. * @copyright 2010-2014 Poweradmin Development Team
  29. * @license http://opensource.org/licenses/GPL-3.0 GPL
  30. */
  31. /** Check if Zone ID exists
  32. *
  33. * @param int $zid Zone ID
  34. *
  35. * @return boolean|int Domain count or false on failure
  36. */
  37. function zone_id_exists($zid) {
  38. global $db;
  39. $query = "SELECT COUNT(id) FROM domains WHERE id = " . $db->quote($zid, 'integer');
  40. $count = $db->queryOne($query);
  41. if (PEAR::isError($count)) {
  42. error($count->getMessage());
  43. return false;
  44. }
  45. return $count;
  46. }
  47. /** Get Zone ID from Record ID
  48. *
  49. * @param int $rid Record ID
  50. *
  51. * @return int Zone ID
  52. */
  53. function get_zone_id_from_record_id($rid) {
  54. global $db;
  55. $query = "SELECT domain_id FROM records WHERE id = " . $db->quote($rid, 'integer');
  56. $zid = $db->queryOne($query);
  57. return $zid;
  58. }
  59. /** Count Zone Records for Zone ID
  60. *
  61. * @param int $zone_id Zone ID
  62. *
  63. * @return int Record count
  64. */
  65. function count_zone_records($zone_id) {
  66. global $db;
  67. $sqlq = "SELECT COUNT(id) FROM records WHERE domain_id = " . $db->quote($zone_id, 'integer');
  68. $record_count = $db->queryOne($sqlq);
  69. return $record_count;
  70. }
  71. /** Get SOA record content for Zone ID
  72. *
  73. * @param int $zone_id Zone ID
  74. *
  75. * @return string SOA content
  76. */
  77. function get_soa_record($zone_id) {
  78. global $db;
  79. $sqlq = "SELECT content FROM records WHERE type = " . $db->quote('SOA', 'text') . " AND domain_id = " . $db->quote($zone_id, 'integer');
  80. $result = $db->queryOne($sqlq);
  81. return $result;
  82. }
  83. /** Get SOA Serial Number
  84. *
  85. * @param string $soa_rec SOA record content
  86. *
  87. * @return string SOA serial
  88. */
  89. function get_soa_serial($soa_rec) {
  90. $soa = explode(" ", $soa_rec);
  91. return $soa[2];
  92. }
  93. /** Get Next Date
  94. *
  95. * @param string $curr_date Current date in YYYYMMDD format
  96. *
  97. * @return string Date +1 day
  98. */
  99. function get_next_date($curr_date) {
  100. $next_date = date('Ymd', strtotime('+1 day', strtotime($curr_date)));
  101. return $next_date;
  102. }
  103. /** Get Next Serial
  104. *
  105. * Zone transfer to zone slave(s) will occur only if the serial number
  106. * of the SOA RR is arithmetically greater that the previous one
  107. * (as defined by RFC-1982).
  108. *
  109. * The serial should be updated, unless:
  110. *
  111. * - the serial is set to "0", see http://doc.powerdns.com/types.html#id482176
  112. *
  113. * - set a fresh serial ONLY if the existing serial is lower than the current date
  114. *
  115. * - update date in serial if it reaches limit of revisions for today or do you
  116. * think that ritual suicide is better in such case?
  117. *
  118. * "This works unless you will require to make more than 99 changes until the new
  119. * date is reached - in which case perhaps ritual suicide is the best option."
  120. * http://www.zytrax.com/books/dns/ch9/serial.html
  121. *
  122. * @param string $curr_serial Current Serial No
  123. * @param string $today Optional date for "today"
  124. *
  125. * @return string Next serial number
  126. */
  127. function get_next_serial($curr_serial, $today = '') {
  128. // Autoserial
  129. if ($curr_serial == 0) {
  130. return 0;
  131. }
  132. // Serial number could be a not date based
  133. if ($curr_serial < 1979999999) {
  134. return $curr_serial+1;
  135. }
  136. // Reset the serial number, Bind was written in the early 1980s
  137. if ($curr_serial == 1979999999) {
  138. return 1;
  139. }
  140. if ($today == '') {
  141. set_timezone();
  142. $today = date('Ymd');
  143. }
  144. $revision = (int) substr($curr_serial, -2);
  145. $ser_date = substr($curr_serial, 0, 8);
  146. if ($curr_serial == '0') {
  147. $serial = $curr_serial;
  148. } elseif ($curr_serial == $today . '99') {
  149. $serial = get_next_date($today) . '00';
  150. } else {
  151. if (strcmp($today, $ser_date) === 0) {
  152. // Current serial starts with date of today, so we need to update the revision only.
  153. ++$revision;
  154. } elseif (strncmp($today, $curr_serial, 8) === -1) {
  155. // Reuse existing serial date if it's in the future
  156. $today = substr($curr_serial, 0, 8);
  157. // Get next date if revision reaches maximum per day (99) limit otherwise increment the counter
  158. if ($revision == 99) {
  159. $today = get_next_date($today);
  160. $revision = "00";
  161. } else {
  162. ++$revision;
  163. }
  164. } else {
  165. // Current serial did not start of today, so it's either an older
  166. // serial, therefore set a fresh serial
  167. $revision = "00";
  168. }
  169. // Create new serial out of existing/updated date and revision
  170. $serial = $today . str_pad($revision, 2, "0", STR_PAD_LEFT);
  171. }
  172. return $serial;
  173. }
  174. /** Update SOA record
  175. *
  176. * @param int $domain_id Domain ID
  177. * @param string $content SOA content to set
  178. *
  179. * @return boolean true if success
  180. */
  181. function update_soa_record($domain_id, $content) {
  182. global $db;
  183. $sqlq = "UPDATE records SET content = " . $db->quote($content, 'text') . " WHERE domain_id = " . $db->quote($domain_id, 'integer') . " AND type = " . $db->quote('SOA', 'text');
  184. $response = $db->query($sqlq);
  185. if (PEAR::isError($response)) {
  186. error($response->getMessage());
  187. return false;
  188. }
  189. return true;
  190. }
  191. /** Set SOA serial in SOA content
  192. *
  193. * @param string $soa_rec SOA record content
  194. * @param string $serial New serial number
  195. *
  196. * @return string Updated SOA record
  197. */
  198. function set_soa_serial($soa_rec, $serial) {
  199. // Split content of current SOA record into an array.
  200. $soa = explode(" ", $soa_rec);
  201. $soa[2] = $serial;
  202. // Build new SOA record content
  203. $soa_rec = join(" ", $soa);
  204. chop($soa_rec);
  205. return $soa_rec;
  206. }
  207. /** Return SOA record
  208. *
  209. * Returns SOA record with incremented serial number
  210. *
  211. * @param int $soa_rec Current SOA record
  212. *
  213. * @return boolean true if success
  214. */
  215. function get_updated_soa_record($soa_rec) {
  216. $curr_serial = get_soa_serial($soa_rec);
  217. $new_serial = get_next_serial($curr_serial);
  218. if ($curr_serial != $new_serial) {
  219. return set_soa_serial($soa_rec, $new_serial);
  220. }
  221. return set_soa_serial($soa_rec, $curr_serial);
  222. }
  223. /** Update SOA serial
  224. *
  225. * Increments SOA serial to next possible number
  226. *
  227. * @param int $domain_id Domain ID
  228. *
  229. * @return boolean true if success
  230. */
  231. function update_soa_serial($domain_id) {
  232. $soa_rec = get_soa_record($domain_id);
  233. if ($soa_rec == NULL) {
  234. return false;
  235. }
  236. $curr_serial = get_soa_serial($soa_rec);
  237. $new_serial = get_next_serial($curr_serial);
  238. if ($curr_serial != $new_serial) {
  239. $soa_rec = set_soa_serial($soa_rec, $new_serial);
  240. return update_soa_record($domain_id, $soa_rec);
  241. }
  242. return true;
  243. }
  244. /** Get Zone comment
  245. *
  246. * @param int $zone_id Zone ID
  247. *
  248. * @return string Zone Comment
  249. */
  250. function get_zone_comment($zone_id) {
  251. global $db;
  252. $query = "SELECT comment FROM zones WHERE domain_id = " . $db->quote($zone_id, 'integer');
  253. $comment = $db->queryOne($query);
  254. if ($comment == "0") {
  255. $comment = '';
  256. }
  257. return $comment;
  258. }
  259. /** Edit the zone comment
  260. *
  261. * This function validates it if correct it inserts it into the database.
  262. *
  263. * @param int $zone_id Zone ID
  264. * @param string $comment Comment to set
  265. *
  266. * @return boolean true on success
  267. */
  268. function edit_zone_comment($zone_id, $comment) {
  269. if (verify_permission('zone_content_edit_others')) {
  270. $perm_content_edit = "all";
  271. } elseif (verify_permission('zone_content_edit_own')) {
  272. $perm_content_edit = "own";
  273. } else {
  274. $perm_content_edit = "none";
  275. }
  276. $user_is_zone_owner = verify_user_is_owner_zoneid($zone_id);
  277. $zone_type = get_domain_type($zone_id);
  278. if ($zone_type == "SLAVE" || $perm_content_edit == "none" || ($perm_content_edit == "own" && $user_is_zone_owner == "0")) {
  279. error(ERR_PERM_EDIT_COMMENT);
  280. return false;
  281. } else {
  282. global $db;
  283. $query = "SELECT COUNT(*) FROM zones WHERE domain_id=" . $db->quote($zone_id, 'integer');
  284. $count = $db->queryOne($query);
  285. if ($count > 0) {
  286. $query = "UPDATE zones
  287. SET comment=" . $db->quote($comment, 'text') . "
  288. WHERE domain_id=" . $db->quote($zone_id, 'integer');
  289. $result = $db->query($query);
  290. if (PEAR::isError($result)) {
  291. error($result->getMessage());
  292. return false;
  293. }
  294. } else {
  295. $query = "INSERT INTO zones (domain_id, owner, comment)
  296. VALUES(" . $db->quote($zone_id, 'integer') . ",1," . $db->quote($comment, 'text') . ")";
  297. $result = $db->query($query);
  298. if (PEAR::isError($result)) {
  299. error($result->getMessage());
  300. return false;
  301. }
  302. }
  303. }
  304. return true;
  305. }
  306. /** Edit a record
  307. *
  308. * This function validates it if correct it inserts it into the database.
  309. *
  310. * @param mixed[] $record Record structure to update
  311. *
  312. * @return boolean true if successful
  313. */
  314. function edit_record($record) {
  315. if (verify_permission('zone_content_edit_others')) {
  316. $perm_content_edit = "all";
  317. } elseif (verify_permission('zone_content_edit_own')) {
  318. $perm_content_edit = "own";
  319. } else {
  320. $perm_content_edit = "none";
  321. }
  322. $user_is_zone_owner = verify_user_is_owner_zoneid($record['zid']);
  323. $zone_type = get_domain_type($record['zid']);
  324. if ($zone_type == "SLAVE" || $perm_content_edit == "none" || ($perm_content_edit == "own" && $user_is_zone_owner == "0")) {
  325. error(ERR_PERM_EDIT_RECORD);
  326. return false;
  327. } else {
  328. global $db;
  329. if (validate_input($record['rid'], $record['zid'], $record['type'], $record['content'], $record['name'], $record['prio'], $record['ttl'])) {
  330. $name = strtolower($record['name']); // powerdns only searches for lower case records
  331. if ($record['type'] == "SPF" || $record['type'] == "TXT") {
  332. $content = $db->quote(stripslashes('\"' . $record['content'] . '\"'), 'text');
  333. } else {
  334. $content = $db->quote($record['content'], 'text');
  335. }
  336. $query = "UPDATE records
  337. SET name=" . $db->quote($name, 'text') . ",
  338. type=" . $db->quote($record['type'], 'text') . ",
  339. content=" . $content . ",
  340. ttl=" . $db->quote($record['ttl'], 'integer') . ",
  341. prio=" . $db->quote($record['prio'], 'integer') . ",
  342. change_date=" . $db->quote(time(), 'integer') . "
  343. WHERE id=" . $db->quote($record['rid'], 'integer');
  344. $result = $db->query($query);
  345. if (PEAR::isError($result)) {
  346. error($result->getMessage());
  347. return false;
  348. }
  349. return true;
  350. }
  351. return false;
  352. }
  353. }
  354. /** Add a record
  355. *
  356. * This function validates it if correct it inserts it into the database.
  357. *
  358. * @param int $zone_id Zone ID
  359. * @param string $name Name part of record
  360. * @param string $type Type of record
  361. * @param string $content Content of record
  362. * @param int $ttl Time-To-Live of record
  363. * @param int $prio Priority of record
  364. *
  365. * @return boolean true if successful
  366. */
  367. function add_record($zone_id, $name, $type, $content, $ttl, $prio) {
  368. global $db;
  369. global $pdnssec_use;
  370. if (verify_permission('zone_content_edit_others')) {
  371. $perm_content_edit = "all";
  372. } elseif (verify_permission('zone_content_edit_own')) {
  373. $perm_content_edit = "own";
  374. } else {
  375. $perm_content_edit = "none";
  376. }
  377. $user_is_zone_owner = verify_user_is_owner_zoneid($zone_id);
  378. $zone_type = get_domain_type($zone_id);
  379. if ($zone_type == "SLAVE" || $perm_content_edit == "none" || ($perm_content_edit == "own" && $user_is_zone_owner == "0")) {
  380. error(ERR_PERM_ADD_RECORD);
  381. return false;
  382. } else {
  383. $response = $db->beginTransaction();
  384. if (validate_input(-1, $zone_id, $type, $content, $name, $prio, $ttl)) {
  385. $change = time();
  386. $name = strtolower($name); // powerdns only searches for lower case records
  387. if ($type == "SPF" || $type == "TXT") {
  388. $content = $db->quote(stripslashes('\"' . $content . '\"'), 'text');
  389. } else {
  390. $content = $db->quote($content, 'text');
  391. }
  392. $query = "INSERT INTO records (domain_id, name, type, content, ttl, prio, change_date) VALUES ("
  393. . $db->quote($zone_id, 'integer') . ","
  394. . $db->quote($name, 'text') . ","
  395. . $db->quote($type, 'text') . ","
  396. . $content . ","
  397. . $db->quote($ttl, 'integer') . ","
  398. . $db->quote($prio, 'integer') . ","
  399. . $db->quote($change, 'integer') . ")";
  400. $response = $db->exec($query);
  401. if (PEAR::isError($response)) {
  402. error($response->getMessage());
  403. $response = $db->rollback();
  404. return false;
  405. } else {
  406. $response = $db->commit();
  407. if ($type != 'SOA') {
  408. update_soa_serial($zone_id);
  409. }
  410. if ($pdnssec_use) {
  411. dnssec_rectify_zone($zone_id);
  412. }
  413. return true;
  414. }
  415. } else {
  416. return false;
  417. }
  418. }
  419. }
  420. /** Add Supermaster
  421. *
  422. * Add a trusted supermaster to the global supermasters table
  423. *
  424. * @param string $master_ip Supermaster IP address
  425. * @param string $ns_name Hostname of supermasterfound in NS records for domain
  426. * @param string $account Account name used for tracking
  427. *
  428. * @return boolean true on success
  429. */
  430. function add_supermaster($master_ip, $ns_name, $account) {
  431. global $db;
  432. if (!is_valid_ipv4($master_ip) && !is_valid_ipv6($master_ip)) {
  433. error(ERR_DNS_IP);
  434. return false;
  435. }
  436. if (!is_valid_hostname_fqdn($ns_name, 0)) {
  437. error(ERR_DNS_HOSTNAME);
  438. return false;
  439. }
  440. if (!validate_account($account)) {
  441. error(sprintf(ERR_INV_ARGC, "add_supermaster", "given account name is invalid (alpha chars only)"));
  442. return false;
  443. }
  444. if (supermaster_ip_name_exists($master_ip, $ns_name)) {
  445. error(ERR_SM_EXISTS);
  446. return false;
  447. } else {
  448. $db->query("INSERT INTO supermasters VALUES (" . $db->quote($master_ip, 'text') . ", " . $db->quote($ns_name, 'text') . ", " . $db->quote($account, 'text') . ")");
  449. return true;
  450. }
  451. }
  452. /** Delete Supermaster
  453. *
  454. * Delete a supermaster from the global supermasters table
  455. *
  456. * @param string $master_ip Supermaster IP address
  457. * @param string $ns_name Hostname of supermaster
  458. *
  459. * @return boolean true on success
  460. */
  461. function delete_supermaster($master_ip, $ns_name) {
  462. global $db;
  463. if (is_valid_ipv4($master_ip) || is_valid_ipv6($master_ip) || is_valid_hostname_fqdn($ns_name, 0)) {
  464. $db->query("DELETE FROM supermasters WHERE ip = " . $db->quote($master_ip, 'text') .
  465. " AND nameserver = " . $db->quote($ns_name, 'text'));
  466. return true;
  467. } else {
  468. error(sprintf(ERR_INV_ARGC, "delete_supermaster", "No or no valid ipv4 or ipv6 address given."));
  469. }
  470. }
  471. /** Get Supermaster Info from IP
  472. *
  473. * Retrieve supermaster details from supermaster IP address
  474. *
  475. * @param string $master_ip Supermaster IP address
  476. *
  477. * @return mixed[] array of supermaster details
  478. */
  479. function get_supermaster_info_from_ip($master_ip) {
  480. global $db;
  481. if (is_valid_ipv4($master_ip) || is_valid_ipv6($master_ip)) {
  482. $result = $db->queryRow("SELECT ip,nameserver,account FROM supermasters WHERE ip = " . $db->quote($master_ip, 'text'));
  483. $ret = array(
  484. "master_ip" => $result["ip"],
  485. "ns_name" => $result["nameserver"],
  486. "account" => $result["account"]
  487. );
  488. return $ret;
  489. } else {
  490. error(sprintf(ERR_INV_ARGC, "get_supermaster_info_from_ip", "No or no valid ipv4 or ipv6 address given."));
  491. }
  492. }
  493. /** Get record details from Record ID
  494. *
  495. * @param $rid Record ID
  496. *
  497. * @return mixed[] array of record details [rid,zid,name,type,content,ttl,prio,change_date]
  498. */
  499. function get_record_details_from_record_id($rid) {
  500. global $db;
  501. $query = "SELECT id AS rid, domain_id AS zid, name, type, content, ttl, prio, change_date FROM records WHERE id = " . $db->quote($rid, 'integer');
  502. $response = $db->query($query);
  503. if (PEAR::isError($response)) {
  504. error($response->getMessage());
  505. return false;
  506. }
  507. $return = $response->fetchRow();
  508. return $return;
  509. }
  510. /** Delete a record by a given record id
  511. *
  512. * @param int $rid Record ID
  513. *
  514. * @return boolean true on success
  515. */
  516. function delete_record($rid) {
  517. global $db;
  518. if (verify_permission('zone_content_edit_others')) {
  519. $perm_content_edit = "all";
  520. } elseif (verify_permission('zone_content_edit_own')) {
  521. $perm_content_edit = "own";
  522. } else {
  523. $perm_content_edit = "none";
  524. }
  525. // Determine ID of zone first.
  526. $record = get_record_details_from_record_id($rid);
  527. $user_is_zone_owner = verify_user_is_owner_zoneid($record['zid']);
  528. if ($perm_content_edit == "all" || ($perm_content_edit == "own" && $user_is_zone_owner == "1" )) {
  529. if ($record['type'] == "SOA") {
  530. error(_('You are trying to delete the SOA record. You are not allowed to remove it, unless you remove the entire zone.'));
  531. } else {
  532. $query = "DELETE FROM records WHERE id = " . $db->quote($rid, 'integer');
  533. $response = $db->query($query);
  534. if (PEAR::isError($response)) {
  535. error($response->getMessage());
  536. return false;
  537. }
  538. return true;
  539. }
  540. } else {
  541. error(ERR_PERM_DEL_RECORD);
  542. return false;
  543. }
  544. }
  545. /** Delete record reference to zone template
  546. *
  547. * @param int $rid Record ID
  548. *
  549. * @return boolean true on success
  550. */
  551. function delete_record_zone_templ($rid) {
  552. global $db;
  553. $query = "DELETE FROM records_zone_templ WHERE record_id = " . $db->quote($rid, 'integer');
  554. $response = $db->query($query);
  555. if (PEAR::isError($response)) {
  556. error($response->getMessage());
  557. return false;
  558. }
  559. return true;
  560. }
  561. /**
  562. * Add a domain to the database
  563. *
  564. * A domain is name obligatory, so is an owner.
  565. * return values: true when succesful.
  566. *
  567. * Empty means templates dont have to be applied.
  568. *
  569. * This functions eats a template and by that it inserts various records.
  570. * first we start checking if something in an arpa record
  571. * remember to request nextID's from the database to be able to insert record.
  572. * if anything is invalid the function will error
  573. *
  574. * @param string $domain A domain name
  575. * @param int $owner Owner ID for domain
  576. * @param string $type Type of domain ['NATIVE','MASTER','SLAVE']
  577. * @param string $slave_master Master server hostname for domain
  578. * @param int|string $zone_template ID of zone template ['none' or int]
  579. *
  580. * @return boolean true on success
  581. */
  582. function add_domain($domain, $owner, $type, $slave_master, $zone_template) {
  583. if (verify_permission('zone_master_add')) {
  584. $zone_master_add = "1";
  585. }
  586. if (verify_permission('zone_slave_add')) {
  587. $zone_slave_add = "1";
  588. }
  589. // TODO: make sure only one is possible if only one is enabled
  590. if ($zone_master_add == "1" || $zone_slave_add == "1") {
  591. global $db;
  592. global $dns_ns1;
  593. global $dns_hostmaster;
  594. global $dns_ttl;
  595. global $db_layer;
  596. global $db_type;
  597. if (($domain && $owner && $zone_template) ||
  598. (preg_match('/in-addr.arpa/i', $domain) && $owner && $zone_template) ||
  599. $type == "SLAVE" && $domain && $owner && $slave_master) {
  600. $response = $db->query("INSERT INTO domains (name, type) VALUES (" . $db->quote($domain, 'text') . ", " . $db->quote($type, 'text') . ")");
  601. if (PEAR::isError($response)) {
  602. error($response->getMessage());
  603. return false;
  604. }
  605. if ($db_layer == 'MDB2' && ($db_type == 'mysql' || $db_type == 'pgsql')) {
  606. $domain_id = $db->lastInsertId('domains', 'id');
  607. } else if ($db_layer == 'PDO' && $db_type == 'pgsql') {
  608. $domain_id = $db->lastInsertId('domains_id_seq');
  609. } else {
  610. $domain_id = $db->lastInsertId();
  611. }
  612. if (PEAR::isError($domain_id)) {
  613. error($domain_id->getMessage());
  614. return false;
  615. }
  616. $response = $db->query("INSERT INTO zones (domain_id, owner, zone_templ_id) VALUES (" . $db->quote($domain_id, 'integer') . ", " . $db->quote($owner, 'integer') . ", " . $db->quote(($zone_template == "none") ? 0 : $zone_template, 'integer') . ")");
  617. if (PEAR::isError($response)) {
  618. error($response->getMessage());
  619. return false;
  620. }
  621. if ($type == "SLAVE") {
  622. $response = $db->query("UPDATE domains SET master = " . $db->quote($slave_master, 'text') . " WHERE id = " . $db->quote($domain_id, 'integer'));
  623. if (PEAR::isError($response)) {
  624. error($response->getMessage());
  625. return false;
  626. }
  627. return true;
  628. } else {
  629. $now = time();
  630. if ($zone_template == "none" && $domain_id) {
  631. $ns1 = $dns_ns1;
  632. $hm = $dns_hostmaster;
  633. $ttl = $dns_ttl;
  634. set_timezone();
  635. $serial = date("Ymd");
  636. $serial .= "00";
  637. $query = "INSERT INTO records (domain_id, name, content, type, ttl, prio, change_date) VALUES ("
  638. . $db->quote($domain_id, 'integer') . ","
  639. . $db->quote($domain, 'text') . ","
  640. . $db->quote($ns1 . ' ' . $hm . ' ' . $serial . ' 28800 7200 604800 86400', 'text') . ","
  641. . $db->quote('SOA', 'text') . ","
  642. . $db->quote($ttl, 'integer') . ","
  643. . $db->quote(0, 'integer') . ","
  644. . $db->quote($now, 'integer') . ")";
  645. $response = $db->query($query);
  646. if (PEAR::isError($response)) {
  647. error($response->getMessage());
  648. return false;
  649. }
  650. return true;
  651. } elseif ($domain_id && is_numeric($zone_template)) {
  652. global $dns_ttl;
  653. $templ_records = get_zone_templ_records($zone_template);
  654. if ($templ_records != -1) {
  655. foreach ($templ_records as $r) {
  656. if ((preg_match('/in-addr.arpa/i', $domain) && ($r["type"] == "NS" || $r["type"] == "SOA")) || (!preg_match('/in-addr.arpa/i', $domain))) {
  657. $name = parse_template_value($r["name"], $domain);
  658. $type = $r["type"];
  659. $content = parse_template_value($r["content"], $domain);
  660. $ttl = $r["ttl"];
  661. $prio = intval($r["prio"]);
  662. if (!$ttl) {
  663. $ttl = $dns_ttl;
  664. }
  665. $query = "INSERT INTO records (domain_id, name, type, content, ttl, prio, change_date) VALUES ("
  666. . $db->quote($domain_id, 'integer') . ","
  667. . $db->quote($name, 'text') . ","
  668. . $db->quote($type, 'text') . ","
  669. . $db->quote($content, 'text') . ","
  670. . $db->quote($ttl, 'integer') . ","
  671. . $db->quote($prio, 'integer') . ","
  672. . $db->quote($now, 'integer') . ")";
  673. $response = $db->query($query);
  674. if (PEAR::isError($response)) {
  675. error($response->getMessage());
  676. return false;
  677. }
  678. if ($db_layer == 'MDB2' && ($db_type == 'mysql' || $db_type == 'pgsql')) {
  679. $record_id = $db->lastInsertId('records', 'id');
  680. } else if ($db_layer == 'PDO' && $db_type == 'pgsql') {
  681. $record_id = $db->lastInsertId('records_id_seq');
  682. } else {
  683. $record_id = $db->lastInsertId();
  684. }
  685. if (PEAR::isError($record_id)) {
  686. error($record_id->getMessage());
  687. return false;
  688. }
  689. $query = "INSERT INTO records_zone_templ (domain_id, record_id, zone_templ_id) VALUES ("
  690. . $db->quote($domain_id, 'integer') . ","
  691. . $db->quote($record_id, 'integer') . ","
  692. . $db->quote($r['zone_templ_id'], 'integer') . ")";
  693. $response = $db->query($query);
  694. if (PEAR::isError($response)) {
  695. error($response->getMessage());
  696. return false;
  697. }
  698. }
  699. }
  700. }
  701. return true;
  702. } else {
  703. error(sprintf(ERR_INV_ARGC, "add_domain", "could not create zone"));
  704. }
  705. }
  706. } else {
  707. error(sprintf(ERR_INV_ARG, "add_domain"));
  708. }
  709. } else {
  710. error(ERR_PERM_ADD_ZONE_MASTER);
  711. return false;
  712. }
  713. }
  714. /** Deletes a domain by a given id
  715. *
  716. * Function always succeeds. If the field is not found in the database, thats what we want anyway.
  717. *
  718. * @param int $id Zone ID
  719. *
  720. * @return boolean true on success
  721. */
  722. function delete_domain($id) {
  723. global $db;
  724. if (verify_permission('zone_content_edit_others')) {
  725. $perm_edit = "all";
  726. } elseif (verify_permission('zone_content_edit_own')) {
  727. $perm_edit = "own";
  728. } else {
  729. $perm_edit = "none";
  730. }
  731. $user_is_zone_owner = verify_user_is_owner_zoneid($id);
  732. if ($perm_edit == "all" || ( $perm_edit == "own" && $user_is_zone_owner == "1")) {
  733. if (is_numeric($id)) {
  734. $db->query("DELETE FROM zones WHERE domain_id=" . $db->quote($id, 'integer'));
  735. $db->query("DELETE FROM domains WHERE id=" . $db->quote($id, 'integer'));
  736. $db->query("DELETE FROM records WHERE domain_id=" . $db->quote($id, 'integer'));
  737. $db->query("DELETE FROM records_zone_templ WHERE domain_id=" . $db->quote($id, 'integer'));
  738. return true;
  739. } else {
  740. error(sprintf(ERR_INV_ARGC, "delete_domain", "id must be a number"));
  741. return false;
  742. }
  743. } else {
  744. error(ERR_PERM_DEL_ZONE);
  745. }
  746. }
  747. /** Record ID to Domain ID
  748. *
  749. * Gets the id of the domain by a given record id
  750. *
  751. * @param int $id Record ID
  752. * @return int Domain ID of record
  753. */
  754. function recid_to_domid($id) {
  755. global $db;
  756. if (is_numeric($id)) {
  757. $result = $db->query("SELECT domain_id FROM records WHERE id=" . $db->quote($id, 'integer'));
  758. $r = $result->fetchRow();
  759. return $r["domain_id"];
  760. } else {
  761. error(sprintf(ERR_INV_ARGC, "recid_to_domid", "id must be a number"));
  762. }
  763. }
  764. /** Change owner of a domain
  765. *
  766. * @param int $zone_id Zone ID
  767. * @param int $user_id User ID
  768. *
  769. * @return boolean true when succesful
  770. */
  771. function add_owner_to_zone($zone_id, $user_id) {
  772. global $db;
  773. if ((verify_permission('zone_meta_edit_others')) || (verify_permission('zone_meta_edit_own')) && verify_user_is_owner_zoneid($_GET["id"])) {
  774. // User is allowed to make change to meta data of this zone.
  775. if (is_numeric($zone_id) && is_numeric($user_id) && is_valid_user($user_id)) {
  776. if ($db->queryOne("SELECT COUNT(id) FROM zones WHERE owner=" . $db->quote($user_id, 'integer') . " AND domain_id=" . $db->quote($zone_id, 'integer')) == 0) {
  777. $zone_templ_id = get_zone_template($zone_id);
  778. if ($zone_templ_id == NULL)
  779. $zone_templ_id = 0;
  780. $db->query("INSERT INTO zones (domain_id, owner, zone_templ_id) VALUES("
  781. . $db->quote($zone_id, 'integer') . ", "
  782. . $db->quote($user_id, 'integer') . ", "
  783. . $db->quote($zone_templ_id, 'integer') . ")"
  784. );
  785. }
  786. return true;
  787. } else {
  788. error(sprintf(ERR_INV_ARGC, "add_owner_to_zone", "$zone_id / $user_id"));
  789. }
  790. } else {
  791. return false;
  792. }
  793. }
  794. /** Delete owner from zone
  795. *
  796. * @param int $zone_id Zone ID
  797. * @param int $user_id User ID
  798. *
  799. * @return boolean true on success
  800. */
  801. function delete_owner_from_zone($zone_id, $user_id) {
  802. global $db;
  803. if ((verify_permission('zone_meta_edit_others')) || (verify_permission('zone_meta_edit_own')) && verify_user_is_owner_zoneid($_GET["id"])) {
  804. // User is allowed to make change to meta data of this zone.
  805. if (is_numeric($zone_id) && is_numeric($user_id) && is_valid_user($user_id)) {
  806. // TODO: Next if() required, why not just execute DELETE query?
  807. if ($db->queryOne("SELECT COUNT(id) FROM zones WHERE owner=" . $db->quote($user_id, 'integer') . " AND domain_id=" . $db->quote($zone_id, 'integer')) != 0) {
  808. $db->query("DELETE FROM zones WHERE owner=" . $db->quote($user_id, 'integer') . " AND domain_id=" . $db->quote($zone_id, 'integer'));
  809. }
  810. return true;
  811. } else {
  812. error(sprintf(ERR_INV_ARGC, "delete_owner_from_zone", "$zone_id / $user_id"));
  813. }
  814. } else {
  815. return false;
  816. }
  817. }
  818. /** Retrieve all supported dns record types
  819. *
  820. * This function might be deprecated.
  821. *
  822. * @return string[] array of types
  823. */
  824. function get_record_types() {
  825. global $rtypes;
  826. return $rtypes;
  827. }
  828. /** Retrieve all records by a given type and domain id
  829. *
  830. * Example get all records that are of type A from domain id 1
  831. *
  832. * <code>
  833. * get_records_by_type_from_domid('A', 1)
  834. * </code>
  835. *
  836. * @param string $type Record type
  837. * @param int $recid Record ID
  838. *
  839. * @return object a DB class result object
  840. */
  841. function get_records_by_type_from_domid($type, $recid) {
  842. global $rtypes;
  843. global $db;
  844. // Does this type exist?
  845. if (!in_array(strtoupper($type), $rtypes)) {
  846. error(sprintf(ERR_INV_ARGC, "get_records_from_type", "this is not a supported record"));
  847. }
  848. // Get the domain id.
  849. $domid = recid_to_domid($recid);
  850. $result = $db->query("select id, type from records where domain_id=" . $db->quote($recid, 'integer') . " and type=" . $db->quote($type, 'text'));
  851. return $result;
  852. }
  853. /** Get Record Type for Record ID
  854. *
  855. * Retrieves the type of a record from a given id.
  856. *
  857. * @param int $id Record ID
  858. * @return string Record type (one of the records types in $rtypes assumable).
  859. */
  860. function get_recordtype_from_id($id) {
  861. global $db;
  862. if (is_numeric($id)) {
  863. $result = $db->query("SELECT type FROM records WHERE id=" . $db->quote($id, 'integer'));
  864. $r = $result->fetchRow();
  865. return $r["type"];
  866. } else {
  867. error(sprintf(ERR_INV_ARG, "get_recordtype_from_id"));
  868. }
  869. }
  870. /** Get Name from Record ID
  871. *
  872. * Retrieves the name (e.g. bla.test.com) of a record by a given id.
  873. *
  874. * @param int $id Record ID
  875. * @return string Name part of record
  876. */
  877. function get_name_from_record_id($id) {
  878. global $db;
  879. if (is_numeric($id)) {
  880. $result = $db->query("SELECT name FROM records WHERE id=" . $db->quote($id, 'integer'));
  881. $r = $result->fetchRow();
  882. return $r["name"];
  883. } else {
  884. error(sprintf(ERR_INV_ARG, "get_name_from_record_id"));
  885. }
  886. }
  887. /** Get Zone Name from Zone ID
  888. *
  889. * @param int $zid Zone ID
  890. *
  891. * @return string Domain name
  892. */
  893. function get_zone_name_from_id($zid) {
  894. global $db;
  895. if (is_numeric($zid)) {
  896. $result = $db->queryRow("SELECT name FROM domains WHERE id=" . $db->quote($zid, 'integer'));
  897. if ($result) {
  898. return $result["name"];
  899. } else {
  900. error(sprintf("Zone does not exist."));
  901. return false;
  902. }
  903. } else {
  904. error(sprintf(ERR_INV_ARGC, "get_zone_name_from_id", "Not a valid domainid: $zid"));
  905. }
  906. }
  907. /** Get zone id from name
  908. *
  909. * @param string $zname Zone name
  910. * @return int Zone ID
  911. */
  912. function get_zone_id_from_name($zname) {
  913. global $db;
  914. if (!empty($zname)) {
  915. $result = $db->queryRow("SELECT id FROM domains WHERE name=" . $db->quote($zname, 'text'));
  916. if ($result) {
  917. return $result["id"];
  918. } elseif ($rows == "0") {
  919. error(sprintf("Zone does not exist."));
  920. return false;
  921. }
  922. } else {
  923. error(sprintf(ERR_INV_ARGC, "get_zone_id_from_name", "Not a valid domainname: $zname"));
  924. }
  925. }
  926. /** Get Zone details from Zone ID
  927. *
  928. * @param int $zid Zone ID
  929. * @return mixed[] array of zone details [type,name,master_ip,record_count]
  930. */
  931. function get_zone_info_from_id($zid) {
  932. if (verify_permission('zone_content_view_others')) {
  933. $perm_view = "all";
  934. } elseif (verify_permission('zone_content_view_own')) {
  935. $perm_view = "own";
  936. } else {
  937. $perm_view = "none";
  938. }
  939. if ($perm_view == "none") {
  940. error(ERR_PERM_VIEW_ZONE);
  941. } else {
  942. global $db;
  943. $query = "SELECT domains.type AS type,
  944. domains.name AS name,
  945. domains.master AS master_ip,
  946. count(records.domain_id) AS record_count
  947. FROM domains LEFT OUTER JOIN records ON domains.id = records.domain_id
  948. WHERE domains.id = " . $db->quote($zid, 'integer') . "
  949. GROUP BY domains.id, domains.type, domains.name, domains.master";
  950. $result = $db->queryRow($query);
  951. $return = array(
  952. "name" => $result['name'],
  953. "type" => $result['type'],
  954. "master_ip" => $result['master_ip'],
  955. "record_count" => $result['record_count']
  956. );
  957. return $return;
  958. }
  959. }
  960. /** Convert IPv6 Address to PTR
  961. *
  962. * @param string $ip IPv6 Address
  963. * @return string PTR form of address
  964. */
  965. function convert_ipv6addr_to_ptrrec($ip) {
  966. // rev-patch
  967. // taken from: http://stackoverflow.com/questions/6619682/convert-ipv6-to-nibble-format-for-ptr-records
  968. // PHP (>= 5.1.0, or 5.3+ on Windows), use the inet_pton
  969. // $ip = '2001:db8::567:89ab';
  970. $addr = inet_pton($ip);
  971. $unpack = unpack('H*hex', $addr);
  972. $hex = $unpack['hex'];
  973. $arpa = implode('.', array_reverse(str_split($hex))) . '.ip6.arpa';
  974. return $arpa;
  975. }
  976. /** Get Best Matching in-addr.arpa Zone ID from Domain Name
  977. *
  978. * @param string $domain Domain name
  979. *
  980. * @return int Zone ID
  981. */
  982. function get_best_matching_zone_id_from_name($domain) {
  983. // rev-patch
  984. // tring to find the correct zone
  985. // %ip6.arpa and %in-addr.arpa is looked for
  986. global $db;
  987. $match = 72; // the longest ip6.arpa has a length of 72
  988. $found_domain_id = -1;
  989. // get all reverse-zones
  990. $query = "SELECT name, id FROM domains
  991. WHERE name like " . $db->quote('%.arpa', 'text') . "
  992. ORDER BY length(name) DESC";
  993. $response = $db->query($query);
  994. if (PEAR::isError($response)) {
  995. error($response->getMessage());
  996. return false;
  997. }
  998. if ($response) {
  999. while ($r = $response->fetchRow()) {
  1000. $pos = stripos($domain, $r["name"]);
  1001. if ($pos !== false) {
  1002. // one possible searched $domain is found
  1003. if ($pos < $match) {
  1004. $match = $pos;
  1005. $found_domain_id = $r["id"];
  1006. }
  1007. }
  1008. }
  1009. } else {
  1010. return -1;
  1011. }
  1012. return $found_domain_id;
  1013. }
  1014. /** Check if Domain Exists
  1015. *
  1016. * Check if a domain is already existing.
  1017. *
  1018. * @param string $domain Domain name
  1019. * @return boolean true if existing, false if it doesnt exist.
  1020. */
  1021. function domain_exists($domain) {
  1022. global $db;
  1023. if (is_valid_hostname_fqdn($domain, 0)) {
  1024. $result = $db->queryRow("SELECT id FROM domains WHERE name=" . $db->quote($domain, 'text'));
  1025. return ($result ? true : false);
  1026. } else {
  1027. error(ERR_DOMAIN_INVALID);
  1028. }
  1029. }
  1030. /** Get All Supermasters
  1031. *
  1032. * Gets an array of arrays of supermaster details
  1033. *
  1034. * @return array[] supermasters detail [master_ip,ns_name,account]s
  1035. */
  1036. function get_supermasters() {
  1037. global $db;
  1038. $result = $db->query("SELECT ip, nameserver, account FROM supermasters");
  1039. if (PEAR::isError($result)) {
  1040. error($result->getMessage());
  1041. return false;
  1042. }
  1043. $ret = array();
  1044. while ($r = $result->fetchRow()) {
  1045. $ret[] = array(
  1046. "master_ip" => $r["ip"],
  1047. "ns_name" => $r["nameserver"],
  1048. "account" => $r["account"],
  1049. );
  1050. }
  1051. return (sizeof($ret) == 0 ? -1 : $ret);
  1052. }
  1053. /** Check if Supermaster IP address exists
  1054. *
  1055. * @param string $master_ip Supermaster IP
  1056. *
  1057. * @return boolean true if exists, otherwise false
  1058. */
  1059. function supermaster_exists($master_ip) {
  1060. global $db;
  1061. if (is_valid_ipv4($master_ip, false) || is_valid_ipv6($master_ip)) {
  1062. $result = $db->queryOne("SELECT ip FROM supermasters WHERE ip = " . $db->quote($master_ip, 'text'));
  1063. return ($result ? true : false);
  1064. } else {
  1065. error(sprintf(ERR_INV_ARGC, "supermaster_exists", "No or no valid IPv4 or IPv6 address given."));
  1066. }
  1067. }
  1068. /** Check if Supermaster IP Address and NS Name combo exists
  1069. *
  1070. * @param string $master_ip Supermaster IP Address
  1071. * @param string $ns_name Supermaster NS Name
  1072. *
  1073. * @return boolean true if exists, false otherwise
  1074. */
  1075. function supermaster_ip_name_exists($master_ip, $ns_name) {
  1076. global $db;
  1077. if ((is_valid_ipv4($master_ip) || is_valid_ipv6($master_ip)) && is_valid_hostname_fqdn($ns_name, 0)) {
  1078. $result = $db->queryOne("SELECT ip FROM supermasters WHERE ip = " . $db->quote($master_ip, 'text') .
  1079. " AND nameserver = " . $db->quote($ns_name, 'text'));
  1080. return ($result ? true : false);
  1081. } else {
  1082. error(sprintf(ERR_INV_ARGC, "supermaster_exists", "No or no valid IPv4 or IPv6 address given."));
  1083. }
  1084. }
  1085. /** Get Zones
  1086. *
  1087. * @param string $perm View Zone Permissions ['own','all','none']
  1088. * @param int $userid Requesting User ID
  1089. * @param string $letterstart Starting letters to match [default='all']
  1090. * @param int $rowstart Start from row in set [default=0]
  1091. * @param int $rowamount Max number of rows to fetch for this query when not 'all' [default=999999]
  1092. * @param string $sortby Column to sort results by [default='name']
  1093. *
  1094. * @return boolean|mixed[] false or array of zone details [id,name,type,count_records]
  1095. */
  1096. function get_zones($perm, $userid = 0, $letterstart = 'all', $rowstart = 0, $rowamount = 999999, $sortby = 'name') {
  1097. global $db;
  1098. global $db_type;
  1099. global $sql_regexp;
  1100. if ($letterstart == '_') {
  1101. $letterstart = '\_';
  1102. }
  1103. $sql_add = '';
  1104. if ($perm != "own" && $perm != "all") {
  1105. error(ERR_PERM_VIEW_ZONE);
  1106. return false;
  1107. } else {
  1108. if ($perm == "own") {
  1109. $sql_add = " AND zones.domain_id = domains.id
  1110. AND zones.owner = " . $db->quote($userid, 'integer');
  1111. }
  1112. if ($letterstart != 'all' && $letterstart != 1) {
  1113. $sql_add .=" AND domains.name LIKE " . $db->quote($letterstart . "%", 'text') . " ";
  1114. } elseif ($letterstart == 1) {
  1115. $sql_add .=" AND substring(domains.name,1,1) " . $sql_regexp . " '^[[:digit:]]'";
  1116. }
  1117. }
  1118. if ($sortby != 'count_records') {
  1119. $sortby = 'domains.' . $sortby;
  1120. }
  1121. $natural_sort = 'LENGTH(domains.name), domains.name';
  1122. if ($db_type == 'mysql' || $db_type == 'mysqli' || $db_type == 'sqlite' || $db_type == 'sqlite3') {
  1123. $natural_sort = 'domains.name+0<>0 DESC, domains.name+0, domains.name';
  1124. }
  1125. $sql_sortby = ($sortby == 'domains.name' ? $natural_sort : $sortby . ', ' . $natural_sort);
  1126. $sqlq = "SELECT domains.id,
  1127. domains.name,
  1128. domains.type,
  1129. Record_Count.count_records
  1130. FROM domains
  1131. LEFT JOIN zones ON domains.id=zones.domain_id
  1132. LEFT JOIN (
  1133. SELECT COUNT(domain_id) AS count_records, domain_id FROM records GROUP BY domain_id
  1134. ) Record_Count ON Record_Count.domain_id=domains.id
  1135. WHERE 1=1" . $sql_add . "
  1136. GROUP BY domains.name, domains.id, domains.type, Record_Count.count_records
  1137. ORDER BY " . $sql_sortby;
  1138. if ($letterstart != 'all') {
  1139. $db->setLimit($rowamount, $rowstart);
  1140. }
  1141. $result = $db->query($sqlq);
  1142. $ret = array();
  1143. while ($r = $result->fetchRow()) {
  1144. //fixme: name is not guaranteed to be unique with round-robin record sets
  1145. $ret[$r["name"]] = array(
  1146. "id" => $r["id"],
  1147. "name" => $r["name"],
  1148. "type" => $r["type"],
  1149. "count_records" => $r["count_records"]
  1150. );
  1151. }
  1152. return $ret;
  1153. }
  1154. // TODO: letterstart limitation and userid permission limitiation should be applied at the same time?
  1155. // fixme: letterstart 'all' forbids searching for domains that actually start with 'all'
  1156. /** Get Count of Zones
  1157. *
  1158. * @param string $perm 'all', 'own' uses session 'userid'
  1159. * @param string $letterstart Starting letters to match [default='all']
  1160. *
  1161. * @return int Count of zones matched
  1162. */
  1163. function zone_count_ng($perm, $letterstart = 'all') {
  1164. global $db;
  1165. global $sql_regexp;
  1166. $fromTable = 'domains';
  1167. $sql_add = '';
  1168. if ($perm != "own" && $perm != "all") {
  1169. $zone_count = "0";
  1170. } else {
  1171. if ($perm == "own") {
  1172. $sql_add = " AND zones.domain_id = domains.id
  1173. AND zones.owner = " . $db->quote($_SESSION['userid'], 'integer');
  1174. $fromTable .= ',zones';
  1175. }
  1176. if ($letterstart != 'all' && $letterstart != 1) {
  1177. $sql_add .=" AND domains.name LIKE " . $db->quote($letterstart . "%", 'text') . " ";
  1178. } elseif ($letterstart == 1) {
  1179. $sql_add .=" AND substring(domains.name,1,1) " . $sql_regexp . " '^[[:digit:]]'";
  1180. }
  1181. # XXX: do we really need this distinct directive as it's unsupported in sqlite)
  1182. # $sqlq = "SELECT COUNT(distinct domains.id) AS count_zones
  1183. $sqlq = "SELECT COUNT(domains.id) AS count_zones
  1184. FROM " . $fromTable . " WHERE 1=1
  1185. " . $sql_add;
  1186. $zone_count = $db->queryOne($sqlq);
  1187. }
  1188. return $zone_count;
  1189. }
  1190. /** Get Zone Count for Owner User ID
  1191. *
  1192. * @param int $uid User ID
  1193. *
  1194. * @return int Count of Zones matched
  1195. */
  1196. function zone_count_for_uid($uid) {
  1197. global $db;
  1198. $query = "SELECT COUNT(domain_id)
  1199. FROM zones
  1200. WHERE owner = " . $db->quote($uid, 'integer') . "
  1201. ORDER BY domain_id";
  1202. $zone_count = $db->queryOne($query);
  1203. return $zone_count;
  1204. }
  1205. /** Get a Record from an Record ID
  1206. *
  1207. * Retrieve all fields of the record and send it back to the function caller.
  1208. *
  1209. * @param int $id Record ID
  1210. * @return int|mixed[] array of record detail, or -1 if nothing found
  1211. */
  1212. function get_record_from_id($id) {
  1213. global $db;
  1214. if (is_numeric($id)) {
  1215. $result = $db->queryRow("SELECT id, domain_id, name, type, content, ttl, prio, change_date FROM records WHERE id=" . $db->quote($id, 'integer'));
  1216. if ($result) {
  1217. $ret = array(
  1218. "id" => $result["id"],
  1219. "domain_id" => $result["domain_id"],
  1220. "name" => $result["name"],
  1221. "type" => $result["type"],
  1222. "content" => $result["content"],
  1223. "ttl" => $result["ttl"],
  1224. "prio" => $result["prio"],
  1225. "change_date" => $result["change_date"]
  1226. );
  1227. return $ret;
  1228. } else {
  1229. return -1;
  1230. }
  1231. } else {
  1232. error(sprintf(ERR_INV_ARG, "get_record_from_id"));
  1233. }
  1234. }
  1235. /** Get all records from a domain id.
  1236. *
  1237. * Retrieve all fields of the records and send it back to the function caller.
  1238. *
  1239. * @param int $id Domain ID
  1240. * @param int $rowstart Starting row [default=0]
  1241. * @param int $rowamount Number of rows to return in this query [default=999999]
  1242. * @param string $sortby Column to sort by [default='name']
  1243. *
  1244. * @return int|mixed[] array of record detail, or -1 if nothing found
  1245. */
  1246. function get_records_from_domain_id($id, $rowstart = 0, $rowamount = 999999, $sortby = 'name') {
  1247. global $db;
  1248. global $db_type;
  1249. $result = array();
  1250. if (is_numeric($id)) {
  1251. if ((isset($_SESSION[$id . "_ispartial"])) && ($_SESSION[$id . "_ispartial"] == 1)) {
  1252. $db->setLimit($rowamount, $rowstart);
  1253. $result = $db->query("SELECT record_owners.record_id as id
  1254. FROM record_owners,domains,records
  1255. WHERE record_owners.user_id = " . $db->quote($_SESSION["userid"], 'integer') . "
  1256. AND record_owners.record_id = records.id
  1257. AND records.domain_id = " . $db->quote($id, 'integer') . "
  1258. GROUP BY record_owners.record_id ORDER BY records." . $sortby);
  1259. $ret = array();
  1260. if ($result) {
  1261. $ret[] = array();
  1262. $retcount = 0;
  1263. while ($r = $result->fetchRow()) {
  1264. // Call get_record_from_id for each row.
  1265. $ret[$retcount] = get_record_from_id($r["id"]);
  1266. $retcount++;
  1267. }
  1268. $result = $ret;
  1269. } else {
  1270. return -1;
  1271. }
  1272. } else {
  1273. $db->setLimit($rowamount, $rowstart);
  1274. $natural_sort = 'LENGTH(records.name), records.name';
  1275. if ($db_type == 'mysql' || $db_type == 'mysqli' || $db_type == 'sqlite' || $db_type == 'sqlite3') {
  1276. $natural_sort = 'records.name+0<>0 DESC, records.name+0, records.name';
  1277. }
  1278. $sql_sortby = ($sortby == 'name' ? $natural_sort : $sortby . ', ' . $natural_sort);
  1279. $result = $db->query("SELECT id FROM records WHERE domain_id=" . $db->quote($id, 'integer') . " ORDER BY " . $sql_sortby);
  1280. $ret = array();
  1281. if ($result) {
  1282. $ret[] = array();
  1283. $retcount = 0;
  1284. while ($r = $result->fetchRow()) {
  1285. // Call get_record_from_id for each row.
  1286. $ret[$retcount] = get_record_from_id($r["id"]);
  1287. $retcount++;
  1288. }
  1289. $result = $ret;
  1290. } else {
  1291. return -1;
  1292. }
  1293. $result = order_domain_results($result, $sortby);
  1294. return $result;
  1295. }
  1296. } else {
  1297. error(sprintf(ERR_INV_ARG, "get_records_from_domain_id"));
  1298. }
  1299. }
  1300. /** Sort Domain Records intelligently
  1301. *
  1302. * @param string[] $domains Array of domains
  1303. * @param string $sortby Column to sort by [default='name','type','content','prio','ttl']
  1304. *
  1305. * @return mixed[] array of records detail
  1306. */
  1307. function order_domain_results($domains, $sortby) {
  1308. $results = array();
  1309. $soa = array();
  1310. $ns = array();
  1311. foreach ($domains as $key => $domain) {
  1312. switch ($domain['type']) {
  1313. case 'SOA':
  1314. $soa[] = $domain;
  1315. unset($domains[$key]);
  1316. break;
  1317. case 'NS':
  1318. $ns[] = $domain;
  1319. unset($domains[$key]);
  1320. break;
  1321. default:
  1322. continue;
  1323. }
  1324. }
  1325. switch ($sortby) {
  1326. case 'name':
  1327. usort($domains, 'sort_domain_results_by_name');
  1328. break;
  1329. case 'type':
  1330. usort($domains, 'sort_domain_results_by_type');
  1331. break;
  1332. case 'content':
  1333. usort($domains, 'sort_domain_results_by_content');
  1334. break;
  1335. case 'prio':
  1336. usort($domains, 'sort_domain_results_by_prio');
  1337. break;
  1338. case 'ttl':
  1339. usort($domains, 'sort_domain_results_by_ttl');
  1340. break;
  1341. default:
  1342. usort($domains, 'sort_domain_results_by_name');
  1343. break;
  1344. }
  1345. $results = array_merge($soa, $ns);
  1346. $results = array_merge($results, $domains);
  1347. return $results;
  1348. }
  1349. /** Sort records by name
  1350. *
  1351. * @param mixed[] $a A
  1352. * @param mixed[] $b B
  1353. *
  1354. * @return mixed[] result of strnatcmp
  1355. */
  1356. function sort_domain_results_by_name($a, $b) {
  1357. return strnatcmp($a['name'], $b['name']);
  1358. }
  1359. /** Sort records by type
  1360. *
  1361. * @param mixed[] $a A
  1362. * @param mixed[] $b B
  1363. *
  1364. * @return mixed[] result of strnatcmp
  1365. */
  1366. function sort_domain_results_by_type($a, $b) {
  1367. if ($a['type'] != $b['type']) {
  1368. return strnatcmp($a['type'], $b['type']);
  1369. } else {
  1370. return strnatcmp($a['name'], $b['name']);
  1371. }
  1372. }
  1373. /** Sort records by content
  1374. *
  1375. * @param mixed[] $a A
  1376. * @param mixed[] $b B
  1377. *
  1378. * @return mixed[] result of strnatcmp
  1379. */
  1380. function sort_domain_results_by_content($a, $b) {
  1381. if ($a['content'] != $b['content']) {
  1382. return strnatcmp($a['content'], $b['content']);
  1383. } else {
  1384. return strnatcmp($a['name'], $b['name']);
  1385. }
  1386. }
  1387. /** Sort records by prio
  1388. *
  1389. * @param mixed[] $a A
  1390. * @param mixed[] $b B
  1391. *
  1392. * @return mixed[] result of strnatcmp
  1393. */
  1394. function sort_domain_results_by_prio($a, $b) {
  1395. if ($a['prio'] != $b['prio']) {
  1396. return strnatcmp($a['prio'], $b['prio']);
  1397. } else {
  1398. return strnatcmp($a['name'], $b['name']);
  1399. }
  1400. }
  1401. /** Sort records by TTL
  1402. *
  1403. * @param mixed[] $a A
  1404. * @param mixed[] $b B
  1405. *
  1406. * @return mixed[] result of strnatcmp
  1407. */
  1408. function sort_domain_results_by_ttl($a, $b) {
  1409. if ($a['ttl'] != $b['ttl']) {
  1410. return strnatcmp($a['ttl'], $b['ttl']);
  1411. } else {
  1412. return strnatcmp($a['name'], $b['name']);
  1413. }
  1414. }
  1415. /** Get list of owners for Domain ID
  1416. *
  1417. * @param int $id Domain ID
  1418. *
  1419. * @return mixed[] array of owners [id,fullename]
  1420. */
  1421. function get_users_from_domain_id($id) {
  1422. global $db;
  1423. $sqlq = "SELECT owner FROM zones WHERE domain_id =" . $db->quote($id, 'integer');
  1424. $id_owners = $db->query($sqlq);
  1425. if ($id_owners) {
  1426. while ($r = $id_owners->fetchRow()) {
  1427. $fullname = $db->queryOne("SELECT fullname FROM users WHERE id=" . $r['owner']);
  1428. $owners[] = array(
  1429. "id" => $r['owner'],
  1430. "fullname" => $fullname
  1431. );
  1432. }
  1433. } else {
  1434. return -1;
  1435. }
  1436. return $owners;
  1437. }
  1438. /** Search for Zone or Record
  1439. *
  1440. * @param string $search_string String to search
  1441. * @param string $perm User permitted to view 'all' or 'own' zones
  1442. * @param string $zone_sortby Column to sort domain results [default='name']
  1443. * @param string $record_sortby Column to sort record results by [default='name']
  1444. * @param boolean $wildcards Add wildcards automatically
  1445. * @param boolean $arpa Search reverse records automatically
  1446. *
  1447. * @return mixed[] 'zones' => array of zones, 'records' => array of records
  1448. */
  1449. function search_zone_and_record($search_string, $perm, $zone_sortby = 'name', $record_sortby = 'name', $wildcards = true, $arpa = true) {
  1450. global $db;
  1451. $search_string = trim($search_string);
  1452. $sql_add_from = '';
  1453. $sql_add_where = '';
  1454. $arpa_search = '';
  1455. $return_zones = array();
  1456. $return_records = array();
  1457. if (verify_permission('zone_content_view_others')) {
  1458. $perm_view = "all";
  1459. } elseif (verify_permission('zone_content_view_own')) {
  1460. $perm_view = "own";
  1461. } else {
  1462. $perm_view = "none";
  1463. }
  1464. if (verify_permission('zone_content_edit_others')) {
  1465. $perm_content_edit = "all";
  1466. } elseif (verify_permission('zone_content_edit_own')) {
  1467. $perm_content_edit = "own";
  1468. } else {
  1469. $perm_content_edit = "none";
  1470. }
  1471. if ($perm == "all") {
  1472. $sql_add_from = ", zones, users ";
  1473. $sql_add_where = " AND zones.domain_id = domains.id AND users.id = " . $db->quote($_SESSION['userid'], 'integer');
  1474. }
  1475. if ($perm == "own") {
  1476. $sql_add_from = ", zones, users ";
  1477. $sql_add_where = " AND zones.domain_id = domains.id AND users.id = " . $db->quote($_SESSION['userid'], 'integer') . " AND zones.owner = " . $db->quote($_SESSION['userid'], 'integer');
  1478. }
  1479. if ($arpa) {
  1480. if (preg_match("/^[0-9\.]+$/", $search_string)) {
  1481. $quads = explode('.', $search_string);
  1482. $arpa_search = join('.', array_reverse($quads));
  1483. }
  1484. if (preg_match("/^[0-9a-f]{0,4}:([0-9a-f]{0,4}:){0,6}[0-9a-f]{0,4}$/i", $search_string)) {
  1485. //TODO ipv6 search
  1486. }
  1487. }
  1488. $query = "SELECT
  1489. domains.id AS zid,
  1490. domains.name AS name,
  1491. domains.type AS type,
  1492. domains.master AS master,
  1493. zones.owner AS owner
  1494. FROM domains" . $sql_add_from . "
  1495. WHERE " . ($arpa_search ? "(" : "") .
  1496. " domains.name LIKE " . $db->quote(($wildcards ? "%" : "") . $search_string . ($wildcards ? "%" : ""), 'text')
  1497. . ($arpa_search ? " OR domains.name LIKE " . $db->quote("%" . $arpa_search . "%in-addr.arpa", 'text') . ")" : "")
  1498. . $sql_add_where . "
  1499. ORDER BY " . $zone_sortby;
  1500. $response = $db->query($query);
  1501. if (PEAR::isError($response)) {
  1502. error($response->getMessage());
  1503. return false;
  1504. }
  1505. $cached_owners = array();
  1506. while ($r = $response->fetchRow()) {
  1507. $owner = '';
  1508. if (isset($cached_owners[$r['owner']])) {
  1509. $owner = $cached_owners[$r['owner']];
  1510. } else {
  1511. $owner = get_owner_from_id($r['owner']);
  1512. $cached_owners[$r['owner']] = $owner;
  1513. }
  1514. $return_zones[] = array(
  1515. "zid" => $r['zid'],
  1516. "name" => $r['name'],
  1517. "type" => $r['type'],
  1518. "master" => $r['master'],
  1519. "owner" => $owner);
  1520. }
  1521. $sql_add_from = '';
  1522. $sql_add_where = '';
  1523. // Search for matching records
  1524. if ($perm == "own") {
  1525. $sql_add_from = ", zones ";
  1526. $sql_add_where = " AND zones.domain_id = records.domain_id AND zones.owner = " . $db->quote($_SESSION['userid'], 'integer');
  1527. }
  1528. $query = "SELECT
  1529. records.id AS rid,
  1530. records.name AS name,
  1531. records.type AS type,
  1532. records.content AS content,
  1533. records.ttl AS ttl,
  1534. records.prio AS prio,
  1535. records.domain_id AS zid
  1536. FROM records" . $sql_add_from . "
  1537. WHERE (records.name LIKE " . $db->quote(($wildcards ? "%" : "") . $search_string . ($wildcards ? "%" : ""), 'text') . " OR records.content LIKE " . $db->quote(($wildcards ? "%" : "") . $search_string . ($wildcards ? "%" : ""), 'text')
  1538. . ($arpa_search ? " OR records.name LIKE " . $db->quote("%" . $arpa_search . "%in-addr.arpa", 'text') : "")
  1539. . ")"
  1540. . $sql_add_where . "
  1541. ORDER BY " . $record_sortby;
  1542. $response = $db->query($query);
  1543. if (PEAR::isError($response)) {
  1544. error($response->getMessage());
  1545. return false;
  1546. }
  1547. while ($r = $response->fetchRow()) {
  1548. $return_records[] = array(
  1549. "rid" => $r['rid'],
  1550. "name" => $r['name'],
  1551. "type" => $r['type'],
  1552. "content" => $r['content'],
  1553. "ttl" => $r['ttl'],
  1554. "zid" => $r['zid'],
  1555. "prio" => $r['prio']);
  1556. }
  1557. return array('zones' => $return_zones, 'records' => $return_records);
  1558. }
  1559. /** Get Domain Type for Domain ID
  1560. *
  1561. * @param int $id Domain ID
  1562. *
  1563. * @return string Domain Type [NATIVE,MASTER,SLAVE]
  1564. */
  1565. function get_domain_type($id) {
  1566. global $db;
  1567. if (is_numeric($id)) {
  1568. $type = $db->queryOne("SELECT type FROM domains WHERE id = " . $db->quote($id, 'integer'));
  1569. if ($type == "") {
  1570. $type = "NATIVE";
  1571. }
  1572. return $type;
  1573. } else {
  1574. error(sprintf(ERR_INV_ARG, "get_record_from_id", "no or no valid zoneid given"));
  1575. }
  1576. }
  1577. /** Get Slave Domain's Master
  1578. *
  1579. * @param int $id Domain ID
  1580. *
  1581. * @return string Master server
  1582. */
  1583. function get_domain_slave_master($id) {
  1584. global $db;
  1585. if (is_numeric($id)) {
  1586. $slave_master = $db->queryOne("SELECT master FROM domains WHERE type = 'SLAVE' and id = " . $db->quote($id, 'integer'));
  1587. return $slave_master;
  1588. } else {
  1589. error(sprintf(ERR_INV_ARG, "get_domain_slave_master", "no or no valid zoneid given"));
  1590. }
  1591. }
  1592. /** Change Zone Type
  1593. *
  1594. * @param string $type New Zone Type [NATIVE,MASTER,SLAVE]
  1595. * @param int $id Zone ID
  1596. *
  1597. * @return null
  1598. */
  1599. function change_zone_type($type, $id) {
  1600. global $db;
  1601. $add = '';
  1602. if (is_numeric($id)) {
  1603. // It is not really neccesary to clear the field that contains the IP address
  1604. // of the master if the type changes from slave to something else. PowerDNS will
  1605. // ignore the field if the type isn't something else then slave. But then again,
  1606. // it's much clearer this way.
  1607. if ($type != "SLAVE") {
  1608. $add = ", master=" . $db->quote('', 'text');
  1609. }
  1610. $result = $db->query("UPDATE domains SET type = " . $db->quote($type, 'text') . $add . " WHERE id = " . $db->quote($id, 'integer'));
  1611. } else {
  1612. error(sprintf(ERR_INV_ARG, "change_domain_type", "no or no valid zoneid given"));
  1613. }
  1614. }
  1615. /** Change Slave Zone's Master IP Address
  1616. *
  1617. * @param int $zone_id Zone ID
  1618. * @param string $ip_slave_master Master IP Address
  1619. *
  1620. * @return null
  1621. */
  1622. function change_zone_slave_master($zone_id, $ip_slave_master) {
  1623. global $db;
  1624. if (is_numeric($zone_id)) {
  1625. if (are_multipe_valid_ips($ip_slave_master)) {
  1626. $result = $db->query("UPDATE domains SET master = " . $db->quote($ip_slave_master, 'text') . " WHERE id = " . $db->quote($zone_id, 'integer'));
  1627. } else {
  1628. error(sprintf(ERR_INV_ARGC, "change_domain_ip_slave_master", "This is not a valid IPv4 or IPv6 address: $ip_slave_master"));
  1629. }
  1630. } else {
  1631. error(sprintf(ERR_INV_ARG, "change_domain_type", "no or no valid zoneid given"));
  1632. }
  1633. }
  1634. /** Get Serial for Zone ID
  1635. *
  1636. * @param int $zid Zone ID
  1637. *
  1638. * @return boolean|string Serial Number or false if not found
  1639. */
  1640. function get_serial_by_zid($zid) {
  1641. global $db;
  1642. if (is_numeric($zid)) {
  1643. $query = "SELECT content FROM records where TYPE = " . $db->quote('SOA', 'text') . " and domain_id = " . $db->quote($zid, 'integer');
  1644. $rr_soa = $db->queryOne($query);
  1645. if (PEAR::isError($rr_soa)) {
  1646. error($rr_soa->getMessage());
  1647. return false;
  1648. }
  1649. $rr_soa_fields = explode(" ", $rr_soa);
  1650. } else {
  1651. error(sprintf(ERR_INV_ARGC, "get_serial_by_zid", "id must be a number"));
  1652. return false;
  1653. }
  1654. return $rr_soa_fields[2];
  1655. }
  1656. /** Validate Account is valid string
  1657. *
  1658. * @param string $account Account name alphanumeric and ._-
  1659. *
  1660. * @return boolean true is valid, false otherwise
  1661. */
  1662. function validate_account($account) {
  1663. if (preg_match("/^[A-Z0-9._-]+$/i", $account)) {
  1664. return true;
  1665. } else {
  1666. return false;
  1667. }
  1668. }
  1669. /** Get Zone Template ID for Zone ID
  1670. *
  1671. * @param int $zone_id Zone ID
  1672. *
  1673. * @return int Zone Template ID
  1674. */
  1675. function get_zone_template($zone_id) {
  1676. global $db;
  1677. $query = "SELECT zone_templ_id FROM zones WHERE domain_id = " . $db->quote($zone_id, 'integer');
  1678. $zone_templ_id = $db->queryOne($query);
  1679. return $zone_templ_id;
  1680. }
  1681. /** Update Zone Templatea ID for Zone ID
  1682. *
  1683. * @param int $zone_id Zone ID
  1684. * @param int $new_zone_template_id New Zone Template ID
  1685. *
  1686. * @return boolean true on success, false otherwise
  1687. */
  1688. function update_zone_template($zone_id, $new_zone_template_id) {
  1689. global $db;
  1690. $query = "UPDATE zones
  1691. SET zone_templ_id = " . $db->quote($new_zone_template_id, 'integer') . "
  1692. WHERE id = " . $db->quote($zone_id, 'integer');
  1693. $response = $db->query($query);
  1694. if (PEAR::isError($response)) {
  1695. error($response->getMessage());
  1696. return false;
  1697. }
  1698. return true;
  1699. }
  1700. /** Update All Zone Records for Zone ID with Zone Template
  1701. *
  1702. * @param int $zone_id Zone ID to update
  1703. * @param int $zone_template_id Zone Template ID to use for update
  1704. *
  1705. * @return null
  1706. */
  1707. function update_zone_records($zone_id, $zone_template_id) {
  1708. global $db;
  1709. global $dns_ttl;
  1710. if (verify_permission('zone_content_edit_others')) {
  1711. $perm_edit = "all";
  1712. } elseif (verify_permission('zone_content_edit_own')) {
  1713. $perm_edit = "own";
  1714. } else {
  1715. $perm_edit = "none";
  1716. }
  1717. $user_is_zone_owner = verify_user_is_owner_zoneid($zone_id);
  1718. if (verify_permission('zone_master_add')) {
  1719. $zone_master_add = "1";
  1720. }
  1721. if (verify_permission('zone_slave_add')) {
  1722. $zone_slave_add = "1";
  1723. }
  1724. $soa_rec = get_soa_record($zone_id);
  1725. $response = $db->beginTransaction();
  1726. if (0 != $zone_template_id) {
  1727. if ($perm_edit == "all" || ( $perm_edit == "own" && $user_is_zone_owner == "1")) {
  1728. if (is_numeric($zone_id)) {
  1729. $db->exec("DELETE FROM records WHERE id IN (SELECT record_id FROM records_zone_templ WHERE "
  1730. . "domain_id = " . $db->quote($zone_id, 'integer') . " AND "
  1731. . "zone_templ_id = " . $db->quote($zone_template_id, 'integer') . ")");
  1732. $db->exec("DELETE FROM records_zone_templ WHERE domain_id = " . $db->quote($zone_id, 'integer'));
  1733. } else {
  1734. error(sprintf(ERR_INV_ARGC, "delete_domain", "id must be a number"));
  1735. }
  1736. } else {
  1737. error(ERR_PERM_DEL_ZONE);
  1738. }
  1739. if ($zone_master_add == "1" || $zone_slave_add == "1") {
  1740. $domain = get_zone_name_from_id($zone_id);
  1741. $now = time();
  1742. $templ_records = get_zone_templ_records($zone_template_id);
  1743. if ($templ_records == -1) {
  1744. return;
  1745. }
  1746. foreach ($templ_records as $r) {
  1747. //fixme: appears to be a bug and regex match should occur against $domain
  1748. if ((preg_match('/in-addr.arpa/i', $zone_id) && ($r["type"] == "NS" || $r["type"] == "SOA")) || (!preg_match('/in-addr.arpa/i', $zone_id))) {
  1749. $name = parse_template_value($r["name"], $domain);
  1750. $type = $r["type"];
  1751. if ($type == "SOA") {
  1752. $content = get_updated_soa_record($soa_rec);
  1753. echo $content;
  1754. } else {
  1755. $content = parse_template_value($r["content"], $domain);
  1756. }
  1757. $ttl = $r["ttl"];
  1758. $prio = intval($r["prio"]);
  1759. if (!$ttl) {
  1760. $ttl = $dns_ttl;
  1761. }
  1762. $query = "INSERT INTO records (domain_id, name, type, content, ttl, prio, change_date) VALUES ("
  1763. . $db->quote($zone_id, 'integer') . ","
  1764. . $db->quote($name, 'text') . ","
  1765. . $db->quote($type, 'text') . ","
  1766. . $db->quote($content, 'text') . ","
  1767. . $db->quote($ttl, 'integer') . ","
  1768. . $db->quote($prio, 'integer') . ","
  1769. . $db->quote($now, 'integer') . ")";
  1770. $response = $db->exec($query);
  1771. if ($db_layer == 'MDB2' && ($db_type == 'mysql' || $db_type == 'pgsql')) {
  1772. $record_id = $db->lastInsertId('records', 'id');
  1773. } else if ($db_layer == 'PDO' && $db_type == 'pgsql') {
  1774. $record_id = $db->lastInsertId('records_id_seq');
  1775. } else {
  1776. $record_id = $db->lastInsertId();
  1777. }
  1778. $query = "INSERT INTO records_zone_templ (domain_id, record_id, zone_templ_id) VALUES ("
  1779. . $db->quote($zone_id, 'integer') . ","
  1780. . $db->quote($record_id, 'integer') . ","
  1781. . $db->quote($zone_template_id, 'integer') . ")";
  1782. $response = $db->query($query);
  1783. }
  1784. }
  1785. }
  1786. }
  1787. $query = "UPDATE zones
  1788. SET zone_templ_id = " . $db->quote($zone_template_id, 'integer') . "
  1789. WHERE domain_id = " . $db->quote($zone_id, 'integer');
  1790. $response = $db->exec($query);
  1791. if (PEAR::isError($response)) {
  1792. $response = $db->rollback();
  1793. } else {
  1794. $response = $db->commit();
  1795. }
  1796. }
  1797. /** Delete array of domains
  1798. *
  1799. * Deletes a domain by a given id.
  1800. * Function always succeeds. If the field is not found in the database, thats what we want anyway.
  1801. *
  1802. * @param int[] $domains Array of Domain IDs to delete
  1803. *
  1804. * @return boolean true on success, false otherwise
  1805. */
  1806. function delete_domains($domains) {
  1807. global $db;
  1808. $error = false;
  1809. $return = false;
  1810. $response = $db->beginTransaction();
  1811. foreach ($domains as $id) {
  1812. if (verify_permission('zone_content_edit_others')) {
  1813. $perm_edit = "all";
  1814. } elseif (verify_permission('zone_content_edit_own')) {
  1815. $perm_edit = "own";
  1816. } else {
  1817. $perm_edit = "none";
  1818. }
  1819. $user_is_zone_owner = verify_user_is_owner_zoneid($id);
  1820. if ($perm_edit == "all" || ( $perm_edit == "own" && $user_is_zone_owner == "1")) {
  1821. if (is_numeric($id)) {
  1822. $zone_type = get_domain_type($id);
  1823. if ($zone_type == 'MASTER') {
  1824. $zone_name = get_zone_name_from_id($id);
  1825. dnssec_unsecure_zone($zone_name);
  1826. }
  1827. $db->exec("DELETE FROM zones WHERE domain_id=" . $db->quote($id, 'integer'));
  1828. $db->exec("DELETE FROM domains WHERE id=" . $db->quote($id, 'integer'));
  1829. $db->exec("DELETE FROM records WHERE domain_id=" . $db->quote($id, 'integer'));
  1830. $db->query("DELETE FROM records_zone_templ WHERE domain_id=" . $db->quote($id, 'integer'));
  1831. } else {
  1832. error(sprintf(ERR_INV_ARGC, "delete_domains", "id must be a number"));
  1833. $error = true;
  1834. }
  1835. } else {
  1836. error(ERR_PERM_DEL_ZONE);
  1837. $error = true;
  1838. }
  1839. }
  1840. if (PEAR::isError($response)) {
  1841. $response = $db->rollback();
  1842. $commit = false;
  1843. } else {
  1844. $response = $db->commit();
  1845. $commit = true;
  1846. }
  1847. if (true == $commit && false == $error) {
  1848. $return = true;
  1849. }
  1850. return $return;
  1851. }
  1852. /** Check if record exists
  1853. *
  1854. * @param string $name Record name
  1855. *
  1856. * @return boolean true on success, false on failure
  1857. */
  1858. function record_name_exists($name) {
  1859. global $db;
  1860. $query = "SELECT COUNT(id) FROM records WHERE name = " . $db->quote($name, 'text');
  1861. $count = $db->queryOne($query);
  1862. return ($count == "1" ? true : false);
  1863. }
  1864. /** Return domain level for given name
  1865. *
  1866. * @param string $name Zone name
  1867. *
  1868. * @return int domain level
  1869. */
  1870. function get_domain_level($name) {
  1871. return substr_count($name, '.') + 1;
  1872. }
  1873. /** Return domain second level domain for given name
  1874. *
  1875. * @param string $name Zone name
  1876. *
  1877. * @return string 2nd level domain name
  1878. */
  1879. function get_second_level_domain($name) {
  1880. $domain_parts = explode('.', $name);
  1881. $domain_parts = array_reverse($domain_parts);
  1882. return $domain_parts[1] . '.' . $domain_parts[0];
  1883. }
  1884. /** Get zone list which use templates
  1885. *
  1886. * @param resource $db DB link
  1887. *
  1888. * @return mixed[] Array with domain and template ids
  1889. */
  1890. function get_zones_with_templates($db) {
  1891. $query = "SELECT id, domain_id, zone_templ_id FROM zones WHERE zone_templ_id <> 0";
  1892. $result = $db->query($query);
  1893. $zones = array();
  1894. while ($zone = $result->fetchRow()) {
  1895. $zones[]=$zone;
  1896. }
  1897. return $zones;
  1898. }
  1899. /** Get records by domain id
  1900. *
  1901. *
  1902. */
  1903. function get_records_by_domain_id($db, $domain_id) {
  1904. $query = "SELECT id, name, type, content FROM records WHERE domain_id = " . $db->quote($domain_id, 'integer');
  1905. $result = $db->query($query);
  1906. $records = array();
  1907. while ($zone_records = $result->fetchRow()) {
  1908. $records[]=$zone_records;
  1909. }
  1910. return $records;
  1911. }