#!/usr/bin/python

# Copyright (c) 2006 Sam Trenholme
# 
# TERMS
# 
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 
# 1. Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in the
#    documentation and/or other materials provided with the distribution.
# 
# This software is provided 'as is' with no guarantees of correctness or
# fitness for purpose.

# This Python script converts a BIND zone file in to a CSV2 zone file

# This program works as a state machine; we have various states and
# convert the input in to the output depending on what state we are in.
# This is not a pure finite state machine, but is one based on the model
# of the CSV2 zone file parser

import sys
import os
import re
import string

ERROR = -1

# We use the compiled regular expressions to determine what input we 
# are getting in a given FSM (finite state machine) state
is_space = re.compile("[ \t]")
is_newline = re.compile("[\r\n]")
is_white = re.compile("[ \t\r\n]")
is_newrec = re.compile("[A-Za-z0-9\-\_\$]")
is_dlabel = re.compile("[A-Za-z0-9\-\_]")
is_number = re.compile("[0-9]")
is_numdot = re.compile("[0-9\.]")
is_hexcolon = re.compile("[0-9a-fA-F\:]")
is_letter = re.compile("[A-Za-z]")
is_pstate = re.compile("paren$")

# This class stores various bits of information we need to look at
# or manipulate while processing the BIND zone file
class buf:
	# Constructor
	def __init__(self):
		self.l = "" # Last dlabel (used to duplicate a dlabel since
                            # BIND zone files interpret a space on a line
                            # before IN/TTL/RDTYPE as a duplicate of the 
                            # last dlabel)
		self.b = "" # Buffer (for comments and the rrtype)
		self.thisrr_comment = "" # Comment for this RR (used for
                                         # complex RR types where we need to
                                         # move comments before the RR)
		self.thisrr_bin = "" # Binary string for the current RR
                                     # (used for TXT and SPF RRs)
		self.thisrr = "" # The current record we're writing to
		self.o = "" # "Write" (for writing things)
		self.s = "" # SOA records; must be at top of CSV2 zone file
		self.n = "" # NS records; must be right after SOA records
		self.rrtype = 1 # 1: Normal RR 2: SOA RR 3: NS RR
	# Methods for the "buffer" (currently unused)
	# Add to buffer
	def ba(self, a):
		self.b += a
	# Get buffer
	def bg(self):
		return self.b
	# Reset buffer
	def br(self):
		self.b = ""
	# Methods for the current domain label for a given rr
	# Add to label
	def la(self, a):
		self.l += a
	# Get label
	def lg(self):
		return self.l
	# Reset label
	def lr(self):
		self.l = ""
	# Add a character to the current RR
	def write(self, a):
		self.thisrr += a
	# Add the current RR to the stream of normal RRs (private method)
	def owrite(self):
		self.o += self.thisrr	
		self.thisrr = ""
	# Add the current RR to the stream of SOA RRs (private method)
	def swrite(self):
		self.s += self.thisrr
		self.thisrr = ""
	# Add the current RR to the stream of NS RRs (private method)
	def nwrite(self):
		self.n += self.thisrr
		self.thisrr = ""
	# Flush the current RR, adding it to the appropiate stream, and
	# get ready to start a new RR
	def flush_rr(self): 
		if self.rrtype == 1: 
			self.owrite()
		elif self.rrtype == 2: 
			self.swrite()
		elif self.rrtype == 3: 
			self.nwrite()
		else:
			self.owrite()
		self.rrtype = 1
	# Make ths current RR a "normal" RR
	def set_normal(self):
		self.rrtype = 1
	# Make the current RR a "soa" RR
	def set_soa(self):
		self.rrtype = 2
	# Make the current RR a "ns" RR
	def set_ns(self):
		self.rrtype = 3
	# Read all of the three RR streams combined together in to a 
        # full zone file
	def read(self):
		return self.s + self.n + self.o
	# Reset the zone file (incomplete method; currently not used)
	def reset(self):
		self.o = ""
		self.s = ""
		self.n = ""

