# Install DNS server.
#
class dns::server {

    case $::operatingsystem {
        "centos","redhat": {
            case $::operatingsystemrelease {
                /^[1-5]/: {
                    $bind_package = [ "bind-chroot", "caching-nameserver" ]
                }
                default: {
                    $bind_package = "bind-chroot"
                }
            }
        }
        "debian","ubuntu": {
            $bind_package = "bind9"
        }
        "openbsd": {
            $bind_package = undef
        }
        default: {
            $bind_package = "bind-chroot"
        }
    }

    if $bind_package {
        package { "bind":
            ensure => installed,
            name   => $bind_package,
        }
    }

    # first set per os paths
    case $::operatingsystem {
        "fedora": {
            $confdir = "/etc/named"
        }
        "centos","redhat": {
            case $::operatingsystemrelease {
                /^5\..*/: {
                    $chroot = "/var/named/chroot"
                }
                default: {
                    $confdir = "/etc/named"
                }
            }
        }
        "ubuntu": {
            $confdir = "/etc/bind"
            $config = "${confdir}/named.conf"
            $rndckey = "${confdir}/rndc.key"
        }
        "openbsd": {
            $chroot = "/var/named"
        }
    }
    # if some var is not set use default value
    if !$confdir {
        $confdir = "/etc"
    }
    if !$config {
        $config = "/etc/named.conf"
    }
    if !$rndckey {
        $rndckey = "/etc/rndc.key"
    }
    if !$chroot {
        $chroot = ""
    }

    case $::operatingsystem {
        "ubuntu": {
            $group = "bind"
        }
        default: {
            $group = "named"
        }
    }

    file { "${chroot}${rndckey}":
        ensure  => present,
        mode    => "0640",
        owner   => "root",
        group   => $group,
        require => $::operatingsystem ? {
            "openbsd" => undef,
            default   => Package["bind"],
        },
    }
    exec { "rndc-confgen":
        command => $chroot ? {
            ""      => "rndc-confgen -r /dev/urandom -a",
            default => "rndc-confgen -r /dev/urandom -a -t ${chroot}",
        },
        path    => "/bin:/usr/bin:/sbin:/usr/sbin",
        unless  => "test -s ${chroot}${rndckey}",
        require => File["${chroot}${rndckey}"],
    }
    if "${chroot}" != "" {
        file { "/etc/rndc.key":
            ensure  => link,
            target  => "${chroot}${rndckey}",
            owner   => "root",
            group   => $group,
            require => Exec["rndc-confgen"],
        }
    }

    exec { "named-checkconf":
        command => $chroot ? {
            ""      => "/usr/sbin/named-checkconf -z",
            default => "/usr/sbin/named-checkconf -z -t ${chroot}"
        },
        refreshonly => true,
        require     => Exec["rndc-confgen"],
        notify      => Service["named"],
    }

    service { "named":
        name    => $::operatingsystem ? {
            "ubuntu" => "bind9",
            default  => "named",
        },
        ensure  => running,
        enable  => true,
        status  => "/usr/sbin/rndc status",
        stop    => $::operatingsystem ? {
            "openbsd" => "pkill -u named",
            default   => undef,
        },
        start   => $::operatingsystem ? {
            "openbsd" => "/usr/sbin/named",
            default   => undef,
        },
    }

    file { "named.conf":
        ensure  => present,
        path    => "${chroot}${config}",
        mode    => "0640",
        owner   => "root",
        group   => $group,
        require => $::operatingsystem ? {
            "openbsd" => undef,
            default   => Package["bind"],
        },
        notify  => Exec["generate-named-conf"],
    }
    file { "/usr/local/sbin/generate-named-conf.sh":
        ensure  => present,
        content => template("dns/generate-named-conf.sh.erb"),
        mode    => "0755",
        owner   => "root",
        group   => $::operatingsystem ? {
            "openbsd" => "wheel",
            default   => "root",
        },
        notify  => Exec["generate-named-conf"],
    }
    exec { "generate-named-conf":
        command     => "/usr/local/sbin/generate-named-conf.sh > ${chroot}${config}",
        path        => "/bin:/usr/bin:/sbin:/usr/sbin",
        user        => "root",
        refreshonly => true,
        require     => File["/usr/local/sbin/generate-named-conf.sh"],
        notify      => Exec["named-checkconf"],
    }

