View Javadoc
1   package org.argeo.naming;
2   
3   import java.io.Closeable;
4   import java.io.IOException;
5   import java.io.PrintStream;
6   import java.util.ArrayList;
7   import java.util.Base64;
8   import java.util.Collections;
9   import java.util.Hashtable;
10  import java.util.List;
11  import java.util.Map;
12  import java.util.SortedSet;
13  import java.util.TreeMap;
14  import java.util.TreeSet;
15  
16  import javax.naming.Binding;
17  import javax.naming.NamingEnumeration;
18  import javax.naming.NamingException;
19  import javax.naming.directory.Attribute;
20  import javax.naming.directory.Attributes;
21  import javax.naming.directory.DirContext;
22  import javax.naming.directory.InitialDirContext;
23  
24  public class DnsBrowser implements Closeable {
25  	private final DirContext initialCtx;
26  
27  	public DnsBrowser() throws NamingException {
28  		this(null);
29  	}
30  
31  	public DnsBrowser(String dnsServerUrls) throws NamingException {
32  		Hashtable<String, Object> env = new Hashtable<>();
33  		env.put("java.naming.factory.initial", "com.sun.jndi.dns.DnsContextFactory");
34  		if (dnsServerUrls != null)
35  			env.put("java.naming.provider.url", dnsServerUrls);
36  		initialCtx = new InitialDirContext(env);
37  	}
38  
39  	public Map<String, List<String>> getAllRecords(String name) throws NamingException {
40  		Map<String, List<String>> res = new TreeMap<>();
41  		Attributes attrs = initialCtx.getAttributes(name);
42  		NamingEnumeration<String> ids = attrs.getIDs();
43  		while (ids.hasMore()) {
44  			String recordType = ids.next();
45  			List<String> lst = new ArrayList<String>();
46  			res.put(recordType, lst);
47  			Attribute attr = attrs.get(recordType);
48  			addValues(attr, lst);
49  		}
50  		return Collections.unmodifiableMap(res);
51  	}
52  
53  	/**
54  	 * Return a single record (typically A, AAAA, etc. or null if not available.
55  	 * Will fail if multiple records.
56  	 */
57  	public String getRecord(String name, String recordType) throws NamingException {
58  		Attributes attrs = initialCtx.getAttributes(name, new String[] { recordType });
59  		if (attrs.size() == 0)
60  			return null;
61  		Attribute attr = attrs.get(recordType);
62  		if (attr.size() > 1)
63  			throw new IllegalArgumentException("Multiple record type " + recordType);
64  		assert attr.size() != 0;
65  		Object value = attr.get();
66  		assert value != null;
67  		return value.toString();
68  	}
69  
70  	/**
71  	 * Return records of a given type.
72  	 */
73  	public List<String> getRecords(String name, String recordType) throws NamingException {
74  		List<String> res = new ArrayList<String>();
75  		Attributes attrs = initialCtx.getAttributes(name, new String[] { recordType });
76  		Attribute attr = attrs.get(recordType);
77  		addValues(attr, res);
78  		return res;
79  	}
80  
81  	/** Ordered, with preferred first. */
82  	public List<String> getSrvRecordsAsHosts(String name) throws NamingException {
83  		List<String> raw = getRecords(name, "SRV");
84  		if (raw.size() == 0)
85  			return null;
86  		SortedSet<SrvRecord> res = new TreeSet<>();
87  		for (int i = 0; i < raw.size(); i++) {
88  			String record = raw.get(i);
89  			String[] arr = record.split(" ");
90  			Integer priority = Integer.parseInt(arr[0]);
91  			Integer weight = Integer.parseInt(arr[1]);
92  			Integer port = Integer.parseInt(arr[2]);
93  			String hostname = arr[3];
94  			SrvRecord order = new SrvRecord(priority, weight, port, hostname);
95  			res.add(order);
96  		}
97  		List<String> lst = new ArrayList<>();
98  		for (SrvRecord order : res) {
99  			lst.add(order.toHost());
100 		}
101 		return Collections.unmodifiableList(lst);
102 	}
103 
104 	private void addValues(Attribute attr, List<String> lst) throws NamingException {
105 		NamingEnumeration<?> values = attr.getAll();
106 		while (values.hasMore()) {
107 			Object value = values.next();
108 			if (value != null) {
109 				if (value instanceof byte[]) {
110 					String str = Base64.getEncoder().encodeToString((byte[]) value);
111 					lst.add(str);
112 				} else
113 					lst.add(value.toString());
114 			}
115 		}
116 
117 	}
118 
119 	public List<String> listEntries(String name) throws NamingException {
120 		List<String> res = new ArrayList<String>();
121 		NamingEnumeration<Binding> ne = initialCtx.listBindings(name);
122 		while (ne.hasMore()) {
123 			Binding b = ne.next();
124 			res.add(b.getName());
125 		}
126 		return Collections.unmodifiableList(res);
127 	}
128 
129 	@Override
130 	public void close() throws IOException {
131 		destroy();
132 	}
133 
134 	public void destroy() {
135 		try {
136 			initialCtx.close();
137 		} catch (NamingException e) {
138 			// silent
139 		}
140 	}
141 
142 	public static void main(String[] args) {
143 		if (args.length == 0) {
144 			printUsage(System.err);
145 			System.exit(1);
146 		}
147 		try (DnsBrowser dnsBrowser = new DnsBrowser()) {
148 			String hostname = args[0];
149 			String recordType = args.length > 1 ? args[1] : "A";
150 			if (recordType.equals("*")) {
151 				Map<String, List<String>> records = dnsBrowser.getAllRecords(hostname);
152 				for (String type : records.keySet()) {
153 					for (String record : records.get(type)) {
154 						String typeLabel;
155 						if ("44".equals(type))
156 							typeLabel = "SSHFP";
157 						else if ("46".equals(type))
158 							typeLabel = "RRSIG";
159 						else if ("48".equals(type))
160 							typeLabel = "DNSKEY";
161 						else
162 							typeLabel = type;
163 						System.out.println(typeLabel + "\t" + record);
164 					}
165 				}
166 			} else {
167 				System.out.println(dnsBrowser.getRecord(hostname, recordType));
168 			}
169 
170 		} catch (Exception e) {
171 			e.printStackTrace();
172 		}
173 	}
174 
175 	public static void printUsage(PrintStream out) {
176 		out.println("java org.argeo.naming.DnsBrowser <hostname> [<record type> | *]");
177 	}
178 
179 }