web frontend:

workflow:

log in, see domain list from current group

-> "current group" includes all subgroups?  (hairy SQL)


logic:
-> need to pass session ID on every call, otherwise we don't know who we are
-> should check ACLs on every call in case of changing permissions
-> should be able to store all webvar bits in the session


ooo! ooo!  what about "clone existing domain"?


export for tinydns:  arbitrary record data is binary blob, decomposed by hex octets to octal codes


components:
menu list (actions/sections)
domain list
group tree
user list
<various edit <entity> pages>



time tables:
3600: 1h
7200: 2h
10800: 3h
14400: 4h
21600: 6h
43200: 12h
86400: 1d
172800: 2d
604800: 7d (1w)

valid records:

nb:  wildcards are supported for most types.  use with extreme caution!  (I don't plan on writing
tools that will create them.)

.fqdn:ip:x:ttl:timestamp:lo
 -> x.fqdn is a nameserver at ip, SOA sets x.fqdn as master with hostmaster@fqdn as contact
 -> if x contains a . that is used as the NS name
(Not much use for us)

Zfqdn:primary:contact:serial:refresh:retry:expire:minttl:recttl:(timestamp:lo)
 -> SOA for fqdn

&fqdn:ip:x:ttl:timestamp:lo
 -> x.fqdn is a nameserver
 -> if x contains a . that is used as the NS name
 -> ip may be omitted;  A record for fqdn->ip is created otherwise

=fqdn:ip:ttl:timestamp:lo
 -> A record and matching PTR record with fqdn and ip

+fqdn:ip:ttl:timestamp:lo
 -> A record

^ptr:fqdn:ttl:timestamp:lo
 -> PTR record.  note ptr must be reverse-IP format ending in .in-addr.arpa

@fqdn:ip:x:dist:ttl:timestamp:lo
 -> MX.  Dist defaults to 0.
 -> ip may be omitted;  A record for fqdn->ip is created otherwise

-fqdn:ip:ttl:timestamp:lo
 -> ignored

'fqdn:text:ttl:timestamp:lo
 -> TXT record
 -> octal-encode special characters in text as \nnn

Cfqdn:name:ttl:timestamp:lo
 -> CNAME for fqdn pointing to name

:fqdn:n:data:ttl:timestamp:lo
 -> Generic data, of type n (n is a 16-bit unsigned integer)
    17 is RP
    16 is TXT
    2 (NS), 5 (CNAME), 6 (SOA), 12 (PTR), 15 (MX) and 252 (AXFR) (WTF?) should not be used
 -> data must use octal escapes for : or nondisplayable characters.  axfr-get seems to escape all
    non-alphanumerics, therefore so will we.

two primary groups of data:
-> forward zones
  -> local master zones
  -> local slave zones
-> reverse zones
- note that it would be really nice to eliminate duplicated A records (+domain.com:ip:: plus =domain.com:ip::)

operations:
-> import zone data (BIND*, djbdns, vegadns-mysql)
  -> allow overwrite of existing SOA
-> export data (BIND*, djbdns)
-> add zone/domain
  -> as slave
  -> as master
-> delete zone/domain
-> add record to domain
-> remove record from domain
-> change record
  -> A record IP
  -> CNAME destination
  -> MX destination
  -> MX priority
  -> A <-> CNAME ?
  -> flag record as "primary" A record for an IP (means that PTR will set that name as rDNS)
-> force propagation (execute propagation script)
-> User ACL fiddling:
  -> take IPDB model, include groups/delegation/etc
    "admin" -> nominally full access to anything/everything
    "staff" -> general access to all domains, can create users and delegate domains to them
    "bulk hoster" -> customer with more than one domain, (can create users and delegate domains to them)?
    "user" -> customer with one domain
    

recommended SOA/TTL/etc times:
refresh 86400 (24h), retry 7200 (2h), expire 2592000 (30d), ttl 345600 (4d)

Qs re: new servers:
-> IPs for cache/authoritative?


flow of data:
user input -> database -> local "zone" data -> rsync/scp to slaves

don't use "domain ID" goop;  its only advantage is slightly lower disk use.  otherwise it's more
complicated, less traceable thru the DB manually


