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