all-in-one 4 (bind9 - internal/external+dynamic update)

ทำ dns server โดยใช้ bind9

จะทำให้สามารถใช้งานได้ทั้งภายใน และภายนอก (ภายนอกไม่ค่อยจำเป็น แต่ติดตั้งไว้เผื่อในอนาคตอาจเพิ่มการ lookup จากเซิร์ฟเวอร์ของเราที่อยู่ภายนอก)
โดยจะแยกไอพีภายในและภายนอกเป็น 2 กลุ่ม
และสร้างกรงขังด้วย (chroot jail)

สมมุติว่า

  • มีโซนภายใน โดเมนหลักเป็น example.com มีชื่อทุกเครื่องในเครือข่าย โดยจะทำ reverse ip ด้วย (คือค้นย้อนกลับจากเลขไอพีได้) และโดเมนรองเป็น example.org มีชื่อเครื่องนี้เครื่องเดียว ไม่ทำค้นย้อน (ทำไม่ได้ เพราะไอพีซ้ำกัน) ทั้งสองโดเมนมีไอพีเป็น 192.168.1.0/24
  • โซนภายนอก โดเมนเป็น example.com และ example.org โดยที่ทุกโดเมนมีไอพีเดียวกัน เพราะถือว่าต่อสาย adsl สายเดียว และแต่ละโดเมนอาจมีหลายชื่อ โดยใช้ CNAME เทียบเอา
    ทุกโดเมนของโซนภายนอก จะไม่ทำค้นย้อน (reverse zone) เพราะเรามีหลายโดเมนแต่มีไอพีเดียว และถึงแม้จะมีโดเมนเดียว ค่าที่โลกภายนอกค้นได้ก็คงไม่ใช่จากเครื่องเราอยู่ดี
  • วงในทำเป็นแบบเปลี่ยนค่าไม่ได้ วงนอกทำเป็นแบบเปลี่ยนค่าได้อย่างง่าย เพื่ออัปเดตตอนสายหลุด

ติดตั้งแพกเกจ แล้วสั่งหยุดการทำงาน
# aptitude install bind9 dnsutils
# /etc/init.d/bind9 stop

เรื่องกรงขัง (chroot jail) จะเก็บไว้ที่ /sys1/sysb/chroot/bind
สร้างไดเรกทอรี่ก่อน แล้วโยงลิงก์ chroot ไปที่ / ให้พิมพ์ง่าย
# mkdir -p /sys1/sysb/chroot/bind
# ln -sf /sys1/sysb/chroot /
# chown root:bind /chroot/bind

แก้ให้อยู่ในกรง
# vi /etc/default/bind9

...
# OPTIONS="-u bind"
OPTIONS="-u bind -t /chroot/bind"
...

แก้ไขผู้ใช้ชื่อ bind ให้ย้ายบ้านไปที่ /chroot/bind แทน
# usermod --home /chroot/bind bind

เตรียมไดเรคทอรี่ย่อยในกรง
# mkdir -p /chroot/bind/{etc,dev,var/{cache,log,run}}
# mkdir -p /chroot/bind/var/cache/bind
# mv /etc/bind /chroot/bind/etc
# ln -sf /chroot/bind/etc/bind /etc
# mknod /chroot/bind/dev/null c 1 3
# mknod /chroot/bind/dev/random c 1 8
# chmod 666 /chroot/bind/dev/{null,random}
# chown -R bind:bind /chroot/bind/var/*

ทำให้ระบบเก็บปูมของ bind (system logging - syslogd)
# vi /etc/default/syslogd

...
# SYSLOGD=""
SYSLOGD="-a /chroot/bind/dev/log"
...

ปรับตั้งระบบปูมของ bind
# vi /etc/bind/named.conf.local

...
logging {
    channel "querylog" { file "/var/log/bind9-query.log"; print-time yes; };
    category queries { querylog; };
};
...

ปรับตั้งประวัติปูมของ bind
# vi /etc/logrotate.d/bind9-query

/chroot/bind/var/log/bind9-query.log {
    weekly
    missingok
    rotate 10
    postrotate
        /etc/init.d/bind9 reload > /dev/null
    endscript
    compress
    notifempty
}

# ln -sf /chroot/bind/var/log/bind9-query.log /var/log/bind9-query.log

แก้ไข options ให้มาเก็บ pid ในกรงขัง
# vi /etc/bind/named.conf.options