db structure (current)

domains:
| domain_id   | int(11)                   |      | MUL | NULL     | auto_increment |
| domain      | varchar(100)              |      |     |          |                |
| group_id    | int(11)                   | YES  |     | NULL     |                |
| description | varchar(255)              |      |     |          |                |
| status      | enum('active','inactive') |      |     | inactive |                |

records:
| domain_id   | int(11)      |      | MUL | 0       |                |
| record_id   | int(11)      |      | PRI | NULL    | auto_increment |
| host        | varchar(100) |      |     |         |                |
| type        | char(1)      | YES  |     | NULL    |                |
| val         | varchar(100) | YES  |     | NULL    |                |
| distance    | int(4)       | YES  |     | 0       |                |
| weight      | int(4)       | YES  |     | NULL    |                | - for SRV only
| port        | int(4)       | YES  |     | NULL    |                | - for SRV only
| ttl         | int(11)      |      |     | 86400   |                |
| description | varchar(255) |      |     |         |                |

default_records is a duplicate of records structurally

log:  (not sure how useful this really is, in this form...)
| domain_id | int(11)      |      |     | 0       |       |
| user_id   | int(11)      |      |     | 0       |       |
| group_id  | int(11)      |      |     | 0       |       |
| email     | varchar(60)  |      |     |         |       |
| name      | varchar(60)  |      |     |         |       |
| entry     | varchar(200) |      |     |         |       |
| time      | int(11)      |      |     | 0       |       |

db structure (proposed)

domains:
domain	char(128)	pk, indexed
group	char(32)	fk, indexed?
status	enum?
masterns char(64)
email	char(128)
serial  long int (needs 2^32 at least)
refresh	long int	needs semi-sane default
retry	long int	needs semi-sane default
expire	long int	needs semi-sane default
minttl	long int	needs semi-sane default
ctime	timestamp
mtime	timestamp

records:
recid	serial
domain	char(128)	fk, indexed?
host	char(128)	pk, indexed
type	enum?
val	char(256)	to allow for 255-char TXT records
extra	char(10)	10 should be enough to express any needs for MX, SRV, or anything else...right?
ttl	long int
ctime	timestamp
mtime	timestamp

default records to be either database-coded as default values or coded in er, code.
-> hrm.
  database-level defaults are "recommended practice" according to the cricket book
  code-level defaults may be hardcoded (easy) or loaded from a config file (harder, but cleaner)
  all must be overrideable by a database-table-stored "local policy defaults" widget

add domain:
 -> need domain name
 -> IP/"company default" radio button pair, with some Javascript to change defaults for:
 -> radio buttons with sane defaults for standard hosts (www/mail/ftp/smtp)
   -> www CNAME @
   -> FTP CNAME @
   -> mail CNAME mail.company.com
   -> smtp CNAME smtp.company.com
   -> MX defaults to <mxlist>
   
+----------------------------------------------------------------+
| Domain: _________________________________                      |
| o Company hosting       o Slave zone        o Custom settings  |
+----------------------------------------------------------------+

add_domain($domain,$class)
update_domain($domain,$group,$status,[$contact,$primary,$serial,$ttl,$refresh,$retry,$expire,$minttl])
delete_domain($domain)
lock_domain($domain)  -hm..  lock/unlock may be admin-level "don't touch!" flags vs "active/inactive" flags
unlock_domain($domain)
add_record($domain,$host,$type,$value,$extra,$ttl)
update_record($id,$host,$type,$val,$extra,$ttl)
delete_record($id)
export_data($domain,$format) ->takes special <ALL> arg for all zones.  $format -> BIND or djb (implement DJB first)
update_nameservers()


FFFF:FFFF:FFFF:FFFF : FFFF:FFFF:FFFF:FFFF
we get:
  <x>:<x>:FFFF:FFFF
we assign:
  <x>:<x>:<y>:<a>	(/64, nominally equivalent to current /32, logically)
  <x>:<x>:<y>:<b>FF	(/56, bitwise equivalent to current /24 relative to /32)
  <x>:<x>:<y>:FFFF	(/48, bitwise equivalent to current /16 relative to /24)
  
