Overview

Packages

  • Beatnik
  • None

Classes

  • Autogenerate
  • Beatnik
  • Beatnik_Driver
  • Beatnik_Driver_ldap2dns
  • Beatnik_Driver_pdnsgsql
  • Beatnik_Driver_sql
  • DeleteRecord
  • EditRecord
  • Overview
  • Package
  • Class
  • Tree
  1: <?php
  2: /**
  3:  * Beatnik base class
  4:  *
  5:  * Copyright 2005-2007 Alkaloid Networks <http://www.alkaloid.net>
  6:  *
  7:  * See the enclosed file COPYING for license information (GPL). If you
  8:  * did not receive this file, see http://www.horde.org/licenses/gpl.
  9:  *
 10:  * @author Ben Klang <ben@alkaloid.net>
 11:  * @package Beatnik
 12:  */
 13: class Beatnik {
 14: 
 15:     /**
 16:      * Build Beatnik's list of menu items.
 17:      */
 18:     function getMenu($returnType = 'object')
 19:     {
 20:         // We are editing rather than adding if an ID was passed
 21:         $editing = Horde_Util::getFormData('id');
 22:         $editing = !empty($editing);
 23: 
 24:         $menu = new Horde_Menu();
 25: 
 26:         $menu->add(Horde::url('listzones.php'), _('List Domains'), 'website.png');
 27:         if (!empty($_SESSION['beatnik']['curdomain'])) {
 28:             $menu->add(Horde_Util::addParameter(Horde::url('editrec.php'), 'curdomain', $_SESSION['beatnik']['curdomain']['zonename']), ($editing) ? _("Edit Record") : _("Add Record"), 'edit.png');
 29:         } else {
 30:             $menu->add(Horde::url('editrec.php?rectype=soa'), _("Add Zone"), 'edit.png');
 31:         }
 32: 
 33:         $url = Horde_Util::addParameter(Horde::selfUrl(true), array('expertmode' => 'toggle'));
 34:         $menu->add($url, _('Expert Mode'), 'hide_panel.png', null, '', null, ($_SESSION['beatnik']['expertmode']) ? 'current' : '');
 35: 
 36:         if (count(Beatnik::needCommit())) {
 37:             $url = Horde_Util::addParameter(Horde::url('commit.php'), array('domain' => 'all'));
 38:             $menu->add($url, _('Commit All'), 'commit-all.png');
 39:         }
 40: 
 41:         if ($returnType == 'object') {
 42:            return $menu;
 43:         } else {
 44:             return $menu->render();
 45:         }
 46:     }
 47: 
 48:     /**
 49:      * Get possible records
 50:      *
 51:      * The keys of this array are the IDs of the record type.  The values
 52:      * are a human friendly description of the record type.
 53:      */
 54:     function getRecTypes()
 55:     {
 56:         $beatnik = $GLOBALS['registry']->getApiInstance('beatnik', 'application');
 57: 
 58:         $records = array(
 59:             'soa' => _("SOA (Start of Authority)"),
 60:             'ns' => _("NS (Name Server)"),
 61:             'a' => _("A (Address)"),
 62:             'aaaa' => _("AAAA (IPv6 Address)"),
 63:             'ptr' => _("PTR (Reverse DNS)"),
 64:             'cname' => _("CNAME (Alias)"),
 65:             'mx' => _("MX (Mail eXchange)"),
 66:             'srv' => _("SRV (Service Record)"),
 67:             'txt' => _("TXT (Text Record)"),
 68:         );
 69: 
 70:         return array_merge($records, $beatnik->driver->getRecDriverTypes());
 71:     }
 72: 
 73:     /**
 74:      */
 75:     function getRecFields($recordtype)
 76:     {
 77:         // Record Format:
 78:         // $recset is an array of fields.  The field IDs are the keys.
 79:         // Each field is an array with the following keys:
 80:         // 'name': The short name of the field.  This key is also used
 81:         //      to reference the help system.
 82:         // 'description': Long description of the field
 83:         // 'type': Field type.  Choose from any available from Horde_Form
 84:         // 'maxlength': Maximum field length.  0 is unlimited
 85:         // 'required': If true, the field will be required by the form
 86:         // 'infoset': one of 'basic' or 'advanced'.  This is used to help keep
 87:         //      the forms simple for non-power-users.  If 'required' is true and
 88:         //      'infoset' is false then 'default' MUST be specified
 89:         // 'default': the default value of the field.
 90:         // 'index': Crude sort ordering.  Lower means show higher in the group
 91: 
 92:         $beatnik = $GLOBALS['registry']->getApiInstance('beatnik', 'application');
 93: 
 94:         // Attempt to return cached results.
 95:         static $recset = array();
 96: 
 97:         if (isset($recset[$recordtype])) {
 98:             return $recset[$recordtype];
 99:         }
100:         $recset[$recordtype] = array();
101: 
102:         $recset[$recordtype]['id'] = array(
103:             'name' => _("UID"),
104:             'description' => _("Unique Identifier (Used as Record ID)"),
105:             'type' => 'hidden',
106:             'maxlength' => 0,
107:             'required' => false, // Empty for "new" entries
108:             'infoset' => 'basic',
109:             'index' => 0,
110:         );
111: 
112:         switch (strtolower($recordtype)) {
113:         case 'soa':
114:             $recset[$recordtype]['zonename'] = array(
115:                 'name' => _("Domain Name"),
116:                 'description' => _("Zone Domain Name"),
117:                 'type' => 'text',
118:                 'maxlength' => 0,
119:                 'required' => true,
120:                 'infoset' => 'basic',
121:                 'index' => 1,
122:             );
123:             $recset[$recordtype]['zonens'] = array(
124:                 'name' => _("Primary Nameserver"),
125:                 'description' => _("Primary nameserver for this zone"),
126:                 'type' => 'text',
127:                 'maxlength' => 0,
128:                 'required' => true,
129:                 'infoset' => 'basic',
130:                 'index' => 2,
131:             );
132:             $recset[$recordtype]['zonecontact'] = array(
133:                 'name' => _("Zone Contact"),
134:                 'description' => _("Contact e-mail address for this zone"),
135:                 'type' => 'text',
136:                 'maxlength' => 0,
137:                 'required' => true,
138:                 'infoset' => 'basic',
139:                 'index' => 2,
140:             );
141:             $recset[$recordtype]['serial'] = array(
142:                 'name' => _("Serial"),
143:                 'description' => _("Zone Serial Number"),
144:                 'type' => 'int',
145:                 'default' => date('Ymd'). '00',
146:                 'maxlength' => 0,
147:                 'required' => false,
148:                 'infoset' => 'advanced',
149:                 'index' => 3,
150:             );
151:             $recset[$recordtype]['refresh'] = array(
152:                 'name' => 'Refresh',
153:                 'description' => _("Zone Refresh"),
154:                 'type' => 'int',
155:                 'maxlength' => 0,
156:                 'required' => false,
157:                 'infoset' => 'advanced',
158:                 'index' => 4,
159:             );
160:             $recset[$recordtype]['retry'] = array(
161:                 'name' => _("Retry"),
162:                 'description' => _("Zone Retry"),
163:                 'type' => 'int',
164:                 'maxlength' => 0,
165:                 'required' => false,
166:                 'infoset' => 'advanced',
167:                 'index' => 5,
168:             );
169:             $recset[$recordtype]['expire'] = array(
170:                 'name' => _("Expiration"),
171:                 'description' => _("Zone Expiry"),
172:                 'type' => 'int',
173:                 'maxlength' => 0,
174:                 'required' => false,
175:                 'infoset' => 'advanced',
176:                 'index' => 6,
177:             );
178:             $recset[$recordtype]['minimum'] = array(
179:                 'name' => _("Minimum"),
180:                 'description' => _("Zone Minimum"),
181:                 'type' => 'int',
182:                 'maxlength' => 0,
183:                 'required' => false,
184:                 'infoset' => 'advanced',
185:                 'index' => 7,
186:             );
187:             break;
188: 
189:         case 'a':
190:             $recset[$recordtype]['hostname'] = array(
191:                 'name' => _("Hostname"),
192:                 'description' => _("Short hostname for this record"),
193:                 'type' => 'text',
194:                 'maxlength' => 0,
195:                 'required' => true,
196:                 'infoset' => 'basic',
197:                 'index' => 1,
198:             );
199:             $recset[$recordtype]['ipaddr'] = array(
200:                 'name' => _("IP Address"),
201:                 'description' => _("IPv4 Network Address"),
202:                 'type' => 'ipaddress',
203:                 'maxlength' => 0,
204:                 'required' => true,
205:                 'infoset' => 'basic',
206:                 'index' => 2,
207:             );
208:             break;
209: 
210:         case 'aaaa':
211:             $recset[$recordtype]['hostname'] = array(
212:                 'name' => _("Hostname"),
213:                 'description' => _("Short hostname for this record"),
214:                 'type' => 'text',
215:                 'maxlength' => 0,
216:                 'required' => true,
217:                 'infoset' => 'basic',
218:                 'index' => 1,
219:             );
220:             $recset[$recordtype]['ip6addr'] = array(
221:                 'name' => _("IPv6 Address"),
222:                 'description' => _("IPv6 Network Address"),
223:                 'type' => 'ip6address',
224:                 'maxlength' => 0,
225:                 'required' => true,
226:                 'infoset' => 'basic',
227:                 'index' => 2,
228:             );
229:             break;
230: 
231:         case 'ptr':
232:             $recset[$recordtype]['hostname'] = array(
233:                 'name' => _("Hostname"),
234:                 'description' => _("IP in Reverse notation (.in-addr.arpa)"),
235:                 'type' => 'text',
236:                 'maxlength' => 0,
237:                 'required' => true,
238:                 'infoset' => 'basic',
239:                 'index' => 1,
240:             );
241:             $recset[$recordtype]['pointer'] = array(
242:                 'name' => _("Hostname Target"),
243:                 'description' => _("Hostname for Reverse DNS"),
244:                 'type' => 'text',
245:                 'maxlength' => 0,
246:                 'required' => true,
247:                 'infoset' => 'basic',
248:                 'index' => 2,
249:             );
250:             break;
251: 
252:         case 'mx':
253:             $recset[$recordtype]['pointer'] = array(
254:                 'name' => _("Hostname Target"),
255:                 'description' => _("Hostname of Mail eXchanger"),
256:                 'type' => 'text',
257:                 'maxlength' => 0,
258:                 'required' => true,
259:                 'infoset' => 'basic',
260:                 'index' => 1,
261:             );
262:             $recset[$recordtype]['pref'] = array(
263:                 'name' => _("Preference"),
264:                 'description' => _("MX Preference (lower is more preferred)"),
265:                 'type' => 'int',
266:                 'default' => 0,
267:                 'maxlength' => 0,
268:                 'required' => true,
269:                 'infoset' => 'basic',
270:                 'index' => 2,
271:             );
272:             break;
273: 
274:         case 'cname':
275:             $recset[$recordtype]['hostname'] = array(
276:                 'name' => _("Hostname"),
277:                 'description' => _("Short hostname for this record"),
278:                 'type' => 'text',
279:                 'maxlength' => 0,
280:                 'required' => true,
281:                 'infoset' => 'basic',
282:                 'index' => 1,
283:             );
284:             $recset[$recordtype]['pointer'] = array(
285:                 'name' => _("Hostname Target"),
286:                 'description' => _("Hostname for CNAME alias"),
287:                 'type' => 'text',
288:                 'maxlength' => 0,
289:                 'required' => true,
290:                 'infoset' => 'basic',
291:                 'index' => 2,
292:             );
293:             break;
294: 
295:         case 'ns':
296:             $recset[$recordtype]['hostname'] = array(
297:                 'name' => _("Domain Name"),
298:                 'description' => _("Short sub-domain for NS record (leave blank unless creating a subdomain)"),
299:                 'type' => 'text',
300:                 'maxlength' => 0,
301:                 'required' => false,
302:                 // If we have a current domain name, use it for the default val
303:                 'default' => @$GLOBALS['curdomain']['zonename'],
304:                 'infoset' => 'basic',
305:                 'index' => 1,
306:             );
307:             $recset[$recordtype]['pointer'] = array(
308:                 'name' => _("Hostname Target"),
309:                 'description' => _("Hostname of Authoritative Name Server"),
310:                 'type' => 'text',
311:                 'maxlength' => 0,
312:                 'required' => true,
313:                 'infoset' => 'basic',
314:                 'index' => 2,
315:             );
316:             break;
317: 
318:         case 'srv':
319:             $recset[$recordtype]['hostname'] = array(
320:                 'name' => _("Hostname"),
321:                 'description' => _("Short hostname for this record"),
322:                 'type' => 'text',
323:                 'maxlength' => 0,
324:                 'required' => true,
325:                 'infoset' => 'basic',
326:                 'index' => 1,
327:             );
328:             $recset[$recordtype]['pointer'] = array(
329:                 'name' => _("Hostname Target"),
330:                 'description' => _("Hostname for DNS Service Record"),
331:                 'type' => 'text',
332:                 'maxlength' => 0,
333:                 'required' => true,
334:                 'infoset' => 'basic',
335:                 'index' => 2,
336:             );
337:             $recset[$recordtype]['priority'] = array(
338:                 'name' => _("SRV Priority"),
339:                 'description' => _("DNS Service Record Priority"),
340:                 'type' => 'int',
341:                 'default' => 0,
342:                 'maxlength' => 0,
343:                 'required' => true,
344:                 'infoset' => 'basic',
345:                 'index' => 3,
346:             );
347:             $recset[$recordtype]['weight'] = array(
348:                 'name' => _("SRV Weight"),
349:                 'description' => _("DNS Service Record Weight"),
350:                 'type' => 'int',
351:                 'default' => 0,
352:                 'maxlength' => 0,
353:                 'required' => true,
354:                 'infoset' => 'basic',
355:                 'index' => 4,
356:             );
357:             $recset[$recordtype]['port'] = array(
358:                 'name' => _("SRV Port"),
359:                 'description' => _("DNS Service Record Port Number"),
360:                 'type' => 'int',
361:                 'default' => 0,
362:                 'maxlength' => 0,
363:                 'required' => true,
364:                 'infoset' => 'basic',
365:                 'index' => 5,
366:             );
367:             break;
368: 
369:         case 'txt':
370:             $recset[$recordtype]['hostname'] = array(
371:                 'name' => _("Hostname"),
372:                 'description' => _("Short hostname for this record"),
373:                 'type' => 'text',
374:                 'maxlength' => 0,
375:                 'required' => true,
376:                 'infoset' => 'basic',
377:                 'index' => 1,
378:             );
379:             $recset[$recordtype]['text'] = array(
380:                 'name' => 'Text',
381:                 'description' => _("String payload for DNS TXT"),
382:                 'type' => 'text',
383:                 'maxlength' => 256,
384:                 'required' => true,
385:                 'infoset' => 'basic',
386:                 'index' => 2,
387:             );
388:             break;
389:         }
390: 
391:         $recset[$recordtype]['ttl'] = array(
392:             'name' => _("TTL"),
393:             'description' => _("Record Time-To-Live (seconds)"),
394:             'type' => 'int',
395:             'maxlength' => 0,
396:             'required' => false,
397:             'infoset' => 'advanced',
398:             'index' => 100,
399:             'default' => $GLOBALS['prefs']->getValue('default_ttl')
400:         );
401: 
402:         //$recset[$recordtype] = array_merge($recset[$recordtype], $beatnik->driver->getRecDriverFields($recordtype));
403:         uasort($recset[$recordtype], array('Beatnik', 'fieldSort'));
404: 
405:         return $recset[$recordtype];
406:     }
407: 
408:     /**
409:      * Check or set a flag to show that a domain has outstanding changes that
410:      * need to be committed.
411:      *
412:      * @param optional string  $domain      Domain to check whether a commit is
413:      *                                      necessary
414:      * @param optional boolean $needcommit  true adds the domain to the list
415:      *                                      that needs committing; false removes
416:      *                                      the domain from the list
417:      *
418:      * @return mixed  Array of domains needing committing if no arguments are
419:      *                passed.
420:      *                Boolean if only a $domain is passed: True if $domain has
421:      *                outstanding changes, false if not.
422:      *                Mixed if both $domain and $needcommit are passed.  True
423:      *                on success, PEAR::Error on error.
424:      */
425:     function needCommit($domain = null, $needcommit = null)
426:     {
427:         // Make sure we have a valid array with which to work
428:         if (!isset($_SESSION['beatnik']['needcommit'])) {
429:             $_SESSION['beatnik']['needcommit'] = array();
430:         }
431: 
432:         if ($domain === null && $needcommit === null) {
433:             // Return the stored list of domains needing changes committed.
434:             return array_keys($_SESSION['beatnik']['needcommit']);
435:         } elseif ($domain !== null && $needcommit === null) {
436:             // Check if domain need committing
437:             return isset($_SESSION['beatnik']['needcommit'][$domain]);
438:         } elseif ($domain !== null && is_bool($needcommit)) {
439:             // Flag domain for committing
440:             if ($needcommit) {
441:                 if(!isset($_SESSION['beatnik']['needcommit'][$domain])) {
442:                     $_SESSION['beatnik']['needcommit'][$domain] = true;
443:                 }
444:             } else {
445:                 if (isset($_SESSION['beatnik']['needcommit'][$domain])) {
446:                     unset($_SESSION['beatnik']['needcommit'][$domain]);
447:                 }
448:             }
449:             return true;
450:         } else {
451:             // Somebody sent something they should not have...
452:             throw new Beatnik_Exception(_("Unable to determine if domain needs committing: invalid parameter."));
453:         }
454:     }
455: 
456:     function notifyCommits()
457:     {
458:         // This check has to come after the page has finished all work in case
459:         // the status has changed due to a now-completed edit.
460:         if (count(Beatnik::needCommit())) {
461:             foreach (Beatnik::needCommit() as $domain) {
462:                 $GLOBALS['notification']->push(sprintf(_("You have uncommitted changes in %s."), $domain));
463:             }
464:         }
465:     }
466: 
467:     /**
468:      * Checks for the given permissions for the current user on the given
469:      * permissions node.  Optionally check for the requested permssion for a
470:      * given number of steps up the tree.
471:      *
472:      * @param string $permname  Name of the permission to check
473:      *
474:      * @param optional int $permmask  Bitfield of permissions to check for
475:      *
476:      * @param options int $numparents  Check for the same permissions this
477:      *                                 many levels up the tree
478:      *
479:      * @return boolean True if the user has permission, False if not
480:      */
481:     function hasPermission($permname, $permmask = null, $numparents = 0)
482:     {
483:         if ($GLOBALS['registry']->isAdmin()) {
484:             return true;
485:         }
486: 
487:         if ($permmask === null) {
488:             $permmask = Horde_Perms::SHOW | Horde_Perms::READ;
489:         }
490: 
491:         # Default deny all permissions
492:         $user = 0;
493:         $superadmin = 0;
494: 
495:         $superadmin = $GLOBALS['injector']->getInstance('Horde_Perms')->hasPermission('beatnik:domains', $GLOBALS['registry']->getAuth(), $permmask);
496: 
497:         while ($numparents >= 0) {
498:             $tmpuser = $GLOBALS['injector']->getInstance('Horde_Perms')->hasPermission($permname, $GLOBALS['registry']->getAuth(), $permmask);
499: 
500:             $user = $user | $tmpuser;
501:             if ($numparents > 0) {
502:                 $pos = strrpos($permname, ':');
503:                 if ($pos) {
504:                     $permname = substr($permname, 0, $pos);
505:                 }
506:             }
507:             $numparents--;
508:         }
509:         return (($superadmin | $user) & $permmask);
510:     }
511: 
512:     /**
513:      * Autogenerate a set of records from a template defined in
514:      * config/autogenerate.php
515:      *
516:      * @param object $vars  Horde_Variables object from Autogenerate form
517:      *
518:      * @return mixed  true on success, PEAR::Error on failure
519:      */
520:     function autogenerate(&$vars)
521:     {
522:         $beatnik = $GLOBALS['registry']->getApiInstance('beatnik', 'application');
523: 
524:         require BEATNIK_BASE . '/config/autogenerate.php';
525:         $template = $templates[$vars->get('template')];
526:         try {
527:             $zonedata = $beatnik->driver->getRecords($_SESSION['beatnik']['curdomain']['zonename']);
528:         } catch (Exception $e) {
529:             $GLOBALS['notification']->push($e);
530:         }
531: 
532:         foreach ($template['types'] as $rectype => $definitions) {
533:             // Only attempt to delete records if the type is already defined
534:             if (isset($zonedata[$rectype])) {
535:                 // Check for collisions and handle as requested
536:                 switch($definitions['replace']) {
537:                 case 'all':
538:                     foreach ($zonedata[$rectype] as $record) {
539:                         try {
540:                             $result = $beatnik->driver->deleteRecord($record);
541:                         } catch (Exception $e) {
542:                             $GLOBALS['notification']->push($e);
543:                         }
544:                     }
545:                     break;
546: 
547:                 case 'match':
548:                     foreach ($zonedata[$rectype] as $record) {
549:                         // Check every record in the template to see if the
550:                         // hostname matches
551:                         foreach ($definitions['records'] as $Trecord) {
552:                             if ($record['hostname'] == $Trecord['hostname']) {
553:                                 try {
554:                                     $result = $beatnik->driver->deleteRecord($record);
555:                                 } catch (Exception $e) {
556:                                     $GLOBALS['notification']->push($e);
557:                                 }
558:                             }
559:                         }
560:                     }
561:                     break;
562: 
563:                 #case 'none':
564:                 #default:
565:                 }
566:             }
567: 
568:             $defaults = array('rectype' => $rectype,
569:                               'zonename'=> $_SESSION['beatnik']['curdomain']['zonename']);
570:             foreach ($definitions['records'] as $info) {
571:                 if ($beatnik->driver->recordExists($info, $rectype)) {
572:                     $GLOBALS['notification']->push(_("Skipping existing identical record"));
573:                     continue;
574:                 }
575:                 try {
576:                     $result = $beatnik->driver->saveRecord(array_merge($defaults, $info));
577:                     $GLOBALS['notification']->push(sprintf(_('Record added: %s/%s'), $rectype, $info['hostname']), 'horde.success');
578:                 } catch (Exception $e) {
579:                     $GLOBALS['notification']->push($e->getMessage(), 'horde.error');
580:                     return false;
581:                 }
582:             }
583:         }
584:         return true;
585:     }
586: 
587:     /**
588:      * Increments a domain serial number.
589:      *
590:      * @param int $serial  Serial number to be incremented
591:      *
592:      * @return int  Incremented serial number
593:      */
594:     function incrementSerial($serial)
595:     {
596:         // Create a serial number of the ad-hoc standard YYYYMMDDNN
597:         // where YYYYMMDD is the year/month/day of the last update to this
598:         // odmain and NN is an incrementer to handle multiple updates in a
599:         // given day.
600:         $newserial = (int) (date('Ymd') . '00');
601:         if ($serial < $newserial) {
602:             return $newserial;
603:         } else {
604:             return ++$serial;
605:         }
606:     }
607: 
608:     /**
609:      * Callback for usort to make field data print in a friendly order
610:      *
611:      * @param mixed $a First sort variable
612:      * @param mixed $b Second sort variable
613:      *
614:      * @return int -1, 0, 1 based on relative sort order
615:      */
616:     function fieldSort($a, $b)
617:     {
618:         if ($a['index'] < $b['index']) {
619:             return -1;
620:         } elseif ($a['index'] > $b['index']) {
621:             return 1;
622:         } else {
623:             return 0;
624:         }
625:     }
626: 
627: }
628: 
API documentation generated by ApiGen