...
options {
    directory "/var/cache/bind";
    pid-file    "/var/run/named.pid";
...

เสร็จขั้นต้น ทดสอบระบบครั้งแรก
# /etc/init.d/sysklogd restart
# /etc/init.d/bind9 restart

ต้องไม่มีรายงานข้อผิดพลาด

ถ้าเรียบร้อยก็สั่งหยุดบริการ เพื่อปรับตั้งต่อ
# /etc/init.d/bind9 stop

เครือข่ายภายในและภายนอก

ต่อไปเป็นเรื่องการทำให้รองรับเครือข่ายภายในและภายนอก

สร้างไดเรกทอรี่ขึ้นมารองรับ
# mkdir -p /etc/bind/{internal,external}
# cd /etc/bind

สร้างกฎ acl สำหรับเครือข่ายภายใน ตั้งชื่อว่า acl_internal (นอกจากนั้นจะถือว่าเป็น external ทั้งหมด)
และให้มาอ่านไฟล์คอนฟิกที่เราจะสร้างขึ้นภายหลัง ของโซน example.com และ example.org
# vi named.conf.local

...
acl acl_internal {
#    127.0.0.0/8;
    192.168.0.0/16;
};

include "/etc/bind/allzone.conf";
...

หมายเหตุ - ปกติวง 127.0.0.0/8 ควรจะถึอเป็นวงในด้วย แต่ในที่นี้เราจะตัดออกจาก acl_internal เนื่องจากเราไม่สามารถสั่งอัปเดตไอพีจากวงในได้ เพราะตั้งเป็นแบบเปลี่ยนค่าไม่ได้ ดังนั้นเราจะใช้ localhost ในการอัปเดตไอพีที่ถูกเรียกจากวงนอก จึงต้องเว้นไว้ แต่ใส่คอมเมนต์ไว้เพื่อจะได้ไม่หลงลืมข้อนี้

สร้างไฟล์คอนฟิกของ example.com และ example.org รวมกัน ตั้งชื่อว่า allzone.conf โดยตัดเอาเนื้อไฟล์ใน named.conf ในส่วนของการกำหนดโซนมาใส่ไว้
(ถ้ามีการใช้ view เราต้องใส่การกำหนดโซนไว้ภายใต้ view เท่านั้น ไม่สามารถกำหนดแบบซ้อนได้)

ตัดเนื้อไฟล์จาก named.conf ออก
# vi named.conf

...
//-----8<-------------------------------------------
// prime the server with knowledge of the root servers
zone "." {
    type hint;
    file "/etc/bind/db.root";
};

// be authoritative for the localhost forward and reverse zones, and for
// broadcast zones as per RFC 1912

zone "localhost" {
    type master;
    file "/etc/bind/db.local";
};

zone "127.in-addr.arpa" {
    type master;
    file "/etc/bind/db.127";
};

zone "0.in-addr.arpa" {
    type master;
    file "/etc/bind/db.0";
};

zone "255.in-addr.arpa" {
    type master;
    file "/etc/bind/db.255";
};
//------------------------------------------->8-----
...

สร้างไฟล์ allzone.conf ในการกำหนดค่าไฟล์ในการคุมทุกโซน โดยเอาเนื้อจาก named.conf ข้างบนมาใส่ในส่วนของ view internal
# vi allzone.conf

include "/etc/bind/key.conf";

view "internal" {
    match-clients { acl_internal; };

    // ----- PASTE FROM named.conf ----------------------------------------
    // prime the server with knowledge of the root servers
    zone "." {
        type hint;
        file "/etc/bind/db.root";
    };

    // be authoritative for the localhost forward and reverse zones, and for
    // broadcast zones as per RFC 1912

    zone "localhost" {
        type master;
        file "/etc/bind/db.local";
    };

    zone "127.in-addr.arpa" {
        type master;
        file "/etc/bind/db.127";
    };

    zone "0.in-addr.arpa" {
        type master;
        file "/etc/bind/db.0";
    };

    zone "255.in-addr.arpa" {
        type master;
        file "/etc/bind/db.255";
    };
    // ----- END PASTE FROM named.conf ------------------------------------
    zone "example.com" IN {
        type master;
        file "/etc/bind/internal/example.com.zone";
        allow-update { none; };
    };
    zone "1.168.192.in-addr.arpa" IN {
        type master;
        file "/etc/bind/internal/example.com.reverse";
        allow-update { none; };
    };
    zone "example.org" IN {
        type master;
        file "/etc/bind/internal/example.org.zone";
        allow-update { none; };
    };
};

view "external" {
    match-clients { any; };
    zone "example.com" IN {
        type master;
        file "/etc/bind/external/example.com.zone";
        allow-update {
            key allhost.;
        }; 
    };
    zone "example.org" IN {
        type master;
        file "/etc/bind/external/example.org.zone";
        allow-update {
            key allhost.;
        }; 
    };
};

สร้างกุญแจสำหรับเปลี่ยนค่าโซน ใช้กุญแจเดียวกันทุกโซน
# dnssec-keygen -r /dev/urandom -a HMAC-MD5 -b 512 -n HOST allhost

Kallhost.+157+38278

และได้ไฟล์ Kallhost.+157+38278.key และ Kallhost.+157+38278.private

ตัวอย่างเนื้อไฟล์ของ Kallhost.+157+38278.key มีดังนี้

allhost. IN KEY 512 3 157 hvql4JlIIajNJzxFLuQQQg4HUNSQExpTuO2kzLudphxHPo3+N976ztqI dPXn2rdvVM6mHSG+0wwhlky+MRwQ+Q==

นำเนื้อไฟล์ของ Kallhost.+157+38278.key ท่อนที่เป็นรหัสมาสร้างไฟล์กุญแจ ตั้งชื่อว่า key.conf
# vi key.conf

key allhost. {
    algorithm HMAC-MD5;
    secret "hvql4JlIIajNJzxFLuQQQg4HUNSQExpTuO2kzLudphxHPo3+N976ztqI dPXn2rdvVM6mHSG+0wwhlky+MRwQ+Q==";
};

ส่วนต่อไปนี้ เขียนไว้เพื่อให้เห็นโครงสร้าง แต่ตอนหน้าจะเขียนเป็นสคริปต์สำหรับผลิตไฟล์โซน และไฟล์รีเวิร์ส หากคร้านที่จะเขียนโซนไฟล์เอง อาจข้ามไปอ่านส่วนขยายตอนหน้าเลยก็ได้ครับ

ต่อไปเป็นการสร้างไฟล์โซนและไฟล์รีเวิร์สโซน

internal
เป็นแบบเปลี่ยนค่าไม่ได้ทั้งหมด example.com ทำทั้ง forward และ reverse
และ example.org ทำ forward อย่างเดียว
มีดังนี้

ไฟล์โซน internal ของ example.com
# vi internal/example.com.zone

$TTL    86400
@       IN      SOA server1.example.com. root.server1.example.com. (
                        41      ; serial (d. adams)
                        3H      ; refresh
                        15M     ; retry
                        1W      ; expiry
                        1D )    ; minimum
@               NS      ns1
server1 IN      A       192.168.1.1
www     IN      CNAME   server1
ftp     IN      CNAME   server1
mail    IN      CNAME   server1
ns1     IN      CNAME   server1
work1   IN      A       192.168.1.101
work2   IN      A       192.168.1.102
work3   IN      A       192.168.1.103

ไฟล์รีเวิร์ส internal ของ example.com
# vi internal/example.com.reverse

$TTL    86400
@       IN      SOA server1.example.com. root.server1.example.com. (
                        41      ; serial (d. adams)
                        3H      ; refresh
                        15M     ; retry
                        1W      ; expiry
                        1D )    ; minimum
@               NS      ns1
1       IN      PTR     server1.example.com.
101     IN      PTR     work1.example.com.
102     IN      PTR     work2.example.com.
103     IN      PTR     work3.example.com.

ไฟล์โซน internal ของ example.org
# vi internal/example.org.zone

$TTL    86400
@       IN      SOA server1.example.org. root.server1.example.org. (
                        42      ; serial (d. adams)
                        3H      ; refresh
                        15M     ; retry
                        1W      ; expiry
                        1D )    ; minimum
@               NS      ns1
server1 IN      A       192.168.1.1
www     IN      CNAME   server1
ns1     IN      CNAME   server1
mail    IN      CNAME   server1
ftp     IN      CNAME   server1
external
เป็นแบบเปลี่ยนค่าได้แบบง่าย ทำ forward อย่างเดียว ทั้ง example.com และ example.org

สร้างโซนไฟล์ external ของ example.com
# vi external/example.com.zone

$TTL    86400
@       IN      SOA server1.example.com. root.server1.example.com. (
                        51      ; serial (d. adams)
                        3H      ; refresh
                        15M     ; retry
                        1W      ; expiry
                        1D )    ; minimum
@               NS      ns1
server1 IN      A       101.102.103.104
www     IN      CNAME   server1
ftp     IN      CNAME   server1
mail    IN      CNAME   server1
ns1     IN      CNAME   server1

สร้างโซนไฟล์ external ของ example.org
# vi external/example.org.zone

$TTL    86400
@       IN      SOA server1.example.org. root.server1.example.org. (
                        52      ; serial (d. adams)
                        3H      ; refresh
                        15M     ; retry
                        1W      ; expiry
                        1D )    ; minimum
@               NS      ns1
server1 IN      A       101.102.103.104
www     IN      CNAME   server1
ns1     IN      CNAME   server1
mail    IN      CNAME   server1
ftp     IN      CNAME   server1

เปลี่ยนเจ้าของและกลุ่ม
# chown -R bind:bind *

ทดสอบขั้นต้น
# /etc/init.d/bind9 restart
ต้องไม่มีรายงานข้อผิดพลาด

เสร็จหมดแล้ว

ทดสอบการอัปเดต

เนื่องจากเราแบ่งเป็นวงในและวงนอก โดยที่สามารถอัปเดตได้เฉพาะวงนอกเท่านั้น ดังนั้นในการสั่งอัปเดต ต้องระบุเซิร์ฟเวอร์เป็น localhost เท่านั้น (ถ้าระบุเซิร์ฟเวอร์เป็น 192.168.1.1 เขาจะไม่ยอมอัปเดตให้)

ทดลองอัปเดต
# nsupdate -d -v -k /etc/bind/Kallhost.+157+38278.private

Creating key...
> server localhost
> update add testing.example.com. 86400 IN A 101.102.103.155
> send
Reply from SOA query:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id:  22523
;; flags: qr aa rd ra ; QUESTION: 1, ANSWER: 0, AUTHORITY: 1, ADDITIONAL: 1
;; QUESTION SECTION:
;testing.example.com.           IN      SOA

;; AUTHORITY SECTION:
example.com.            86400   IN      SOA     server1.example.com. root.server1.example.com. 57 10800 900 604800 86400

;; TSIG PSEUDOSECTION:
allhost.                0       ANY     TSIG    hmac-md5.sig-alg.reg.int. 1203992365 300 16 sMkuPmAy7VF1RCoGpHjrXA== 22523 NOERROR 0


Found zone name: example.com
The master is: server1.example.com
Sending update to 127.0.0.1#53
Outgoing update query:
;; ->>HEADER<<- opcode: UPDATE, status: NOERROR, id:  50100
;; flags: ; ZONE: 1, PREREQ: 0, UPDATE: 1, ADDITIONAL: 1
;; ZONE SECTION:
;example.com.                   IN      SOA

;; UPDATE SECTION:
testing.example.com.    86400   IN      A       101.102.103.155

;; TSIG PSEUDOSECTION:
allhost.                0       ANY     TSIG    hmac-md5.sig-alg.reg.int. 1203992365 300 16 pmaYIKqjlgGU+FJvT3uZxA== 50100 NOERROR 0


Reply from update query:
;; ->>HEADER<<- opcode: UPDATE, status: NOERROR, id:  50100
;; flags: qr ra ; ZONE: 0, PREREQ: 0, UPDATE: 0, ADDITIONAL: 1
;; TSIG PSEUDOSECTION:
allhost.                0       ANY     TSIG    hmac-md5.sig-alg.reg.int. 1203992365 300 16 9ca6E1RY9RfHacqi7uSuaQ== 50100 NOERROR 0

> quit

ทั้งหมดต้องไม่มีข้อผิดพลาด

ลองค้นค่าดู
# nslookup testing.example.com localhost

nslookup testing.example.com 
Server:         localhost
Address:        127.0.0.1#53

Name:   testing.example.com
Address: 101.102.103.155

เริ่ม bind ใหม่
# /etc/init.d/bind9 restart

จบส่วนของ name server ปกติ

การอัปเดตไอพีเมื่อสายหลุด

(ส่วนนี้เป็นตอนต่อจากตอนที่แล้ว คือการทำเรื่อง dynamic dns client)

กลับไปแก้ไขไฟล์ d.router-reconnect โดยเพิ่มส่วนของการอัปเดตไอพี
# vi /usr/local/sbin/d.router-reconnect

...
#-------ADDITIONAL BIND9 SCRIPT:------------------

#UPDATE LOCAL BIND9
IP=`/usr/local/sbin/d.router-getip`
KEY="/etc/bind/Kallhost.+157+38278.private"
HOSTNAME=`hostname`

#GET DOMAIN FROM FIRST PROVIDER ONLY
DIR="/etc/ddns/ddns-enabled"
PROVIDER=`ls $DIR | cut -d\  -f1`
. $DIR/$PROVIDER
for i in $UPDATE_DOMAIN; do
    DOMAIN=`echo $i | cut -d: -f1`
    nsupdate -k $KEY << EOF
server localhost
update delete $HOSTNAME.$DOMAIN
update add $HOSTNAME.$DOMAIN. 86400 IN A $IP
send
quit
EOF

done

จบจริง ๆ แล้ว

อ้างอิง

Topic: 

all-in-one 4.1 (bind9 - zone generator script)

ทำ dns server โดยใช้ bind9 - ต่อส่วนขยาย

ท่อนนี้ไม่มีอะไรมาก เพียงแต่ต้องการรวมศูนย์ข้อมูลไว้ในไฟล์เดียว เพื่อต้องการให้ดูง่ายตรวจสอบง่ายและลดโอกาสผิดพลาดในการแก้ไขไฟล์โซนและไฟล์รีเวิร์สโซน
จึงสร้างเป็นสคริปต์เล็ก ๆ ขึ้นมาเพื่อใช้อ่านค่าข้อมูลที่เราสร้างเอาไว้ แล้วผลิตไฟล์โซนต่าง ๆ ออกมาให้ครบตามที่เราตั้งไว้

สคริปต์ตั้งชื่อว่า d.bind-genzone เอาไว้ที่ /usr/local/sbin มีดังนี้
# vi /usr/local/sbin/d.bind-genzone

#!/bin/bash
# GENERATE ZONE FILE [AND REVERSE ZONE FILE IF ADD OPTION -b]
# READ DATA FROM DATAFILE IN FORMAT
#     zone:ZONENAME:HOSTNAME:SERIAL:IP_ABC
#     ns:NAMESERVER1 NAMESERVER2
#     mx:MAILSERVER1,RR1 MAILSERVER2,RR2
#     IP_D:NAME CNAME1 CNAME2 ...
#     ...
# EXAMPLE:
#     zone:example.com:server1:43:192.168.1
#     ns:ns1 ns2
#     mx:mail,10 mail2,20
#     1:server1 ns1 mail www ftp
#     2:ns2
#     101:work1
#     102:work2
#     ...

#GLOBAL VAR
TTL=86400
BINDUSER="bind"
BINDGROUP="bind"

#FUNTION
function usage {
    cat << EOF

Script to generate zone file and reverse zone file
USAGE: $0 [-b] DATAFILE
OPTIONS:
  -b = both, generate both forward and reverse zone file,
       only generate forward zone file, if omitted
ARGUMENT:
  DATAFILE : datafile in format:-
     zone:ZONENAME:HOSTNAME:SERIAL:IP_ABC
    [ns:NAMESERVER1 NAMESERVER2]
    [mx:MAILSERVER1,RR1 MAILSERVER2,RR2]
     IP_D:NAME CNAME1 CNAME2 ...
     ...
  EXAMPLE OF DATAFILE:
  example1:
     zone:example.com:server1:43:192.168.1
     ns:ns1 ns2
     mx:mail,10 mail2,20
     1:server1 ns1 mail www ftp
     2:ns2
     101:work1
     102:work2
     192.168.5.1:router
     ...
  example2:
     zone:example.org:ns2:44:192.168.1
     2:ns2
     ns:ns1 ns2

EOF
}

function gen_header {
    Z=$1
    H=$2
    S=$3
    cat << EOF
\$TTL   $TTL
@       IN      SOA $H.$Z. root.$H.$Z. (
                        $S      ; serial (d. adams)
                        3H      ; refresh
                        15M     ; retry
                        1W      ; expiry
                        1D )    ; minimum
EOF
}

function gen_ns {
    H=$1
    cat << EOF
@               NS      $H
EOF
}

function gen_mx {
    H=$1
    R=$2
    cat << EOF
@               MX      $R $H
EOF
}

function gen_name {
    N=$1
    I=$2
    cat << EOF
$N      IN      A       $I
EOF
}

function gen_ptr {
    I=$1
    N=$2
    cat << EOF
$I      IN      PTR     $N.$ZONENAME.
EOF
}

function gen_cname {
    C=$1
    N=$2
    cat << EOF
$C      IN      CNAME   $N
EOF
}

function get_ip {       
    I1="$1."      #input ip     
    I2="$2."      #$IP_ABC      
    A=$(echo "$I1" | cut -d. -f1) 
    B=$(echo "$I1" | cut -d. -f2)
    C=$(echo "$I1" | cut -d. -f3)
    D=$(echo "$I1" | cut -d. -f4)
    W=$(echo "$I2" | cut -d. -f1)
    X=$(echo "$I2" | cut -d. -f2)
    Y=$(echo "$I2" | cut -d. -f3)
    Z=$(echo "$I2" | cut -d. -f4)
    if [ ! "$B" ]; then
        echo "$W.$X.$Y.$A"; return
    elif [ ! "$C" ]; then
        echo "$W.$X.$A.$B"; return
    elif [ ! "$D" ]; then
        echo "$W.$A.$B.$C"; return
    else
        echo "$I1"      
    fi
}

#BEGIN MAIN
DATAFILE=""
WITHREVERSE=""
    
while getopts "b" ARGS; do
    case "$ARGS" in
        b) WITHREVERSE="true"; shift $(($OPTIND - 1)) ;;
    esac