Allocations SHOULD leave space for growth


SELECT u.user_id, u.email, u.firstname, u.lastname, u.type, g.group_name
        "FROM users u ".
        "INNER JOIN groups g ON u.group_id=g.group_id ".
	($offset eq 'all' ? '' : " LIMIT $perpage OFFSET ".$offset*$perpage)


SELECT g.group_id, g.group_name, g2.group_name, g.children, count(distinct(u.email)), count(distinct(d.domain))
FROM groups g
INNER JOIN groups g2 ON g2.group_id=g.parent_group_id
LEFT OUTER JOIN users u ON u.group_id=g.group_id
LEFT OUTER JOIN domains d ON d.group_id=g.group_id
GROUP BY g.group_id, g.group_name, g2.group_name, g.children



 record_id | group_id |                  host                  | type |           val           | distance | weight | port |  ttl  | description
-----------+----------+----------------------------------------+------+-------------------------+----------+--------+------+-------+-------------
         1 |        1 | ns1.example.com:hostmaster.DOMAIN      |    6 | 10800:3600:604800:10800 |        0 |      0 |    0 | 86400 |
        25 |        1 | DOMAIN                                 |    1 | 10.2.3.4                |        0 |      0 |    0 |  7200 |
         2 |        1 | DOMAIN                                 |   15 | mx1.example.com         |       10 |      0 |    0 |  7200 |
        26 |        1 | DOMAIN                                 |   15 | mx2.example.com         |       10 |      0 |    0 |  7200 |
        27 |        1 | DOMAIN                                 |    2 | ns2.example.com         |        0 |      0 |    0 |  7200 |
        22 |        1 | DOMAIN                                 |    2 | ns1.example.com         |        0 |      0 |    0 |  7200 |
        31 |        1 | www.DOMAIN                             |    5 | DOMAIN                  |        0 |      0 |    0 | 10800 |
        32 |        1 | DOMAIN                                 |   16 | "v=spf1 a mx -all"      |        0 |      0 |    0 | 10800 |
        17 |        1 | DOMAIN                                 |   33 | srv.example.com         |       15 |      2 |  325 |  7200 |


serial in domains table
'manual' - date+inc
'manual' - monotone
'auto' - generated (TinyDNS only;  uses auto(date) for other exports)

add enable/disable for individual records

log_id?  domain_id?  group_id  user_id  action  detail timestamp



       LOG_EMERG
              A panic condition.

       LOG_ALERT
              A condition that should be corrected immediately, such as a corrupted system database.

       LOG_CRIT
              Critical conditions, such as hard device errors.

       LOG_ERR
              Errors.

       LOG_WARNING

              Warning messages.

       LOG_NOTICE
              Conditions that are not error conditions, but that may require special handling.

       LOG_INFO
              Informational messages.

       LOG_DEBUG
#define LOG_EMERG       0       /* system is unusable */
#define LOG_ALERT       1       /* action must be taken immediately */
#define LOG_CRIT        2       /* critical conditions */
#define LOG_ERR         3       /* error conditions */
#define LOG_WARNING     4       /* warning conditions */
#define LOG_NOTICE      5       /* normal but significant condition */
#define LOG_INFO        6       /* informational */
#define LOG_DEBUG       7       /* debug-level messages */



another web-UI for DNS record maintenance:
http://www.henriknordstrom.net/code/webdns/


sub-octet delegation for v4 nets:
p 216-218 in cricket^Wgrasshopper book

Also see new draft spec, applies to both v4 and v6:
http://tools.ietf.org/html/draft-gersch-dnsop-revdns-cidr-01

new custom types "Forward delegation" and "Reverse delegation"?
 - forward creates NS records in parent for <sub>.parent
 - reverse creates NS records plus CNAMEs for sub-octet zones
-> would solve the conundrum of what to do with the unsightly CNAME
   records presented in the UI to indicate sub-octet zone delegation

BIND reference for views/locations/split-horizon
https://kb.isc.org/article/AA-00851/0/Understanding-views-in-BIND-9-by-example.html

HTML calendar widget?
http://www.cssflow.com/snippets/tag/date-picker  (paid)