9740880a951c021505bd09ed2a674b20d03c5e99
[captive-validator.git] / src / com / verisign / cl / DNSSECReconciler.java
1 package com.verisign.cl;
2
3 import java.io.BufferedReader;
4 import java.io.FileReader;
5 import java.io.IOException;
6 import java.net.SocketTimeoutException;
7 import java.util.*;
8
9 import org.xbill.DNS.*;
10
11 import com.verisign.tat.dnssec.CaptiveValidator;
12 import com.verisign.tat.dnssec.SecurityStatus;
13
14 public class DNSSECReconciler {
15
16     /**
17      * Invoke with java -jar dnssecreconciler.jar server=127.0.0.1 \
18      * query_file=queries.txt dnskey_query=net dnskey_query=edu
19      */
20     private CaptiveValidator validator;
21     private SimpleResolver resolver;
22
23     private BufferedReader queryStream;
24     private Set<Name> zoneNames;
25
26     // Options
27     public String server;
28     public String query;
29     public String queryFile;
30     public String dnskeyFile;
31     public List<String> dnskeyNames;
32
33     DNSSECReconciler() {
34         validator = new CaptiveValidator();
35     }
36
37     /**
38      * Convert a query line of the form: <qname> <qtype> <flags> to a request
39      * message.
40      * 
41      * @param query_line
42      * @return A query message
43      * @throws TextParseException
44      * @throws NameTooLongException
45      */
46     private Message queryFromString(String query_line)
47             throws TextParseException, NameTooLongException {
48
49         String[] tokens = query_line.split("[ \t]+");
50
51         Name qname = null;
52         int qtype = -1;
53         int qclass = -1;
54
55         if (tokens.length < 1)
56             return null;
57         qname = Name.fromString(tokens[0]);
58         if (!qname.isAbsolute()) {
59             qname = Name.concatenate(qname, Name.root);
60         }
61
62         for (int i = 1; i < tokens.length; i++) {
63             if (tokens[i].startsWith("+")) {
64                 // For now, we ignore flags as uninteresting
65                 continue;
66             }
67
68             int type = Type.value(tokens[i]);
69             if (type > 0) {
70                 qtype = type;
71                 continue;
72             }
73             int cls = DClass.value(tokens[i]);
74             if (cls > 0) {
75                 qclass = cls;
76                 continue;
77             }
78         }
79         if (qtype < 0) {
80             qtype = Type.A;
81         }
82         if (qclass < 0) {
83             qclass = DClass.IN;
84         }
85
86         Message query = Message
87                 .newQuery(Record.newRecord(qname, qtype, qclass));
88
89         return query;
90     }
91
92     /**
93      * Fetch the next query from either the command line or the query file
94      * 
95      * @return a query Message, or null if the query list is exhausted
96      * @throws IOException
97      */
98     private Message nextQuery() throws IOException {
99         if (query != null) {
100             Message res = queryFromString(query);
101             query = null;
102             return res;
103         }
104
105         if (queryStream == null && queryFile != null) {
106             queryStream = new BufferedReader(new FileReader(queryFile));
107         }
108
109         if (queryStream != null) {
110             String line = queryStream.readLine();
111
112             if (line == null)
113                 return null;
114
115             return queryFromString(line);
116         }
117
118         return null;
119
120     }
121
122     /**
123      * Figure out the correct zone from the query by comparing the qname to the
124      * list of trusted DNSKEY owner names.
125      * 
126      * @param query
127      * @return a zone name
128      * @throws IOException 
129      */
130     private Name zoneFromQuery(Message query) throws IOException {
131
132         if (zoneNames == null) {
133             zoneNames = new HashSet<Name>();
134             for (String key : validator.listTrustedKeys()) {
135                 String[] components = key.split("/");
136                 Name keyname = Name.fromString(components[0]);
137                 if (! keyname.isAbsolute()) {
138                     keyname = Name.concatenate(keyname, Name.root);
139                 }
140                 zoneNames.add(keyname);
141             }
142         }
143         
144         Name qname = query.getQuestion().getName();
145         for (Name n : zoneNames) {
146             if (qname.subdomain(n)) {
147                 return n;
148             }
149         }
150         
151         return null;
152     }
153
154     private Message resolve(Message query) {
155         
156         try {
157             return resolver.send(query);
158         } catch (SocketTimeoutException e) {
159             System.err.println("Error: timed out querying " + server + " for " + queryToString(query));
160         } catch (IOException e) {
161             System.err.println("Error: error querying " + server + " for " + queryToString(query) + ":" + e.getMessage());
162         }
163         return null;
164     }
165     
166     private String queryToString(Message query) {
167         if (query == null) return null;
168         Record question = query.getQuestion();
169         return question.getName() + "/" + Type.string(question.getType()) + "/" + DClass.string(question.getDClass());
170     }
171
172     public void execute() throws IOException {
173         // Configure our resolver
174         resolver = new SimpleResolver(server);
175         resolver.setEDNS(0, 4096, Flags.DO, null);
176
177         // Prime the validator
178         if (dnskeyFile != null) {
179             validator.addTrustedKeysFromFile(dnskeyFile);
180         } else {
181             for (String name : dnskeyNames) {
182                 Message query = queryFromString(name + " DNSKEY");
183                 Message response = resolve(query);
184                 validator.addTrustedKeysFromResponse(response);
185             }
186         }
187
188         // Log our set of trusted keys
189         for (String key : validator.listTrustedKeys()) {
190             System.out.println("Trusted Key: " + key);
191         }
192
193         // Iterate over all queries
194         Message query = nextQuery();
195         long count = 0;
196
197         while (query != null) {
198             
199             Name zone = zoneFromQuery(query);
200             // Skip queries in zones that we don't have keys for
201             if (zone == null) {
202                 continue;
203             }
204             
205             Message response = resolve(query);
206             if (response == null) {
207                 continue;
208             }
209             byte result = validator.validateMessage(response, zone.toString());
210
211             switch (result) {
212             case SecurityStatus.BOGUS:
213             case SecurityStatus.INVALID:
214                 System.out.println("BOGUS Answer:");
215                 System.out.println("Query: " + queryToString(query));
216                 System.out.println("Response:\n" + response);
217                 for (String err : validator.getErrorList()) {
218                     System.out.println("Error: " + err);
219                 }
220                 System.out.println("");
221                 break;
222             case SecurityStatus.INSECURE:
223             case SecurityStatus.INDETERMINATE:
224             case SecurityStatus.UNCHECKED:
225                 System.out.println("Insecure Answer:");
226                 System.out.println("Query: " + queryToString(query));
227                 System.out.println("Response:\n" + response);
228                 for (String err : validator.getErrorList()) {
229                     System.out.println("Error: " + err);
230                 }
231                 break;
232             case SecurityStatus.SECURE:
233                 break;
234             }
235             
236             if (++count % 1000 == 0) {
237                 System.out.println("Completed " + count + " queries.");
238             }
239             
240             query = nextQuery();
241         }
242         
243         System.out.println("Completed " + count + (count > 1 ? " queries" : " query"));
244     }
245
246     private static void usage() {
247         System.err
248                 .println("usage: java -jar dnssecreconiler.jar [..options..]");
249         System.err.println("       server: the DNS server to query.");
250         System.err.println("       query: a name [type [flags]] string.");
251         System.err
252                 .println("       query_file: a list of queries, one query per line.");
253         System.err
254                 .println("       dnskey_file: a file containing DNSKEY RRs to trust.");
255         System.err
256                 .println("       dnskey_query: query 'server' for DNSKEY at given name to trust, may repeat");
257     }
258
259     public static void main(String[] argv) {
260
261         DNSSECReconciler dr = new DNSSECReconciler();
262
263         try {
264             // Parse the command line options
265             for (String arg : argv) {
266
267                 if (arg.indexOf('=') < 0) {
268                     System.err.println("Unrecognized option: " + arg);
269                     usage();
270                     System.exit(1);
271                 }
272
273                 String[] split_arg = arg.split("=", 2);
274                 String opt = split_arg[0];
275                 String optarg = split_arg[1];
276
277                 if (opt.equals("server")) {
278                     dr.server = optarg;
279                 } else if (opt.equals("query")) {
280                     dr.query = optarg;
281                 } else if (opt.equals("query_file")) {
282                     dr.queryFile = optarg;
283                 } else if (opt.equals("dnskey_file")) {
284                     dr.dnskeyFile = optarg;
285                 } else if (opt.equals("dnskey_query")) {
286                     if (dr.dnskeyNames == null) {
287                         dr.dnskeyNames = new ArrayList<String>();
288                     }
289                     dr.dnskeyNames.add(optarg);
290                 } else {
291                     System.err.println("Unrecognized option: " + opt);
292                     usage();
293                     System.exit(1);
294                 }
295             }
296
297             // Check for minimum usage
298             if (dr.server == null) {
299                 System.err.println("'server' must be specified");
300                 usage();
301                 System.exit(1);
302             }
303             if (dr.query == null && dr.queryFile == null) {
304                 System.err
305                         .println("Either 'query' or 'query_file' must be specified");
306                 usage();
307                 System.exit(1);
308             }
309             if (dr.dnskeyFile == null && dr.dnskeyNames == null) {
310                 System.err
311                         .println("Either 'dnskey_file' or 'dnskey_query' must be specified");
312                 usage();
313                 System.exit(1);
314             }
315
316             // Execute the job
317             dr.execute();
318
319         } catch (Exception e) {
320             e.printStackTrace();
321             System.exit(1);
322         }
323     }
324 }