done

DATAFILE=$1
shift

while getopts "b" ARGS; do
    case "$ARGS" in
        b) WITHREVERSE="true"; shift $(($OPTIND - 1)) ;;
    esac
done

if [ ! "$DATAFILE" ] || [ ! -f "$DATAFILE" ]; then
    usage
    exit 1
fi

ZONENAME=""
HOSTNAME=""
SERIAL=""
IP_ABC=""
ZONEFILE=""
REVERSEFILE=""

cat $DATAFILE | grep -v "#" | while read DATA; do
    #ZONE HEADER
    if [ "${DATA:0:5}" == "zone:" ]; then
        ZONENAME=$(echo $DATA | cut -d: -f2)
        HOSTNAME=$(echo $DATA | cut -d: -f3)
        SERIAL=$(echo $DATA | cut -d: -f4)
        IP_ABC=$(echo $DATA | cut -d: -f5)

        #FORWARD ZONE FILE
        ZONEFILE="$ZONENAME.zone"
        if [ -f "$ZONEFILE" ]; then
            mv "$ZONEFILE" "$ZONEFILE.bak"
        fi
        gen_header $ZONENAME $HOSTNAME $SERIAL > $ZONEFILE
        chown $BINDUSER:$BINDGROUP $ZONEFILE

        if [ "$WITHREVERSE" ]; then
            #REVERSE ZONE FILE
            REVERSEFILE="$ZONENAME.reverse"
            if [ -f "$REVERSEFILE" ]; then
                mv "$REVERSEFILE" "$REVERSEFILE.bak"
            fi
            gen_header $ZONENAME $HOSTNAME $SERIAL > $REVERSEFILE
            chown $BINDUSER:$BINDGROUP $REVERSEFILE
    
            echo "Generate $ZONEFILE and $REVERSEFILE ..."
        else
            echo "Generate $ZONEFILE ..."
        fi

    #NS 
    elif [ "${DATA:0:3}" == "ns:" ] && [ "$ZONENAME" ]; then
        for i in $(echo $DATA | cut -d: -f2); do
            gen_ns $i >> $ZONEFILE
            if [ "$WITHREVERSE" ]; then
                gen_ns $i >> $REVERSEFILE
            fi
        done
    
    #MX
    elif [ "${DATA:0:3}" == "mx:" ] && [ "$ZONENAME" ]; then
        for i in $(echo $DATA | cut -d: -f2); do
            $MAILNAME=$(echo $i | cut -d, -f1)
            $RRPRIORITY=$(echo $i | cut -d, -f2)
            gen_mx $MAILNAME $RRPRIORITY >> $ $ZONEFILE
        done
    
    #DATA
    elif [ "$DATA" ]; then
        RAW_IP=$(echo $DATA | cut -d: -f1)
        FULL_IP=`get_ip $RAW_IP $IP_ABC`
        NAMES=$(echo $DATA | cut -d: -f2)
        FIRSTNAME=$(echo $NAMES | cut -d\  -f1)
        gen_name $FIRSTNAME $FULL_IP >> $ZONEFILE
        if [ "$WITHREVERSE" ]; then
            gen_ptr $RAW_IP $FIRSTNAME >> $REVERSEFILE
        fi
        for i in $NAMES; do
            if [ "$i" != "$FIRSTNAME" ]; then
                gen_cname $i $FIRSTNAME >> $ZONEFILE
            fi
        done
    fi  