# "Output print"; we give this a short name because this function is
# called so often.  This puts a string on the output tape

def op(o,a):
	o.write(a)

# The actions we do when the finite state machine is in various
# states

# Process a comment
def process_comment(i,o):
	z = 0
	op(o,"#")
	while z < 100:
		a = i.read(1)
		if is_newline.match(a):
			return a
		else:
			op(o,a)
		z += 1
	return a
			
# Based on the rrtype they gave us, determine what state we should be
# in next

def get_rr_type(rrname):
	rrname = string.lower(rrname)
	if rrname == "in":
		return "error"
	elif rrname == "a":
		return "rr_a"
	elif rrname == "ns":
		return "rr_ns"
	elif rrname == "cname":
		return "rr_1dlabel"
	elif rrname == "ptr":
		return "rr_1dlabel"
	elif rrname == "soa":
		return "rr_soa"
	elif rrname == "mx":
		return "rr_mx"
	elif rrname == "aaaa":
		return "rr_aaaa"
	elif rrname == "srv":
		return "rr_srv"
	elif rrname == "txt":
		return "rr_txt"
	elif rrname == "spf":
		return "rr_txt"
	# Obscure RRs follow
	elif rrname == "hinfo":
		return "rr_txt" # TXT with mandatory two fields
	elif rrname == "wks":
		return "rr_wks" # Tricky variable-argument corner case
	elif rrname == "mb":
		return "rr_1dlabel"
	elif rrname == "md":
		return "rr_1dlabel"
	elif rrname == "mf":
		return "rr_1dlabel"
	elif rrname == "mg":
		return "rr_1dlabel"
	elif rrname == "mr":
		return "rr_1dlabel"
	elif rrname == "minfo":
		return "rr_2dnames"
	elif rrname == "afsdb":
		return "rr_mx"
	elif rrname == "rp":
		return "rr_2dnames"
	elif rrname == "x25":
		return "rr_txt"
	elif rrname == "isdn":
		return "rr_txt"
	elif rrname == "rt":
		return "rr_mx"
	elif rrname == "nsap":
		return "rr_hex"
	elif rrname == "px":
		return "rr_px"
	elif rrname == "gpos":
		return "rr_txt" # TXT with three mandatory fields
	elif rrname == "loc":
		return "rr_loc" # Complicated RR with variable # of fields
	else:	
		print "Error: Unknown RR " + rrname
		return "error"

# Generic handler that handles parenthesis in a zone file
def handle_paren(a,o,state,buffer):
	paren = ""
	if is_pstate.search(state):
		paren = "_paren"
	if paren == "" and is_newline.match(a):
		print "Error: Premature termination of RR"
		return ("error", "", 1, paren)
	if paren == "_paren" and a == "(":
		print "Error: Parens don't nest"
		retrun ("error", "", 1, paren)
	if paren == "" and a == "(":
		op(o," ")
		return (state + "_paren", buffer, 1, paren)
	if paren == "_paren" and a == ")":
		op(o," ")
		paren = ""
		return (state[:-6], buffer, 1, paren)
	return (state,buffer,0,paren)

# Between the rrtype and the rrdata in the zone file
def prrtype(a,o,state,buffer):
	(state, buffer, paren, pstr) = handle_paren(a,o,state,buffer)
	if paren == 1:
		return (state, buffer)
	if is_white.match(a):
		op(o,a)
		return (state,buffer)
	x = get_rr_type(buffer)
	# NS record is a 1-dlabel record we put near the top of the zone
        # file.
	if x == "rr_ns":
		x = "rr_1dlabel"
		o.set_ns()
	# SOA record is a complicated RR type we put at the top of the
        # zone file.
	if x == "rr_soa":
		o.set_soa()
	return (x + pstr, "")
		
# Between the rrdata and the next rr in the zone file
def postrr(a,o,state,buffer):
	if is_newline.match(a) and not is_pstate.search(state):
		# Flush out this RR
		op(o,a)
		o.flush_rr()
		return("pre_rr",buffer)
	if is_white.match(a):
		op(o,a)
		return (state,buffer)
	(state, buffer, paren, pstr) = handle_paren(a,o,state,buffer)
	if paren == 1:
		return (state, buffer)
	print "Error: Unexpected character after RR " + a
	return("error","")

