1#!/usr/bin/env python
2# Copyright (c) 2007, Secure64 Software Corporation
3#
4# Permission is hereby granted, free of charge, to any person obtaining a copy
5# of this software and associated documentation files (the "Software"), to deal
6# in the Software without restriction, including without limitation the rights
7# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8# copies of the Software, and to permit persons to whom the Software is
9# furnished to do so, subject to the following conditions:
10#
11# The above copyright notice and this permission notice shall be included in
12# all copies or substantial portions of the Software.
13#
14# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20# THE SOFTWARE.
21#
22#
23#
24#	class containing the parser for BIND configuration files
25#
26#
27
28import os
29import os.path
30import sys
31
32if os.path.exists('../bind2nsd/Config.py'):
33   sys.path.append('../bind2nsd')
34   from NamedConf import *
35   from Zone import *
36   from Key import *
37   from Tokenizer import *
38   from Utils import *
39else:
40   from bind2nsd.NamedConf import *
41   from bind2nsd.Zone import *
42   from bind2nsd.Key import *
43   from bind2nsd.Tokenizer import *
44   from bind2nsd.Utils import *
45
46
47#-- the following options {} clauses are currently currently ignored,
48#   typically because they are not implemented or not applicable to NSD
49IGNORED_OPTIONS = [ 'cleaning-interval',
50                    'datasize',
51                    'interface-interval',
52		    'max-cache-size',
53                    'stacksize',
54                  ]
55
56class Parser:
57
58   def __init__(self, named):
59      self.named = named
60      return
61
62   def handle_include(self, tokens):
63      (ttype, val, curline) = tokens.get()
64      if ttype == T_STRING:
65         fname = val.replace('"', '')
66         self.named.includes.append(fname)
67         report_info('   include file "%s"...' % (fname))
68         if os.path.exists(fname):
69            self.parse(Tokenizer(fname))
70         else:
71            report_error('? missing include file "%s"' % (fname))
72      else:
73         bail('? dude.  where is the filename string after "include"?')
74      (ttype, val, curline) = tokens.get()
75      if ttype != T_SEMI:
76         bail('? need a semicolon to end the "include" (%d: %s)' % (ttype, val))
77      return
78
79   def handle_key(self, tokens):
80      (ttype, val, curline) = tokens.get()
81      if ttype == T_STRING:
82         name = val.replace('"', '')
83         key = Key(name)
84         (ttype, val, curline) = tokens.get()
85         if ttype != T_LBRACE:
86            bail('? need opening brace for "key" defn (%d: %s)' % (ttype, val))
87         (ttype, val, curline) = tokens.get()
88         while ttype != T_RBRACE:
89            if ttype == T_KEYWORD and val == 'algorithm':
90               (ttype, val, curline) = tokens.get()
91 	       if ttype == T_KEYWORD or ttype == T_NAME:
92	          key.setAlgorithm(val)
93	       else:
94	          bail('? bogosity after "algorithm" (%d: %s)' % (ttype, val))
95	    elif ttype == T_KEYWORD and val == 'secret':
96	       (ttype, val, curline) = tokens.get()
97	       if ttype == T_STRING:
98	          s = val.replace('"','')
99		  key.setSecret(s)
100	       else:
101	          bail('? bogosity after "secret" (%d: %s)' % (ttype, val))
102	    elif ttype == T_SEMI:
103	       pass
104	    (ttype, val, curline) = tokens.get()
105      else:
106         bail('? need to have a string after "key" (%d: %s)' % (ttype, val))
107
108      self.named.addKey(key)
109      (ttype, val, curline) = tokens.get()
110      if ttype != T_SEMI:
111         bail('? need a semicolon to end the "key" (%d: %s)' % (ttype, val))
112      return
113
114   def handle_server(self, tokens):
115      (ttype, val, curline) = tokens.get()
116      if ttype == T_QUAD:
117         ipaddr = val
118         (ttype, val, curline) = tokens.get()
119         if ttype != T_LBRACE:
120            bail('? need opening brace for "server" (%d: %s)' % (ttype, val))
121         (ttype, val, curline) = tokens.get()
122         while ttype != T_RBRACE:
123            if ttype == T_KEYWORD and val == 'keys':
124               (ttype, val, curline) = tokens.get()
125 	       if ttype == T_LBRACE:
126	          nlist = self.name_list(val, tokens)
127		  for ii in nlist:
128		     self.named.keys[ii].addIpAddr(ipaddr)
129	       else:
130	          bail('? bogosity after "keys" (%d: %s)' % (ttype, val))
131	    (ttype, val, curline) = tokens.get()
132      else:
133         bail('? need to have an IP address after "server" (%d: %s)' % \
134	      (ttype, val))
135
136      (ttype, val, curline) = tokens.get()
137      if ttype != T_SEMI:
138         bail('? need a semicolon to end the "server" (%d: %s)' % (ttype, val))
139      return
140
141   def name_list(self, name, tokens):
142      nlist = []
143      (ttype, val, curline) = tokens.get()
144      if ttype == T_LBRACE or ttype == T_LPAREN:
145         (ttype, val, curline) = tokens.get()
146         while ttype != T_RBRACE and ttype != T_RPAREN:
147            if ttype == T_NAME:
148	       nlist.append(val)
149            elif ttype == T_SEMI or ttype == T_COMMENT:
150               pass
151            else:
152               bail('? was expecting a name in "%s" (%d, %d: %s)' % \
153	            (name, ttype, curline, val.strip()))
154            (ttype, val, curline) = tokens.get()
155         (ttype, val, curline) = tokens.get()
156         if ttype != T_SEMI:
157            bail('? junk after closing brace of "%s" section' % (name))
158
159      return nlist
160
161   def quad_list(self, name, tokens):
162      qlist = []
163      (ttype, val, curline) = tokens.get()
164      if ttype == T_LBRACE or ttype == T_LPAREN:
165         (ttype, val, curline) = tokens.get()
166         while ttype != T_RBRACE and ttype != T_RPAREN:
167            if ttype == T_QUAD:
168	       qlist.append(val)
169            elif ttype == T_SEMI or ttype == T_COMMENT:
170               pass
171            else:
172               bail('? was expecting a quad in "%s" (%d, %d: %s)' % \
173	            (name, ttype, curline, val.strip()))
174            (ttype, val, curline) = tokens.get()
175         (ttype, val, curline) = tokens.get()
176         if ttype != T_SEMI:
177            bail('? junk after closing brace of "%s" section' % (name))
178
179      return qlist
180
181   def quad_list_with_keys(self, name, tokens, named):
182      qlist = []
183      (ttype, val, curline) = tokens.get()
184      if ttype == T_LBRACE or ttype == T_LPAREN:
185         (ttype, val, curline) = tokens.get()
186         while ttype != T_RBRACE and ttype != T_RPAREN:
187            if ttype == T_QUAD:
188	       qlist.append(val)
189            elif ttype == T_SEMI or ttype == T_COMMENT:
190               pass
191	    elif ttype == T_KEYWORD and val == 'key':
192               (ttype, val, curline) = tokens.get()
193	       if ttype == T_NAME:
194	          if val in named.getKeys():
195		     qlist.append(val)
196	       else:
197                  bail('? was expecting a key name in "%s" (%d, %d: %s)' % \
198	               (name, ttype, curline, val.strip()))
199            else:
200               bail('? was expecting a quad in "%s" (%d, %d: %s)' % \
201	            (name, ttype, curline, val.strip()))
202            (ttype, val, curline) = tokens.get()
203         (ttype, val, curline) = tokens.get()
204         if ttype != T_SEMI:
205            bail('? junk after closing brace of "%s" section' % (name))
206
207      return qlist
208
209   def skip_value(self, name, tokens):
210      (ttype, val, curline) = tokens.get()
211      if ttype == T_NAME or ttype == T_STRING:
212         pass
213      else:
214         bail('? need something after "%s" keyword (%d: %s)' %
215	      (name, ttype, val))
216      return
217
218   def handle_options(self, tokens):
219      report_info('=> processing server options')
220      optsDone = False
221      depth = 0
222      (ttype, val, curline) = tokens.get()
223      if ttype == T_LBRACE:
224         depth += 1
225      else:
226         bail('? urk.  bad info after "options" keyword.')
227      (ttype, val, curline) = tokens.get()
228
229      while not optsDone:
230         if ttype == T_KEYWORD and val == 'directory':
231            (ttype, val, curline) = tokens.get()
232            if ttype == T_STRING:
233               self.named.options.setDirectory(val)
234	    else:
235               bail('? need string after "directory" keyword')
236
237	 elif ttype == T_KEYWORD and val == 'dump-file':
238	    (ttype, val, curline) = tokens.get()
239	    if ttype == T_STRING:
240	       self.named.options.setDumpFile(val)
241	    else:
242	       bail('? need string after "dump-file" keyword')
243
244	 elif ttype == T_KEYWORD and val == 'pid-file':
245	    (ttype, val, curline) = tokens.get()
246	    if ttype == T_STRING:
247	       self.named.options.setPidFile(val)
248	    else:
249	       bail('? need string after "pid-file" keyword')
250
251	 elif ttype == T_KEYWORD and val == 'coresize':
252	    (ttype, val, curline) = tokens.get()
253	    if ttype == T_NAME or ttype == T_KEYWORD:
254	       self.named.options.setCoresize(val)
255	    else:
256	       bail('? need name after "coresize" keyword (%d: %s)' % \
257	            (ttype, val))
258
259	 elif ttype == T_KEYWORD and val == 'version':
260	    (ttype, val, curline) = tokens.get()
261	    if ttype == T_STRING:
262	       self.named.options.setVersion(val)
263	    else:
264	       bail('? need string after "version" keyword')
265
266	 elif ttype == T_KEYWORD and val == 'transfer-format':
267	    (ttype, val, curline) = tokens.get()
268	    if ttype == T_NAME or ttype == T_KEYWORD:
269	       self.named.options.setTransferFormat(val)
270	    else:
271	       bail('? need name after "transfer-format" keyword (%d: %s)' % \
272	            (ttype, val))
273
274         elif ttype == T_KEYWORD and val == 'statistics-file':
275            (ttype, val, curline) = tokens.get()
276            if ttype == T_STRING:
277               self.named.options.setStatisticsFile(val)
278            else:
279  	       bail('? need string after "statistics-file" keyword')
280
281	 elif ttype == T_KEYWORD and val == 'allow-transfer':
282	    qlist = self.quad_list_with_keys(val, tokens, self.named)
283	    self.named.options.setAllowTransfer(qlist)
284
285	 elif ttype == T_KEYWORD and val == 'allow-notify':
286	    qlist = self.quad_list(val, tokens)
287	    self.named.options.setAllowNotify(qlist)
288
289	 elif ttype == T_KEYWORD and val == 'also-notify':
290	    qlist = self.quad_list(val, tokens)
291	    self.named.options.setAlsoNotify(qlist)
292
293	 elif ttype == T_KEYWORD and val == 'allow-recursion':
294	    qlist = self.quad_list(val, tokens)
295	    self.named.options.setAllowRecursion(qlist)
296
297         elif ttype == T_KEYWORD and val == 'check-names':
298            (ttype, val, curline) = tokens.get()
299            info = []
300            while ttype != T_SEMI:
301  	       info.append(val)
302               (ttype, val, curline) = tokens.get()
303            self.named.options.addCheckNames(' '.join(info))
304
305         elif ttype == T_KEYWORD and val in IGNORED_OPTIONS:
306	    self.skip_value(val, tokens)
307
308	 elif ttype == T_KEYWORD and val == 'recursive-clients':
309	    (ttype, val, curline) = tokens.get()
310	    if ttype == T_QUAD:		# well, it's a number, actually...
311	       self.named.options.setRecursiveClients(val)
312	    else:
313	       bail('? need value after "recursive-clients" keyword (%d: %s)'
314	            % (ttype, val))
315
316         elif ttype == T_RBRACE:
317            optDone = True
318            break
319         (ttype, val, curline) = tokens.get()
320
321      (ttype, val, curline) = tokens.get()
322      if ttype != T_SEMI:
323         bail('? um, junk after closing brace of "options" section')
324
325      return
326
327   def handle_zone(self, tokens):
328      (ttype, val, curline) = tokens.get()
329      if ttype != T_STRING:
330         bail('? expected string for zone name (%d: %s)' % (ttype, val))
331      zname = val.replace('"','')
332      zone = Zone(zname)
333
334      (ttype, val, curline) = tokens.get()
335      if ttype == T_KEYWORD and (val == 'in' or val == 'IN'):
336         (ttype, val, curline) = tokens.get()
337      if ttype == T_LBRACE or ttype == T_LPAREN:
338         (ttype, val, curline) = tokens.get()
339         while ttype != T_RBRACE and ttype != T_RPAREN:
340            if ttype == T_KEYWORD and val == 'type':
341               (ttype, val, curline) = tokens.get()
342  	       info = []
343  	       while ttype == T_KEYWORD or ttype == T_NAME:
344  	          info.append(val)
345                  (ttype, val, curline) = tokens.get()
346  	       zone.setType(' '.join(info))
347
348            elif ttype == T_KEYWORD and val == 'file':
349               (ttype, val, curline) = tokens.get()
350	       if ttype != T_STRING:
351	          bail('? eh?  no string for zone file name? (%d: %s)' % \
352		       (ttype, val))
353	       fname = val.replace('"','')
354	       zone.setFile(fname)
355
356	    elif ttype == T_KEYWORD and val == 'masters':
357	       qlist = self.quad_list(val, tokens)
358	       zone.setMasters(qlist)
359
360	    elif ttype == T_KEYWORD and val == 'allow-transfer':
361	       qlist = self.quad_list_with_keys(val, tokens, self.named)
362	       zone.setAllowTransfers(qlist)
363
364	    elif ttype == T_KEYWORD and val == 'also-notify':
365	       qlist = self.quad_list(val, tokens)
366	       zone.setAlsoNotify(qlist)
367
368	    elif ttype == T_SEMI:
369	       pass
370
371	    else:
372	       bail('? bugger.  do NOT grok "%s" yet (%d: %s @ line %s)' % \
373	            (val, ttype, val, curline))
374
375	    (ttype, val, curline) = tokens.get()
376
377	 self.named.addZone(zone)
378
379      else:
380         bail('? expected left brace to start zone definition (%d: %s)' % \
381	      (ttype, val))
382
383      (ttype, val, curline) = tokens.get()
384      if ttype != T_SEMI:
385         bail('? need a semicolon to end the "zone" (%d: %s)' % (ttype, val))
386
387      return
388
389   def ignore_section(self, name, tokens):
390      report_info('=> ignoring %s' % (name))
391      done = False
392      depth = 0
393      (ttype, val, curline) = tokens.get()
394      if ttype == T_LBRACE:
395         depth += 1
396      else:
397         bail('? urk.  bad info after "%s" keyword.' % (name))
398      (ttype, val, curline) = tokens.get()
399      while not done:
400         if ttype == T_LBRACE:
401            depth += 1
402         elif ttype == T_RBRACE:
403            depth -= 1
404            if depth == 0:
405               break
406            else:
407               pass
408         (ttype, val, curline) = tokens.get()
409      (ttype, val, curline) = tokens.get()
410      if ttype != T_SEMI:
411         bail('? um, junk after closing brace of "%s" section' % (name))
412      return
413
414   def parse(self, tokens):
415      (ttype, val, curline) = tokens.get()
416      while ttype != T_EOF:
417         if ttype == T_COMMENT:
418            pass
419
420         elif ttype == T_KEYWORD and val == 'controls':
421	    self.ignore_section(val, tokens)
422
423         elif ttype == T_KEYWORD and val == 'key':
424	    self.handle_key(tokens)
425
426         elif ttype == T_KEYWORD and val == 'include':
427	    self.handle_include(tokens)
428
429         elif ttype == T_KEYWORD and val == 'logging':
430	    self.ignore_section(val, tokens)
431
432         elif ttype == T_KEYWORD and val == 'lwres':
433	    self.ignore_section(val, tokens)
434
435         elif ttype == T_KEYWORD and val == 'options':
436	    self.handle_options(tokens)
437
438         elif ttype == T_KEYWORD and val == 'server':
439	    self.handle_server(tokens)
440
441         elif ttype == T_KEYWORD and val == 'trusted-keys':
442	    self.ignore_section(val, tokens)
443
444         elif ttype == T_KEYWORD and val == 'view':
445	    self.ignore_section(val, tokens)
446
447         elif ttype == T_KEYWORD and val == 'zone':
448	    self.handle_zone(tokens)
449
450         else:
451            bail('? ew.  what _is_ this? -> %2d: %s @ line %d' \
452	         % (ttype, val, curline))
453         (ttype, val, curline) = tokens.get()
454
455      return
456
457