done

แก้ให้รันได้เฉพาะ root
# chmod 700 /usr/local/sbin/d.bind-genzone

หลังจากนั้นสร้างไฟล์ข้อมูลของ internal บรรจุข้อมูลของ example.com เอาไว้ใน /etc/bind/internal ตั้งชื่อว่า both-data เพราะจะทำค้นย้อนด้วย ดังนี้
# cd /etc/bind/internal
# vi both-data

# DATAFILE FORMAT
#     zone:ZONENAME:HOSTNAME:SERIAL:IP_ABC
#     ns:NAMESERVER1 NAMESERVER2
#     mx:MAILSERVER1,RR1 MAILSERVER2,RR2
#     IP_D:NAME CNAME1 CNAME2 ...
#     ...
# EXAMPLE:
#     zone:example.com:server1:43:192.168.1
#     ns:ns1 ns2
#     mx:mail,10 mail2,20
#     1:server1 ns1 mail www ftp
#     2:ns2
#     101:work1
#     102:work2
#     ...

#EXAMPLE.COM
zone:example.com:server1:41:192.168.1
ns:ns1
1:server1 www ftp mail ns1
101:work1
102:work2
103:work3

สั่งผลิตไฟล์
# d.bind-genzone -b both-data
จะได้ไฟล์โซนและไฟล์รีเวิร์สโซน ของ example.com ที่เป็นของเครือข่ายภายในออกมา
(ถ้ามีไฟล์เก่า จะสำเนาไว้ในชื่อเดิม ต่อท้ายนามสกุลด้วย .bak)