    file { "${chroot}${confdir}/named.conf.options":
        ensure  => present,
        source  => [ "puppet:///files/dns/named.conf.options.${fqdn}",
                     "puppet:///files/dns/named.conf.options",
                     "puppet:///modules/dns/named.conf.options", ],
        mode    => "0640",
        owner   => "root",
        group   => $group,
        require => $::operatingsystem ? {
            "openbsd" => undef,
            default   => Package["bind"],
        },
        notify  => Exec["named-checkconf"],
    }

    file { "${chroot}${confdir}/named.conf.local":
        ensure  => present,
        source  => [ "puppet:///files/dns/named.conf.local.${fqdn}",
                     "puppet:///files/dns/named.conf.local",
                     "puppet:///modules/dns/named.conf.local", ],
        mode    => "0640",
        owner   => "root",
        group   => $group,
        require => $::operatingsystem ? {
            "openbsd" => undef,
            default   => Package["bind"],
        },
        notify  => Exec["named-checkconf"],
    }
}

# Generate named config from LDAP
#
# Usage:
#
# Put templates you want to generate to master.in directory.
# See dnsdump.py for example template tags.

class dns::server::ldap inherits dns::server {

    include ldap::client::python

    file { "/usr/local/sbin/dnsdump.py":
        ensure  => present,
        source  => "puppet:///modules/dns/dnsdump.py",
        mode    => 0755,
        owner   => root,
        group   => $::operatingsystem ? {
            OpenBSD => wheel,
            default => root,
        },
    }

    file { "/var/named/master.in":
        ensure  => directory,
        source  => "puppet:///files/dns/master.in",
        recurse => true,
        mode    => 0755,
        owner   => root,
        purge   => true,
    }

    exec { "generate-dns-conf":
        path      => "/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin",
        command   => "dnsdump.py --notest /var/named/master.in /var/named/master",
        require   => File["/usr/local/sbin/dnsdump.py"],
        unless    => "dnsdump.py --test /var/named/master.in /var/named/master",
        notify    => Exec["named-checkconf"],
    }
}