# After the first non-escaped newline at the end of a rr in the
# zone file
def pre_rr(a,o,state,buffer):
	if is_space.match(a):
		return ("possible_lo",buffer)
	if is_white.match(a):
		op(o,a)
		return (state,buffer)
	elif is_newrec.match(a):
		if a == "$":
			print "$ commands not done yet, sorry"
			return ("dcommand","")
		else:
			op(o,a)
			o.lr()
			o.la(a)
			return("dlabel",buffer)
	print "Error: Unexpected character before RR " + a
	return("error","")

# After a space at the beginning of an RR and before a newline
def possible_lo(a,o,state,buffer):
	if is_space.match(a):
		return (state,buffer)
	if is_white.match(a):
		return ("pre_rr",buffer)
	# Output the stored dlabel from the last record
	op(o,o.lg())
	op(o," ")
	if is_number.match(a):
		op(o,"+" + a)
		return ("ttl",buffer)
	if is_letter.match(a):
		return ("rrtype",a)      

# After a space at the beginning of an RR and before the TTL/IN/RRTYPE
def post_lo(a,o,state,buffer):
	return (a,o,state,buffer);
		
# Various handlers for the rr types that we may see

# A: Internet IP address 
def rr_a(a,o,state,buffer):
	pstr = ""
	if is_pstate.search(state):
		pstr = "_paren"
	if is_newline.match(a) and not is_pstate.search(state):
		return ("postrr",buffer)
	if is_white.match(a):
		op(o,a)
		return ("postrr" + pstr,buffer)
	if is_numdot.match(a):
		op(o,a)
		return (state,buffer)	
	print "Error: Unexpected character in A RR " + a
	return("error","")

# AAAA: Ipv6 Internet IP address 
def rr_aaaa(a,o,state,buffer):
	pstr = ""
	if is_pstate.search(state):
		pstr = "_paren"
	if is_newline.match(a) and not is_pstate.search(state):
		return ("postrr",buffer)
	if is_white.match(a):
		op(o,a)
		return ("postrr" + pstr,buffer)
	if is_hexcolon.match(a):
		op(o,a)
		return (state,buffer)	
	print "Error: Unexpected character in A RR " + a
	return("error","")

# Generic handler for NS records or anything else that has a single dlabel
def rr_1dlabel(a,o,state,buffer):
	pstr = ""
	if is_pstate.search(state):
		pstr = "_paren"
	if is_newline.match(a) and not is_pstate.search(state):
		return ("postrr",buffer)
	if is_white.match(a):
		# I think BIND zone files require a final dot in dlabels
                # also, or will append the domain name.  If not, this
                # bit of code will change to not have the '+ ".%"'
		op(o,a + ".%")
		return ("postrr" + pstr,buffer)
	if is_dlabel.match(a):
		op(o,a)
		return (state,buffer)
	if a == ".":
		op(o,a)
		return ("rr_1dlabel_dot" + pstr, buffer)
	print "Error: Unexpected character in RR " + a
	return("error","")
	
def rr_1dlabel_dot(a,o,state,buffer):
	pstr = ""
	if is_pstate.search(state):
		pstr = "_paren"
	if is_newline.match(a) and not is_pstate.search(state):
		return ("postrr",buffer)
	# End of RR after dot
	if is_white.match(a):
		op(o,a)
		return ("postrr" + pstr,buffer)
	if is_dlabel.match(a):
		op(o,a)
		return ("rr_1dlabel" + pstr,buffer)
	print "Error: Unexpected character in RR " + a
	return("error","")

# Generic handlers for dlabels and numbers