สร้างไฟล์ข้อมูลของ internal บรรจุข้อมูลของ example.org ตั้งชื่อว่า forward-data เพราะจะทำค้นชื่ออย่างเดียว ไม่ทำค้นย้อน ดังนี้
# vi forward-data

# DATAFILE FORMAT
#     zone:ZONENAME:HOSTNAME:SERIAL:IP_ABC
#     ns:NAMESERVER1 NAMESERVER2
#     mx:MAILSERVER1,RR1 MAILSERVER2,RR2
#     IP_D:NAME CNAME1 CNAME2 ...
#     ...
# EXAMPLE:
#     zone:example.com:server1:43:192.168.1
#     ns:ns1 ns2
#     mx:mail,10 mail2,20
#     1:server1 ns1 mail www ftp
#     2:ns2
#     101:work1
#     102:work2
#     ...

#EXAMPLE.ORG
zone:example.org:server1:42:192.168.1
ns:ns1
1:server1 www ns1 mail ftp

สั่งผลิตไฟล์ โดยไม่ต้องใส่ออปชั่น -b
# d.bind-genzone forward-data
จะได้ไฟล์โซน ของ example.org ที่เป็นของเครือข่ายภายใน

และสร้างไฟล์ข้อมูลของ external บรรจุข้อมูลของทุกโซน เอาไว้ใน /etc/bind/external ตั้งชื่อว่า forward-data (จะทำแค่ forward อย่างเดียว) ดังนี้
# cd /etc/bind/external
# vi forward-data

