001: // Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)
002:
003: package org.xbill.DNS;
004:
005: import java.io.*;
006: import java.util.*;
007:
008: /**
009: * A DNS master file parser. This incrementally parses the file, returning
010: * one record at a time. When directives are seen, they are added to the
011: * state and used when parsing future records.
012: *
013: * @author Brian Wellington
014: */
015:
016: public class Master {
017:
018: private Name origin;
019: private File file;
020: private Record last = null;
021: private long defaultTTL;
022: private Master included = null;
023: private Tokenizer st;
024: private int currentType;
025: private int currentDClass;
026: private long currentTTL;
027: private boolean needSOATTL;
028:
029: private Generator generator;
030: private List generators;
031: private boolean noExpandGenerate;
032:
033: Master(File file, Name origin, long initialTTL) throws IOException {
034: if (origin != null && !origin.isAbsolute()) {
035: throw new RelativeNameException(origin);
036: }
037: this .file = file;
038: st = new Tokenizer(file);
039: this .origin = origin;
040: defaultTTL = initialTTL;
041: }
042:
043: /**
044: * Initializes the master file reader and opens the specified master file.
045: * @param filename The master file.
046: * @param origin The initial origin to append to relative names.
047: * @param ttl The initial default TTL.
048: * @throws IOException The master file could not be opened.
049: */
050: public Master(String filename, Name origin, long ttl)
051: throws IOException {
052: this (new File(filename), origin, ttl);
053: }
054:
055: /**
056: * Initializes the master file reader and opens the specified master file.
057: * @param filename The master file.
058: * @param origin The initial origin to append to relative names.
059: * @throws IOException The master file could not be opened.
060: */
061: public Master(String filename, Name origin) throws IOException {
062: this (new File(filename), origin, -1);
063: }
064:
065: /**
066: * Initializes the master file reader and opens the specified master file.
067: * @param filename The master file.
068: * @throws IOException The master file could not be opened.
069: */
070: public Master(String filename) throws IOException {
071: this (new File(filename), null, -1);
072: }
073:
074: /**
075: * Initializes the master file reader.
076: * @param in The input stream containing a master file.
077: * @param origin The initial origin to append to relative names.
078: * @param ttl The initial default TTL.
079: */
080: public Master(InputStream in, Name origin, long ttl) {
081: if (origin != null && !origin.isAbsolute()) {
082: throw new RelativeNameException(origin);
083: }
084: st = new Tokenizer(in);
085: this .origin = origin;
086: defaultTTL = ttl;
087: }
088:
089: /**
090: * Initializes the master file reader.
091: * @param in The input stream containing a master file.
092: * @param origin The initial origin to append to relative names.
093: */
094: public Master(InputStream in, Name origin) {
095: this (in, origin, -1);
096: }
097:
098: /**
099: * Initializes the master file reader.
100: * @param in The input stream containing a master file.
101: */
102: public Master(InputStream in) {
103: this (in, null, -1);
104: }
105:
106: private Name parseName(String s, Name origin)
107: throws TextParseException {
108: try {
109: return Name.fromString(s, origin);
110: } catch (TextParseException e) {
111: throw st.exception(e.getMessage());
112: }
113: }
114:
115: private void parseTTLClassAndType() throws IOException {
116: String s;
117: boolean seen_class = false;
118:
119: // This is a bit messy, since any of the following are legal:
120: // class ttl type
121: // ttl class type
122: // class type
123: // ttl type
124: // type
125: seen_class = false;
126: s = st.getString();
127: if ((currentDClass = DClass.value(s)) >= 0) {
128: s = st.getString();
129: seen_class = true;
130: }
131:
132: currentTTL = -1;
133: try {
134: currentTTL = TTL.parseTTL(s);
135: s = st.getString();
136: } catch (NumberFormatException e) {
137: if (defaultTTL >= 0)
138: currentTTL = defaultTTL;
139: else if (last != null)
140: currentTTL = last.getTTL();
141: }
142:
143: if (!seen_class) {
144: if ((currentDClass = DClass.value(s)) >= 0) {
145: s = st.getString();
146: } else {
147: currentDClass = DClass.IN;
148: }
149: }
150:
151: if ((currentType = Type.value(s)) < 0)
152: throw st.exception("Invalid type '" + s + "'");
153:
154: // BIND allows a missing TTL for the initial SOA record, and uses
155: // the SOA minimum value. If the SOA is not the first record,
156: // this is an error.
157: if (currentTTL < 0) {
158: if (currentType != Type.SOA)
159: throw st.exception("missing TTL");
160: needSOATTL = true;
161: currentTTL = 0;
162: }
163: }
164:
165: private long parseUInt32(String s) {
166: if (!Character.isDigit(s.charAt(0)))
167: return -1;
168: try {
169: long l = Long.parseLong(s);
170: if (l < 0 || l > 0xFFFFFFFFL)
171: return -1;
172: return l;
173: } catch (NumberFormatException e) {
174: return -1;
175: }
176: }
177:
178: private void startGenerate() throws IOException {
179: String s;
180: int n;
181:
182: // The first field is of the form start-end[/step]
183: // Regexes would be useful here.
184: s = st.getIdentifier();
185: n = s.indexOf("-");
186: if (n < 0)
187: throw st.exception("Invalid $GENERATE range specifier: "
188: + s);
189: String startstr = s.substring(0, n);
190: String endstr = s.substring(n + 1);
191: String stepstr = null;
192: n = endstr.indexOf("/");
193: if (n >= 0) {
194: stepstr = endstr.substring(n + 1);
195: endstr = endstr.substring(0, n);
196: }
197: long start = parseUInt32(startstr);
198: long end = parseUInt32(endstr);
199: long step;
200: if (stepstr != null)
201: step = parseUInt32(stepstr);
202: else
203: step = 1;
204: if (start < 0 || end < 0 || start > end || step <= 0)
205: throw st.exception("Invalid $GENERATE range specifier: "
206: + s);
207:
208: // The next field is the name specification.
209: String nameSpec = st.getIdentifier();
210:
211: // Then the ttl/class/type, in the same form as a normal record.
212: // Only some types are supported.
213: parseTTLClassAndType();
214: if (!Generator.supportedType(currentType))
215: throw st.exception("$GENERATE does not support "
216: + Type.string(currentType) + " records");
217:
218: // Next comes the rdata specification.
219: String rdataSpec = st.getIdentifier();
220:
221: // That should be the end. However, we don't want to move past the
222: // line yet, so put back the EOL after reading it.
223: st.getEOL();
224: st.unget();
225:
226: generator = new Generator(start, end, step, nameSpec,
227: currentType, currentDClass, currentTTL, rdataSpec,
228: origin);
229: if (generators == null)
230: generators = new ArrayList(1);
231: generators.add(generator);
232: }
233:
234: private void endGenerate() throws IOException {
235: // Read the EOL that we put back before.
236: st.getEOL();
237:
238: generator = null;
239: }
240:
241: private Record nextGenerated() throws IOException {
242: try {
243: return generator.nextRecord();
244: } catch (Tokenizer.TokenizerException e) {
245: throw st.exception("Parsing $GENERATE: "
246: + e.getBaseMessage());
247: } catch (TextParseException e) {
248: throw st.exception("Parsing $GENERATE: " + e.getMessage());
249: }
250: }
251:
252: /**
253: * Returns the next record in the master file. This will process any
254: * directives before the next record.
255: * @return The next record.
256: * @throws IOException The master file could not be read, or was syntactically
257: * invalid.
258: */
259: public Record _nextRecord() throws IOException {
260: Tokenizer.Token token;
261: String s;
262:
263: if (included != null) {
264: Record rec = included.nextRecord();
265: if (rec != null)
266: return rec;
267: included = null;
268: }
269: if (generator != null) {
270: Record rec = nextGenerated();
271: if (rec != null)
272: return rec;
273: endGenerate();
274: }
275: while (true) {
276: Name name;
277:
278: token = st.get(true, false);
279: if (token.type == Tokenizer.WHITESPACE) {
280: Tokenizer.Token next = st.get();
281: if (token.type == Tokenizer.EOL)
282: continue;
283: else if (token.type == Tokenizer.EOF)
284: return null;
285: else
286: st.unget();
287: if (last == null)
288: throw st.exception("no owner");
289: name = last.getName();
290: } else if (token.type == Tokenizer.EOL)
291: continue;
292: else if (token.type == Tokenizer.EOF)
293: return null;
294: else if (((String) token.value).charAt(0) == '$') {
295: s = token.value;
296:
297: if (s.equalsIgnoreCase("$ORIGIN")) {
298: origin = st.getName(Name.root);
299: st.getEOL();
300: continue;
301: } else if (s.equalsIgnoreCase("$TTL")) {
302: defaultTTL = st.getTTL();
303: st.getEOL();
304: continue;
305: } else if (s.equalsIgnoreCase("$INCLUDE")) {
306: String filename = st.getString();
307: File newfile;
308: if (file != null) {
309: String parent = file.getParent();
310: newfile = new File(parent, filename);
311: } else {
312: newfile = new File(filename);
313: }
314: Name incorigin = origin;
315: token = st.get();
316: if (token.isString()) {
317: incorigin = parseName(token.value, Name.root);
318: st.getEOL();
319: }
320: included = new Master(newfile, incorigin,
321: defaultTTL);
322: /*
323: * If we continued, we wouldn't be looking in
324: * the new file. Recursing works better.
325: */
326: return nextRecord();
327: } else if (s.equalsIgnoreCase("$GENERATE")) {
328: if (generator != null)
329: throw new IllegalStateException(
330: "cannot nest $GENERATE");
331: startGenerate();
332: if (noExpandGenerate) {
333: endGenerate();
334: continue;
335: }
336: return nextGenerated();
337: } else {
338: throw st.exception("Invalid directive: " + s);
339: }
340: } else {
341: s = token.value;
342: name = parseName(s, origin);
343: if (last != null && name.equals(last.getName())) {
344: name = last.getName();
345: }
346: }
347:
348: parseTTLClassAndType();
349: last = Record.fromString(name, currentType, currentDClass,
350: currentTTL, st, origin);
351: if (needSOATTL) {
352: long ttl = ((SOARecord) last).getMinimum();
353: last.setTTL(ttl);
354: defaultTTL = ttl;
355: needSOATTL = false;
356: }
357: return last;
358: }
359: }
360:
361: /**
362: * Returns the next record in the master file. This will process any
363: * directives before the next record.
364: * @return The next record.
365: * @throws IOException The master file could not be read, or was syntactically
366: * invalid.
367: */
368: public Record nextRecord() throws IOException {
369: Record rec = null;
370: try {
371: rec = _nextRecord();
372: } finally {
373: if (rec == null) {
374: st.close();
375: }
376: }
377: return rec;
378: }
379:
380: /**
381: * Specifies whether $GENERATE statements should be expanded. Whether
382: * expanded or not, the specifications for generated records are available
383: * by calling {@link #generators}. This must be called before a $GENERATE
384: * statement is seen during iteration to have an effect.
385: */
386: public void expandGenerate(boolean wantExpand) {
387: noExpandGenerate = !wantExpand;
388: }
389:
390: /**
391: * Returns an iterator over the generators specified in the master file; that
392: * is, the parsed contents of $GENERATE statements.
393: * @see Generator
394: */
395: public Iterator generators() {
396: if (generators != null)
397: return Collections.unmodifiableList(generators).iterator();
398: else
399: return Collections.EMPTY_LIST.iterator();
400: }
401:
402: protected void finalize() {
403: st.close();
404: }
405:
406: }
|