# In a dot in a dlabel
def rr_generic_dlabel_dot(a,o,state,buffer,next):
	pstr = ""
	if is_pstate.search(state):
		pstr = "_paren"
	if is_white.match(a):
		op(o,a)
		return(next + pstr, buffer)
	if is_dlabel.match(a):  
		op(o,a)
		if pstr == "_paren":
			return(state[:-10] + pstr, buffer)
		return (state[:-4], buffer)
	print "Error: unexpected character in RR dlabel dot " + a
	return("error","")

# In the whitespace before a dlabel		
def rr_generic_dlabel_pre(a,o,state,buffer,next):
	(state, buffer, paren, pstr) = handle_paren(a,o,state,buffer)
	if paren == 1:
		return(state, buffer)
	if is_white.match(a):
		op(o,a)
		return(state, buffer)
	if is_dlabel.match(a):	
		op(o,a)
		return (next + pstr, buffer)
	print "Error: unexpected character before RR dlabel " + a
	return("error","")

# In a dlabel
def rr_generic_dlabel(a,o,state,buffer,next):
	pstr = ""
	if is_pstate.search(state):
		pstr = "_paren"
	if is_white.match(a):
		op(o,".%" + a)
		return(next + pstr, buffer)
	if is_dlabel.match(a):	
		op(o,a)
		return (state, buffer)
	if a == ".":	
		op(o,a)
		if pstr == "_paren":
			return(state[:-10] + "_dot" + pstr, buffer)
		return (state + "_dot", buffer)
	print "Error: unexpected character in RR dlabel " + a
	return("error","")

# In the whitespace before a number		
def rr_generic_number_pre(a,o,state,buffer,next):
	(state, buffer, paren, pstr) = handle_paren(a,o,state,buffer)
	if paren == 1:
		return(state, buffer)
	if is_white.match(a):
		op(o,a)
		return(state, buffer)
	if is_number.match(a):	
		op(o,a)
		return (next + pstr, buffer)
	print "Error: unexpected character before RR number field " + a
	return("error","")

# In a number
def rr_generic_number(a,o,state,buffer,next):
	pstr = ""
	if is_pstate.search(state):
		pstr = "_paren"
	if is_white.match(a):
		op(o,a)
		return(next + pstr, buffer)
	if is_number.match(a):	
		op(o,a)
		return (state, buffer)
	print "Error: unexpected character in numer field of RR " + a
	return("error","")

# SOA: Start of authority record (2 dlabels, 5 numbers)
def rr_soa(a,o,state,buffer):
	return rr_generic_dlabel(a,o,state,buffer,"rr_soa_2_pre")

def rr_soa_dot(a,o,state,buffer):
	return rr_generic_dlabel_dot(a,o,state,buffer,"rr_soa_2_pre")

def rr_soa_2_pre(a,o,state,buffer):
	return rr_generic_dlabel_pre(a,o,state,buffer,"rr_soa_2")

def rr_soa_2(a,o,state,buffer):
	return rr_generic_dlabel(a,o,state,buffer,"rr_soa_3_pre")

def rr_soa_2_dot(a,o,state,buffer):
	return rr_generic_dlabel_dot(a,o,state,buffer,"rr_soa_3_pre")

def rr_soa_3_pre(a,o,state,buffer):
	return rr_generic_number_pre(a,o,state,buffer,"rr_soa_3")

def rr_soa_3(a,o,state,buffer):
	return rr_generic_number(a,o,state,buffer,"rr_soa_4_pre")

def rr_soa_4_pre(a,o,state,buffer):
	return rr_generic_number_pre(a,o,state,buffer,"rr_soa_4")

def rr_soa_4(a,o,state,buffer):
	return rr_generic_number(a,o,state,buffer,"rr_soa_5_pre")

def rr_soa_5_pre(a,o,state,buffer):
	return rr_generic_number_pre(a,o,state,buffer,"rr_soa_5")

def rr_soa_5(a,o,state,buffer):
	return rr_generic_number(a,o,state,buffer,"rr_soa_6_pre")

def rr_soa_6_pre(a,o,state,buffer):
	return rr_generic_number_pre(a,o,state,buffer,"rr_soa_6")

def rr_soa_6(a,o,state,buffer):
	return rr_generic_number(a,o,state,buffer,"rr_soa_7_pre")