# Configure DNS zone.
#
# === Parameters
#
# $name:
#      Zone name.
# $role:
#       The role {master, slave, dynamic, forward} of this host.
# $master:
#      IP address of DNS master for this zone if role is slave.
#      IP address and FQDN of DNS master for this zone if running as
#      master and using autogenerated zone.
# $slaves:
#      IP addresess and host names of the DNS slaves for this zone.
#      Required only when using autogenrated zones.
# $forwarders:
#      Array of forwarder IP addresses for forward role zones.
# $source:
#      Source file to use for zone. Defaults to auto.
# $key:
#      Key for dynamic zones.
# $keytype:
#      Key algorithm. Defaults to 'hmac-md5'.
# $contact:
#      SOA contact email for autogenerated zones.
#      Defaults to hostmaster.$zone.
#
define dns::zone($role = "master", $master = "", $slaves = [], $forwarders = [],
                 $source = "AUTO", $key = "none", $keytype = "hmac-md5",
                 $contact = "NONE") {

    if $name =~ /^([^\/]+\/)?([0-9]+)\/([0-9\.]+\.in-addr\.arpa)$/ {
        # Special handling for RFC2317 style in-addr.arpa zones
        case $1 {
            ".":       { $view = "" }
            "default": { $view = "" }
            default:   { $view = $1 }
        }
        $zone     = "$2/$3"
        $zonefile = "$2-$3"
    } else {
        case dirname($name) {
            ".":       { $view = "" }
            "default": { $view = "" }
            default:   { $view = dirname($name) }
        }
        $zone     = basename($name)
        $zonefile = $zone
    }

    case $role {
        "master": {
            case $::operatingsystem {
                "openbsd": {
                    $zonedir = "/master/${view}"
                }
                "fedora","centos","redhat": {
                    $zonedir = "/var/named/${view}"
                }
                "ubuntu": {
                    $zonedir = "/etc/bind/${view}"
                }
            }
        }
        "dynamic": {
            if $key == "none" {
                fail("No key defined for dns::zone '${name}'")
            }
            case $::operatingsystem {
                "openbsd": {
                    $zonedir = "/dynamic/${view}"
                }
                "fedora","centos","redhat": {
                    $zonedir = "/var/named/dynamic/${view}"
                }
                "ubuntu": {
                    $zonedir = "/etc/bind/dynamic/${view}"
                }
            }
        }
        "slave": {
            if $master == "" {
                fail("No master defined for dns::zone '${name}'")
            }
            case $::operatingsystem {
                "openbsd": {
                    $zonedir = "/slave/${view}"
                }
                "fedora","centos","redhat": {
                    $zonedir = "/var/named/slaves/${view}"
                }
                "ubuntu": {
                    $zonedir = "/var/cache/bind/${view}"
                }
            }
        }
        "forward": {
            if $forwarders == [] {
                fail("No forwarders defined for dns::zone '${name}'")
            }
            $zonedir = ""
        }
        default: {
            fail("Unknown DNS zone type '${role}'")
        }
    }

    if $view != "" {
        if !defined(File["${dns::server::chroot}${dns::server::confdir}/${view}"]) {
            file { "${dns::server::chroot}${dns::server::confdir}/${view}":
                ensure => directory,
                mode   => "0750",
                owner  => "root",
                group  => $dns::server::group,
                before => File["${dns::server::chroot}${dns::server::confdir}/${view}/zone.${zonefile}"],
            }
        }
    }

    if $zonedir != "" {
        if !defined(File["${dns::server::chroot}${zonedir}"]) {
            file { "${dns::server::chroot}${zonedir}":
                ensure  => directory,
                mode    => $role ? {
                    "master" => "0750",
                    default  => "0770",
                },
                owner   => "root",
                group   => $dns::server::group,
                before  => $role ? {
                    "master" => File["${dns::server::chroot}${zonedir}/db.${zonefile}"],
                    default  => undef,
                },
                require => $::operatingsystem ? {
                    "openbsd" => undef,
                    default   => Package["bind"],
                },
            }
        }
    }

    file { "${dns::server::chroot}${dns::server::confdir}/${view}/zone.${zonefile}":
        ensure  => present,
        content => template("dns/zone.$role.erb"),
        mode    => "0640",
        owner   => "root",
        group   => $dns::server::group,
        require => $::operatingsystem ? {
            "openbsd" => undef,
            default   => Package["bind"],
        },
        notify  => Exec["generate-named-conf"],
    }

    if $role == "master" and $zone != "." {
        if $source != "AUTO" {
             file { "${dns::server::chroot}${zonedir}/db.${zonefile}":
                 ensure  => present,
                 source  => $source,
                 mode    => "0640",
                 owner   => "root",
                 group   => $dns::server::group,
                 require => $::operatingsystem ? {
                     "openbsd" => undef,
                     default   => Package["bind"],
                 },
                 notify  => Exec["generate-named-conf"],
            }
        } else {
            if $contact == "NONE" {
                $soacontact = "hostmaster.${zone}"
            } else {
                $soacontact = $contact
            }

            file { "${dns::server::chroot}${zonedir}/db.${zonefile}":
                ensure  => present,
                content => template("dns/db.erb"),
                mode    => "0640",
                owner   => "root",
                group   => $dns::server::group,
                require => $::operatingsystem ? {
                    "openbsd" => undef,
                    default   => Package["bind"],
                },
                notify  => Exec["named-checkconf"],
            }

            file { "${dns::server::chroot}${zonedir}/db.${zonefile}-dynamic":
                ensure  => present,
                mode    => "0640",
                owner   => "root",
                group   => $dns::server::group,
                require => $::operatingsystem ? {
                    "openbsd" => undef,
                    default   => Package["bind"],
                },
                notify  => Exec["named-checkconf"],
            }
            file { "${dns::server::chroot}${zonedir}/db.${zonefile}-dynamic.d":
                ensure  => directory,
                mode    => "0640",
                owner   => "root",
                group   => $dns::server::group,
                require => $::operatingsystem ? {
                    "openbsd" => undef,
                    default   => Package["bind"],
                },
                purge   => true,
                force   => true,
                recurse => true,
                source  => "puppet:///modules/custom/empty",
                notify  => Service["named"],
            }

            file { "${dns::server::chroot}${zonedir}/db.${zonefile}-static":
                ensure  => present,
                source  => [
                  "puppet:///files/dns/db.${zonefile}-static.${homename}",
                  "puppet:///files/dns/db.${zonefile}-static",
                  "puppet:///modules/dns/empty",
                ],
                mode    => "0640",
                owner   => "root",
                group   => $dns::server::group,
                require => $::operatingsystem ? {
                    "openbsd" => undef,
                    default   => Package["bind"],
                },
                notify  => Exec["named-checkconf"],
            }

            exec { "update-db.${zonefile}-dynamic":
                path        => "/bin:/usr/bin:/sbin:/usr/sbin",
                command     => "/bin/sh -c 'find db.${zonefile}-dynamic.d -name rr.\\* | xargs cat > db.${zonefile}-dynamic'",
                cwd         => "${dns::server::chroot}${zonedir}",
                user        => "root",
                refreshonly => true,
                notify      => Service["named"],
            }

            Dns::Node::Record <<| tag == $zone |>> {
                dbdir   => "${dns::server::chroot}${zonedir}/db.${zonefile}-dynamic.d",
                notify  => Exec["update-db.${zonefile}-dynamic"],
                require => File["${dns::server::chroot}${zonedir}/db.${zonefile}-dynamic",
                                "${dns::server::chroot}${zonedir}/db.${zonefile}-dynamic.d"],
            }
            Dns::Node::Record::Reverse <<| tag == $zone |>> {
                dbdir   => "${dns::server::chroot}${zonedir}/db.${zonefile}-dynamic.d",
                notify  => Exec["update-db.${zonefile}-dynamic"],
                require => File["${dns::server::chroot}${zonedir}/db.${zonefile}-dynamic",
                                "${dns::server::chroot}${zonedir}/db.${zonefile}-dynamic.d"],
            }
        }
    }

}