# DATAFILE FORMAT
#     zone:ZONENAME:HOSTNAME:SERIAL:IP_ABC
#     ns:NAMESERVER1 NAMESERVER2
#     mx:MAILSERVER1,RR1 MAILSERVER2,RR2
#     IP_D:NAME CNAME1 CNAME2 ...
#     ...
# EXAMPLE:
#     zone:example.com:server1:43:192.168.1
#     ns:ns1 ns2
#     mx:mail,10 mail2,20
#     1:server1 ns1 mail www ftp
#     2:ns2
#     101:work1
#     102:work2
#     ...

#EXAMPLE.COM
zone:example.com:server1:51:101.102.103
ns:ns1
104:server1 www ftp mail ns1

#EXAMPLE.ORG
zone:example.org:server1:52:101.102.103
ns:ns1
104:server1 www ns1 mail ftp

สั่งผลิตไฟล์ โดยไม่ต้องใส่ออปชั่น -b
# d.bind-genzone forward-data
จะได้เฉพาะไฟล์โซนของ example.com และ example.org ที่เป็นของเครือข่ายภายนอก

เสร็จแล้ว สั่งเริ่ม bind9 ใหม่ได้เลย
# /etc/init.d/bind9 restart

Topic: 

all-in-one 4.2 (ปรับปรุง zone generator script)