def rr_soa_7_pre(a,o,state,buffer):
	return rr_generic_number_pre(a,o,state,buffer,"rr_soa_7")

def rr_soa_7(a,o,state,buffer):
	return rr_generic_number(a,o,state,buffer,"postrr")

# MX: Mail exchange record (1 number, 1 dlabel)
def rr_mx(a,o,state,buffer):
	return rr_generic_number(a,o,state,buffer,"rr_mx_2_pre")

def rr_mx_pre(a,o,state,buffer):
	return rr_generic_number_pre(a,o,state,buffer,"rr_mx")

def rr_mx_2_pre(a,o,state,buffer):
	return rr_generic_dlabel_pre(a,o,state,buffer,"rr_mx_2")

def rr_mx_2(a,o,state,buffer):
	return rr_generic_dlabel(a,o,state,buffer,"postrr")

def rr_mx_2_dot(a,o,state,buffer):
	return rr_generic_dlabel_dot(a,o,state,buffer,"postrr")

# SRV record (3 numbers, 1 dlabel)
def rr_srv(a,o,state,buffer):
	return rr_generic_number(a,o,state,buffer,"rr_srv_2_pre")

def rr_srv_2_pre(a,o,state,buffer):
	return rr_generic_number_pre(a,o,state,buffer,"rr_srv_2")

# Since the last fields of a SRV record are identical to a MX
# record, we can simply jump to the MX record after the second
# SRV field
def rr_srv_2(a,o,state,buffer):
	return rr_generic_number(a,o,state,buffer,"rr_mx_pre")

# In rrtype
def rrtype(a,o,state,buffer):
	(state, buffer, paren, pstr) = handle_paren(a,o,state,buffer)
	if paren == 1:
		return (state, buffer)
	if is_dlabel.match(a):
		return (state,buffer + a)
	if is_white.match(a):
		# In the case of "IN", we go back to the state of being
                # after the dlabel, where we look for either a TTL or a 
                # RRTYPE
		if string.lower(buffer) != "in":
			op(o,buffer)
		else:
			op(o,a)
			return("pdlabel" + pstr, "")
		op(o,a)
		return ("prrtype" + pstr, buffer)
	print "Invalid character in RR type " + a
	return ("error","")

# In TTL
def ttl(a,o,state,buffer):
	(state, buffer, paren, pstr) = handle_paren(a,o,state,buffer)
	if paren == 1:
		return (state, buffer)
	if is_white.match(a):
		op(o,a)
		return ("pdlabel" + pstr, buffer)
	if is_number.match(a):
		op(o,a)
		return ("ttl" + pstr,buffer)
	print "Unexpected character in TTL " + a
	return("error","")

# After dlabel (name of record we have info for) and before TTL/rrtype
def pdlabel(a,o,state,buffer):
	(state, buffer, paren, pstr) = handle_paren(a,o,state,buffer)
	if paren == 1:
		return (state, buffer)
	if is_white.match(a):
		op(o,a)
		return (state, buffer)
	if is_number.match(a):
		op(o,"+" + a)
		return ("ttl" + pstr,buffer)
	if is_letter.match(a):
		return ("rrtype" + pstr,a)	
	print "Unexpected character after dlabel " + a
	return("error","")

# In a "." character in the dlabel (name of machine we have record for)	
def dlabel_dot(a,o,state,buffer):
	if is_dlabel.match(a):
		op(o,a)
		o.la(a)
		return ("dlabel",buffer)
	elif is_space.match(a):
		op(o,a)
		return ("pdlabel",buffer)
	else:
		print "Unexpected chatacter near dlabel " + a
		return ("error","")

# In the dlabel (name of machine we are looking at record for)
def dlabel_s(a,o,state,buffer):
	if is_dlabel.match(a):
		op(o,a)
		o.la(a)
		return (state,buffer)
	elif is_space.match(a):
		op(o,".%" + a)
		o.la(".%" + a)
		return ("pdlabel",buffer)
	elif a == ".":
		op(o,a)
		o.la(a)
		return ("dlabel_dot",buffer)
	else:
		print "Unexpected character in dlabel " + a
		return ("error","")	

