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 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 +----------------------------------------------------------------+ | 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 arg for all zones. $format -> BIND or djb (implement DJB first) update_nameservers() FFFF:FFFF:FFFF:FFFF : FFFF:FFFF:FFFF:FFFF we get: ::FFFF:FFFF we assign: ::: (/64, nominally equivalent to current /32, logically) :::FF (/56, bitwise equivalent to current /24 relative to /32) :::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 book