ปรับสคริปต์นิดหน่อย

# vi /usr/local/sbin/d.bind-genzone
#!/bin/bash
# GENERATE ZONE FILE [AND REVERSE ZONE FILE IF ADD OPTION -b]
# READ DATA FROM DATAFILE IN FORMAT
#     zone:ZONENAME:IP_D:CNAME1 CNAME2 ...:SERIAL:IP_ABC
#     ns:NAMESERVER1 NAMESERVER2
#     mx:MAILSERVER1,RR1 MAILSERVER2,RR2
#     IP_D:NAME CNAME1 CNAME2 ...
#     ...
# EXAMPLE:
#     zone:example.com:1:server1 ns1 mail www ftp:43:192.168.1
#     ns:ns1 ns2
#     mx:mail,10 mail2,20
#     2:ns2
#     101:work1
#     102:work2
#     ...

#GLOBAL VAR
TTL=86400
BINDUSER="bind"
BINDGROUP="bind"

#FUNTION
function usage {
    cat << EOF

Script to generate zone file and reverse zone file
USAGE: $0 [-b] DATAFILE
OPTIONS:
  -b = both, generate both forward and reverse zone file,
       only generate forward zone file, if omitted
ARGUMENT:
  DATAFILE : datafile in format:-
     zone:ZONENAME:IP_D:CNAME1 CNAME2 ...:SERIAL:IP_ABC
    [ns:NAMESERVER1 NAMESERVER2]
    [mx:MAILSERVER1,RR1 MAILSERVER2,RR2]
     IP_D:NAME CNAME1 CNAME2 ... 
     ...
  EXAMPLE OF DATAFILE:
  example1:
     zone:example.com:1:server1 ns1 mail www ftp:43:192.168.1
     ns:ns1 ns2
     mx:mail,10 mail2,20
     2:ns2
     101:work1
     102:work2
     192.168.5.1:router
     ...
  example2:
     zone:ns2.example.org:2::44:192.168.1
     ns:ns1 ns2

EOF
}

function gen_header {
    Z=$1
    S=$2
    cat << EOF
\$TTL   $TTL
@       IN      SOA $Z. root.$Z. (
                        $S      ; serial (d. adams)
                        3H      ; refresh
                        15M     ; retry
                        1W      ; expiry
                        1D )    ; minimum
EOF
}

function gen_ns {
    H=$1
    cat << EOF
@               NS      $H
EOF
}

function gen_mx {
    H=$1
    R=$2
    cat << EOF
@               MX      $R $H
EOF
}

function gen_name {
    N=$1
    I=$2
    cat << EOF
$N      IN      A       $I
EOF
}

function gen_ptr {
    I=$1
    N=$2
    cat << EOF
$I      IN      PTR     $N.$ZONENAME.
EOF
}