# Initial state at beginning of file				
def init_s(a,o,state,buffer):
	if is_white.match(a):
		op(o,a)
		return (state,buffer)
	elif is_newrec.match(a):
		if a == "$":
			print "$ commands not done yet, sorry"
			return ("dcommand","")
		else:
			op(o,a)
			o.la(a)
			return("dlabel",buffer)
	else:
		print "Error: Unexpected character at new file start " + a
		return("error","")
		
# END machine states

# This routine goes to the appropriate routine for the state we are in
def process_char(a,o,state,buffer):
	# print "Char is " + a + " state is " + state # DEBUG
	if state == "init_state":
		(state, buffer) = init_s(a,o,state,buffer)
	elif state == "dlabel":
		(state,buffer) = dlabel_s(a,o,state,buffer)
	elif state == "dlabel_dot":
		(state,buffer) = dlabel_dot(a,o,state,buffer)
	elif state == "pdlabel" or state == "pdlabel_paren":
		(state,buffer) = pdlabel(a,o,state,buffer)
	elif state == "ttl" or state == "ttl_paren":
		(state,buffer) = ttl(a,o,state,buffer)
	elif state == "rrtype" or state == "rrtype_paren":
		(state,buffer) = rrtype(a,o,state,buffer)
	elif state == "prrtype" or state == "prrtype_paren":
		(state,buffer) = prrtype(a,o,state,buffer)
	elif state == "possible_lo":
		(state,buffer) = possible_lo(a,o,state,buffer)
	# We put an if instead of an elif here because prrtype, when it 
        # hits the first character of the rr, "falls through" since 
        # what we do with the character we see depends on the rrtype
	
	# The A RR
	if state == "rr_a" or state == "rr_a_paren":
		(state,buffer) = rr_a(a,o,state,buffer)

	# Generic handlers for 1 dlabel RRs: NS, CNAME, PTR, and the
	# obscure MB, MD, MF, MG, and MR records
	elif state == "rr_1dlabel" or state == "rr_1dlabel_paren":
		(state,buffer) = rr_1dlabel(a,o,state,buffer)
	elif state == "rr_1dlabel_dot" or state == "rr_1dlabel_dot_paren":
		(state,buffer) = rr_1dlabel_dot(a,o,state,buffer)

	# The SOA RRs
	elif state == "rr_soa" or state == "rr_soa_paren":
		(state,buffer) = rr_soa(a,o,state,buffer)
	elif state == "rr_soa_dot" or state == "rr_soa_dot_paren":
		(state,buffer) = rr_soa_dot(a,o,state,buffer)
	elif state == "rr_soa_2_pre" or state == "rr_soa_2_pre_paren":
		(state,buffer) = rr_soa_2_pre(a,o,state,buffer)
	elif state == "rr_soa_2" or state == "rr_soa_2_paren":
		(state,buffer) = rr_soa_2(a,o,state,buffer)
	elif state == "rr_soa_2_dot" or state == "rr_soa_2_dot_paren":
		(state,buffer) = rr_soa_2_dot(a,o,state,buffer)
	elif state == "rr_soa_3_pre" or state == "rr_soa_3_pre_paren":
		(state,buffer) = rr_soa_3_pre(a,o,state,buffer)
	elif state == "rr_soa_3" or state == "rr_soa_3_paren":
		(state,buffer) = rr_soa_3(a,o,state,buffer)
	elif state == "rr_soa_4_pre" or state == "rr_soa_4_pre_paren":
		(state,buffer) = rr_soa_4_pre(a,o,state,buffer)
	elif state == "rr_soa_4" or state == "rr_soa_4_paren":
		(state,buffer) = rr_soa_4(a,o,state,buffer)
	elif state == "rr_soa_5_pre" or state == "rr_soa_5_pre_paren":
		(state,buffer) = rr_soa_5_pre(a,o,state,buffer)
	elif state == "rr_soa_5" or state == "rr_soa_5_paren":
		(state,buffer) = rr_soa_5(a,o,state,buffer)
	elif state == "rr_soa_6_pre" or state == "rr_soa_6_pre_paren":
		(state,buffer) = rr_soa_6_pre(a,o,state,buffer)
	elif state == "rr_soa_6" or state == "rr_soa_6_paren":
		(state,buffer) = rr_soa_6(a,o,state,buffer)
	elif state == "rr_soa_7" or state == "rr_soa_7_paren":
		(state,buffer) = rr_soa_7(a,o,state,buffer)
	elif state == "rr_soa_7_pre" or state == "rr_soa_7_pre_paren":
		(state,buffer) = rr_soa_7_pre(a,o,state,buffer)

	# The MX RR (and also the obscure AFSDB and RT RRs)
	elif state == "rr_mx" or state == "rr_mx_paren":
		(state,buffer) = rr_mx(a,o,state,buffer)
	elif state == "rr_mx_2_pre" or state == "rr_mx_2_pre_paren":
		(state,buffer) = rr_mx_2_pre(a,o,state,buffer)
	elif state == "rr_mx_2" or state == "rr_mx_2_paren":
		(state,buffer) = rr_mx_2(a,o,state,buffer)
	elif state == "rr_mx_2_dot" or state == "rr_mx_2_dot_paren":
		(state,buffer) = rr_mx_2_dot(a,o,state,buffer)
	# We have a rr_mx_pre handler because the SRV record
        # uses the MX handler for the last two fields
	elif state == "rr_mx_pre" or state == "rr_mx_pre_paren":
		(state,buffer) = rr_mx(a,o,state,buffer)

	# The SRV RR
	elif state == "rr_srv" or state == "rr_srv_paren":
		(state,buffer) = rr_srv(a,o,state,buffer)
	elif state == "rr_srv_2" or state == "rr_srv_2_paren":
		(state,buffer) = rr_srv_2(a,o,state,buffer)
	elif state == "rr_srv_2_pre" or state == "rr_srv_2_pre_paren":
		(state,buffer) = rr_srv_2_pre(a,o,state,buffer)

	# The AAAA RR
	elif state == "rr_aaaa" or state == "rr_aaaa_paren":
		(state,buffer) = rr_aaaa(a,o,state,buffer)

	if state == "postrr" or state == "postrr_paren":
		(state,buffer) = postrr(a,o,state,buffer)
	elif state == "pre_rr":
		(state,buffer) = pre_rr(a,o,state,buffer)

	return (state, buffer)