# Generate dynamic DNS records for puppet node.
#
# === Global variables
#
#   $dns_node_name:
#       A record name. Defaults to $hostname.
#
#   $dns_node_zone:
#       DNS zone. Defaults to $domain.
#
#   $dns_node_ipaddr:
#       IP address to use in records. Defaults to $ipaddress.
#
#   $dns_node_netmask:
#       Netmask for determining reverse zone class. Defaults to $netmask.
#
#   $dns_node_alias:
#       List of names used as CNAME records.
#
class dns::node {

    if !$dns_node_name {
        $dns_node_name = $::hostname
    }

    if !$dns_node_zone {
        $dns_node_zone = $::domain
    }

    if !$dns_node_ipaddr and !$dns_node_netmask {
        $dns_node_ipaddr = $::ipaddress
        $dns_node_netmask = $::netmask
    } else {
        if !$dns_node_ipaddr or !$dns_node_netmask {
            fail("Must define both \$dns_node_ipaddr and \$dns_node_netmask")
        }
    }

    if $dns_node_alias {
        $dns_node_cname = inline_template("<% dns_node_alias.each do |a| -%><%= a %>\tIN\tCNAME\t<%= dns_node_name %>\n<% end -%>")
    } else {
        $dns_node_cname = ""
    }

    @@dns::node::record { "${dns_node_name}.${dns_node_zone}":
        ipaddr => $dns_node_ipaddr,
        host   => $dns_node_name,
        cname  => $dns_node_cname,
        tag    => $dns_node_zone
    }

    $dns_node_class = inline_template("<%= dns_node_netmask.scan('255.').length -%>")
    $dns_node_raddr = inline_template("<%= dns_node_ipaddr.split('.').reverse.first(4-dns_node_class.to_i).join('.') -%>")
    $dns_node_rzone = inline_template("<%= dns_node_ipaddr.split('.').reverse.last(dns_node_class.to_i).join('.') -%>.in-addr.arpa")

    @@dns::node::record::reverse { "${dns_node_name}.${dns_node_zone}":
        ipaddr => $dns_node_raddr,
        host   => "${dns_node_name}.${dns_node_zone}",
        tag    => $dns_node_rzone
    }

}


define dns::node::record($dbdir, $ipaddr, $host, $cname="") {

    file { "${dbdir}/rr.${name}":
        ensure  => present,
        content => "${host}\tIN\tA\t${ipaddr}\n${cname}",
    }

}


define dns::node::record::reverse($dbdir, $ipaddr, $host) {

    file { "${dbdir}/rr.${name}":
        ensure  => present,
        content => "${ipaddr}\tIN\tPTR\t${host}.\n",
    }

}


# Install dynamic DNS update script
#
# === Global variables
#
#   $dns_nsupdate_name:
#       FQDN to update into DNS.
#
#   $dns_nsupdate_key:
#       DNS key to use when updating entry. Usually in format:
#         <keyname> <secret>
#       for example:
#         gw1.example.com. sZ6GgTZLBX83LXCoo
#
#   $dns_nsupdate_server:
#       DNS server address where to update entry.
#
#   $dns_nsupdate_zone:
#       Zone name to update. Defaults to domain part of
#       $dns_nsupdate_name variable.
#
class dns::nsupdate {

    file { "/usr/local/sbin/nsupdate.sh":
        ensure  => present,
        content => template("dns/nsupdate.sh.erb"),
        mode    => "0700",
        owner   => root,
        group   => $::operatingsystem ? {
            openbsd => wheel,
            default => root,
        },
    }

    cron { "nsupdate":
        ensure  => present,
        command => "/usr/local/sbin/nsupdate.sh",
        minute  => "*/5",
        require => File["/usr/local/sbin/nsupdate.sh"],
    }
}