function gen_cname {
    C=$1
    N=$2
    cat << EOF
$C      IN      CNAME   $N.
EOF
}
function get_ip {
    I1="$1."      #input ip     
    I2="$2."      #$IP_ABC      
    A=$(echo "$I1" | cut -d. -f1)
    B=$(echo "$I1" | cut -d. -f2)
    C=$(echo "$I1" | cut -d. -f3)
    D=$(echo "$I1" | cut -d. -f4)
    W=$(echo "$I2" | cut -d. -f1)
    X=$(echo "$I2" | cut -d. -f2)
    Y=$(echo "$I2" | cut -d. -f3)
    Z=$(echo "$I2" | cut -d. -f4)
    if [ ! "$B" ]; then
        echo "$W.$X.$Y.$A"; return
    elif [ ! "$C" ]; then
        echo "$W.$X.$A.$B"; return
    elif [ ! "$D" ]; then
        echo "$W.$A.$B.$C"; return
    else
        echo "$I1"      
    fi
}

#BEGIN MAIN
DATAFILE=""
WITHREVERSE=""

while getopts "b" ARGS; do
    case "$ARGS" in
        b) WITHREVERSE="true"; shift $(($OPTIND - 1)) ;;
    esac
done

DATAFILE=$1
shift
while getopts "b" ARGS; do
    case "$ARGS" in
        b) WITHREVERSE="true"; shift $(($OPTIND - 1)) ;;
    esac
done

if [ ! "$DATAFILE" ] || [ ! -f "$DATAFILE" ]; then
    usage
    exit 1
fi

ZONENAME=""
SERIAL=""
IP_ABC=""
ZONEFILE=""
REVERSEFILE=""

cat $DATAFILE | grep -v "#" | while read DATA; do
    #ZONE HEADER
    if [ "${DATA:0:5}" == "zone:" ]; then
        ZONENAME=$(echo $DATA | cut -d: -f2)
        IP_D=$(echo $DATA | cut -d: -f3)
        CNAMES=$(echo $DATA | cut -d: -f4)
        SERIAL=$(echo $DATA | cut -d: -f5)
        IP_ABC=$(echo $DATA | cut -d: -f6)

        #FORWARD ZONE FILE
        ZONEFILE="$ZONENAME.zone"
        if [ -f "$ZONEFILE" ]; then
            mv "$ZONEFILE" "$ZONEFILE.bak"
        fi
        FULL_IP=`get_ip $IP_D $IP_ABC`
        gen_header $ZONENAME $SERIAL > $ZONEFILE
        chown $BINDUSER:$BINDGROUP $ZONEFILE
        gen_name "@" $FULL_IP >> $ZONEFILE
        for i in $CNAMES; do
            gen_cname $i $ZONENAME >> $ZONEFILE
        done

        if [ "$WITHREVERSE" ]; then
            #REVERSE ZONE FILE
            REVERSEFILE="$ZONENAME.reverse"
            if [ -f "$REVERSEFILE" ]; then
                mv "$REVERSEFILE" "$REVERSEFILE.bak"
            fi
            gen_header $ZONENAME $SERIAL > $REVERSEFILE
            chown $BINDUSER:$BINDGROUP $REVERSEFILE
            gen_ptr $IP_D "" >> $REVERSEFILE

            echo "Generate $ZONEFILE and $REVERSEFILE ..."
        else
            echo "Generate $ZONEFILE ..."
        fi

    #NS 
    elif [ "${DATA:0:3}" == "ns:" ] && [ "$ZONENAME" ]; then
        for i in $(echo $DATA | cut -d: -f2); do
            gen_ns $i >> $ZONEFILE
            if [ "$WITHREVERSE" ]; then
                gen_ns $i >> $REVERSEFILE
            fi
        done

    #MX
    elif [ "${DATA:0:3}" == "mx:" ] && [ "$ZONENAME" ]; then
        for i in $(echo $DATA | cut -d: -f2); do
            $MAILNAME=$(echo $i | cut -d, -f1)
            $RRPRIORITY=$(echo $i | cut -d, -f2)
            gen_mx $MAILNAME $RRPRIORITY >> $ $ZONEFILE
        done

    #DATA
    elif [ "$DATA" ]; then
        RAW_IP=$(echo $DATA | cut -d: -f1)
        FULL_IP=`get_ip $RAW_IP $IP_ABC`
        NAMES=$(echo $DATA | cut -d: -f2)
        FIRSTNAME=$(echo $NAMES | cut -d\  -f1)
        echo "gen_name $FIRSTNAME $FULL_IP  $ZONEFILE"
        gen_name $FIRSTNAME $FULL_IP >> $ZONEFILE
        if [ "$WITHREVERSE" ]; then
            gen_ptr $RAW_IP $FIRSTNAME >> $REVERSEFILE
        fi
        for i in $NAMES; do
            if [ "$i" != "$FIRSTNAME" ]; then
                gen_cname $i $FIRSTNAME >> $ZONEFILE
            fi
        done
    fi

done