# Process a given file; this allows this program to process multiple
# BIND zone files at the same time
def process_file(filename):
	if os.access(filename,os.R_OK) != 1:
		print "Can not read " + filename
		return ERROR
	
	outf = filename + ".csv2"

	if os.path.isfile(outf) == 1:
		print "Warning: " + outf + " already exists, overwriting"

	if os.path.isdir(filename) == 1:
		print "Error: " + outf + " is a directory, skipping"
		return ERROR

	i = file(filename,"rb")
	o = file(outf,"wb")
	oz = buf()

	state = "init_state"
	buffer = ""

	x = 0
	linenum = 1
	while x < 100000:
		a = i.read(1)
		if a == "":
			break
		if a == ";":
			a = process_comment(i,oz)
		if a == "\n":
			linenum += 1
		(state, buffer) = process_char(a,oz,state,buffer)
		if state == "error" or state == "error_paren":
			print "Error found, no longer processing this file"
			print "Error is on line " + str(linenum) ,
			print "of file " + filename
			return ERROR
		x += 1


	o.write(
"""# This MaraDNS CSV2 zone file was converted from a BIND zone file by
# the bind2csv2.py tool

""")
	oz.flush_rr() # Make sure to flush out the last RR we read
	o.write(oz.read())

	print outf + " written"


# MAIN
	
#process_file("test.zonefile")
print "This will eventually become the tool that will convert"
print "BIND zone files in to MaraDNS csv2 zone files.  It is right now a"
print "work in progress."


