PageRenderTime 50ms CodeModel.GetById 25ms RepoModel.GetById 1ms app.codeStats 0ms

/projects/hsqldb-2.0.0/hsqldb/src/org/hsqldb/server/ServerAcl.java

https://gitlab.com/essere.lab.public/qualitas.class-corpus
Java | 525 lines | 319 code | 101 blank | 105 comment | 52 complexity | c5072ca55e9ea4a7bd65bd0b8d475d6d MD5 | raw file
  1. /* Copyright (c) 2001-2010, The HSQL Development Group
  2. * All rights reserved.
  3. *
  4. * Redistribution and use in source and binary forms, with or without
  5. * modification, are permitted provided that the following conditions are met:
  6. *
  7. * Redistributions of source code must retain the above copyright notice, this
  8. * list of conditions and the following disclaimer.
  9. *
  10. * Redistributions in binary form must reproduce the above copyright notice,
  11. * this list of conditions and the following disclaimer in the documentation
  12. * and/or other materials provided with the distribution.
  13. *
  14. * Neither the name of the HSQL Development Group nor the names of its
  15. * contributors may be used to endorse or promote products derived from this
  16. * software without specific prior written permission.
  17. *
  18. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  19. * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  20. * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  21. * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
  22. * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
  23. * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  24. * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  25. * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  26. * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  27. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  28. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  29. */
  30. package org.hsqldb.server;
  31. import java.io.BufferedReader;
  32. import java.io.File;
  33. import java.io.FileReader;
  34. import java.io.IOException;
  35. import java.io.InputStreamReader;
  36. import java.io.PrintWriter;
  37. import java.net.InetAddress;
  38. import java.net.UnknownHostException;
  39. import java.util.ArrayList;
  40. import java.util.List;
  41. import java.util.StringTokenizer;
  42. import org.hsqldb.store.BitMap;
  43. /**
  44. * A list of ACL permit and deny entries with a permitAccess method
  45. * which tells whether candidate addresses are permitted or denied
  46. * by this ACL list.
  47. * <P>
  48. * The ACL file is reloaded whenever a modification to it is detected.
  49. * If you copy in a file with an older file date, you will need to touch it.
  50. * </P>
  51. *
  52. * <P>
  53. * The public runtime method is permitAccess().
  54. * The public setup method is the constructor.
  55. * </P> <P>
  56. * Each non-comment line in the ACL file must be a rule of the format:
  57. * <PRE><CODE>
  58. * {allow|deny} <ip_address>[/significant-bits]
  59. * </CODE></PRE>
  60. * For example
  61. * <PRE><CODE>
  62. * allow ahostname
  63. * deny ahost.domain.com
  64. * allow 127.0.0.1
  65. * allow 2001:db8::/32
  66. * </CODE></PRE>
  67. * </P> <P>
  68. * In order to detect bit specification mistakes, we require that
  69. * non-significant bits be zero in the values.
  70. * An undesirable consequence of this is, you can't use a specification like
  71. * the following to mean "all of the hosts on the same network as x.admc.com":
  72. * <PRE><CODE>
  73. * allow x.admc.com/24
  74. * </CODE></PRE>
  75. * </P>
  76. *
  77. * @see #ServerAcl(File)
  78. * @see #permitAccess
  79. **/
  80. public final class ServerAcl {
  81. public static final class AclFormatException extends Exception {
  82. public AclFormatException(String s) {
  83. super(s);
  84. }
  85. }
  86. protected final static byte[] ALL_SET_4BYTES = new byte[] {
  87. -1, -1, -1, -1
  88. };
  89. protected final static byte[] ALL_SET_16BYTES = new byte[] {
  90. -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1
  91. };
  92. // -1 is all-bits-on in 2's-complement for signed values.
  93. // Must do it this way since Java has no support for unsigned integral
  94. // constants.
  95. private static final class AclEntry {
  96. private byte[] value;
  97. private byte[] mask; // These are the bits in candidate which must match
  98. private int bitBlockSize;
  99. public boolean allow;
  100. public AclEntry(byte[] value, int bitBlockSize,
  101. boolean allow) throws AclFormatException {
  102. byte[] allOn = null;
  103. switch (value.length) {
  104. case 4 :
  105. allOn = ALL_SET_4BYTES;
  106. break;
  107. case 16 :
  108. allOn = ALL_SET_16BYTES;
  109. break;
  110. default :
  111. throw new IllegalArgumentException(
  112. "Only 4 and 16 bytes supported, not " + value.length);
  113. }
  114. if (bitBlockSize > value.length * 8) {
  115. throw new IllegalArgumentException(
  116. "Specified " + bitBlockSize
  117. + " significant bits, but value only has "
  118. + (value.length * 8) + " bits");
  119. }
  120. this.bitBlockSize = bitBlockSize;
  121. this.value = value;
  122. mask = BitMap.leftShift(allOn, value.length * 8 - bitBlockSize);
  123. if (mask.length != value.length) {
  124. throw new RuntimeException(
  125. "Basic program assertion failed. "
  126. + "Generated mask length " + mask.length
  127. + " (bytes) does not match given value length "
  128. + value.length + " (bytes).");
  129. }
  130. this.allow = allow;
  131. validateMask();
  132. }
  133. public String toString() {
  134. StringBuffer sb = new StringBuffer("Addrs ");
  135. sb.append((value.length == 16)
  136. ? ("[" + ServerAcl.colonNotation(value) + ']')
  137. : ServerAcl.dottedNotation(value));
  138. sb.append("/" + bitBlockSize + ' ' + (allow ? "ALLOW"
  139. : "DENY"));
  140. return sb.toString();
  141. }
  142. public boolean matches(byte[] candidate) {
  143. if (value.length != candidate.length) {
  144. return false;
  145. }
  146. return !BitMap.hasAnyBitSet(BitMap.xor(value,
  147. BitMap.and(candidate,
  148. mask)));
  149. }
  150. public void validateMask() throws AclFormatException {
  151. if (BitMap.hasAnyBitSet(BitMap.and(value, BitMap.not(mask)))) {
  152. throw new AclFormatException(
  153. "The base address '" + ServerAcl.dottedNotation(value)
  154. + "' is too specific for block-size-spec /"
  155. + bitBlockSize);
  156. }
  157. }
  158. }
  159. /**
  160. * @param uba Unsigned byte array
  161. */
  162. static public String dottedNotation(byte[] uba) {
  163. StringBuffer sb = new StringBuffer();
  164. for (int i = 0; i < uba.length; i++) {
  165. if (i > 0) {
  166. sb.append('.');
  167. }
  168. sb.append((int) uba[i] & 0xff);
  169. }
  170. return sb.toString();
  171. }
  172. /**
  173. * @param uba Unsigned byte array
  174. */
  175. static public String colonNotation(byte[] uba) {
  176. // TODO: handle odd byte lengths.
  177. if ((uba.length / 2) * 2 != uba.length) {
  178. throw new RuntimeException(
  179. "At this time .colonNotation only handles even byte quantities");
  180. }
  181. StringBuffer sb = new StringBuffer();
  182. for (int i = 0; i < uba.length; i += 2) {
  183. if (i > 0) {
  184. sb.append(':');
  185. }
  186. sb.append(Integer.toHexString((uba[i] & 0xff) * 256
  187. + (uba[i + 1] & 0xff)));
  188. }
  189. return sb.toString();
  190. }
  191. private PrintWriter pw = null;
  192. public void setPrintWriter(PrintWriter pw) {
  193. this.pw = pw;
  194. }
  195. public String toString() {
  196. StringBuffer sb = new StringBuffer();
  197. for (int i = 0; i < aclEntries.size(); i++) {
  198. if (i > 0) {
  199. sb.append('\n');
  200. }
  201. sb.append("Entry " + (i + 1) + ": " + aclEntries.get(i));
  202. }
  203. return sb.toString();
  204. }
  205. private List aclEntries;
  206. static private AclEntry PROHIBIT_ALL_IPV4;
  207. static private AclEntry PROHIBIT_ALL_IPV6;
  208. static {
  209. try {
  210. PROHIBIT_ALL_IPV4 =
  211. new AclEntry(InetAddress.getByName("0.0.0.0").getAddress(), 0,
  212. false);
  213. PROHIBIT_ALL_IPV6 =
  214. new AclEntry(InetAddress.getByName("::").getAddress(), 0,
  215. false);
  216. } catch (UnknownHostException uke) {
  217. // Should never reach here, since no name service is needed to
  218. // look up either address.
  219. throw new RuntimeException(
  220. "Unexpected problem in static initializer", uke);
  221. } catch (AclFormatException afe) {
  222. throw new RuntimeException(
  223. "Unexpected problem in static initializer", afe);
  224. }
  225. }
  226. /**
  227. * Uses system network libraries to resolve the given String to an IP addr,
  228. * then determine whether this address is permitted or denied.
  229. *
  230. * Specified name may be a numerical-based String like "1.2.3.4", a
  231. * constant known to the networking libraries, or a host name to be
  232. * resolved by the systems name resolution system.
  233. *
  234. * If the given String can't be resolved to an IP addr, false is returned.
  235. *
  236. * @see #permitAccess(byte[])
  237. */
  238. public boolean permitAccess(String s) {
  239. try {
  240. return permitAccess(InetAddress.getByName(s).getAddress());
  241. } catch (UnknownHostException uke) {
  242. println("'" + s + "' denied because failed to resolve to an addr");
  243. return false; // Resolution of candidate failed
  244. }
  245. }
  246. /**
  247. * @return true if access for the candidate address should be permitted,
  248. * false if access should be denied.
  249. * @throws RuntimeException if no rule covers the candidate address.
  250. * This would be the case if this class is applied to some
  251. * network protocol other than ipv4 or ipv6, without adding a
  252. * default rule for it.
  253. */
  254. public boolean permitAccess(byte[] addr) {
  255. ensureAclsUptodate();
  256. for (int i = 0; i < aclEntries.size(); i++) {
  257. if (((AclEntry) aclEntries.get(i)).matches(addr)) {
  258. AclEntry hit = (AclEntry) aclEntries.get(i);
  259. println("Addr '" + ServerAcl.dottedNotation(addr)
  260. + "' matched rule #" + (i + 1) + ": " + hit);
  261. return hit.allow;
  262. }
  263. }
  264. throw new RuntimeException("No rule matches address '"
  265. + ServerAcl.dottedNotation(addr) + "'");
  266. }
  267. private void println(String s) {
  268. if (pw == null) {
  269. return;
  270. }
  271. pw.println(s);
  272. pw.flush();
  273. }
  274. private File aclFile;
  275. private long lastLoadTime = 0;
  276. private static final class InternalException extends Exception {}
  277. public ServerAcl(File aclFile) throws IOException, AclFormatException {
  278. this.aclFile = aclFile;
  279. aclEntries = load();
  280. }
  281. synchronized protected void ensureAclsUptodate() {
  282. if (lastLoadTime > aclFile.lastModified()) {
  283. return;
  284. }
  285. try {
  286. aclEntries = load();
  287. println("ACLs reloaded from file");
  288. return;
  289. } catch (Exception e) {
  290. println("Failed to reload ACL file. Retaining old ACLs. " + e);
  291. }
  292. }
  293. protected List load() throws IOException, AclFormatException {
  294. if (!aclFile.exists()) {
  295. throw new IOException("File '" + aclFile.getAbsolutePath()
  296. + "' is not present");
  297. }
  298. if (!aclFile.isFile()) {
  299. throw new IOException("'" + aclFile.getAbsolutePath()
  300. + "' is not a regular file");
  301. }
  302. if (!aclFile.canRead()) {
  303. throw new IOException("'" + aclFile.getAbsolutePath()
  304. + "' is not accessible");
  305. }
  306. String line;
  307. String ruleTypeString;
  308. StringTokenizer toker;
  309. String addrString,
  310. bitString = null;
  311. int slashIndex;
  312. int linenum = 0;
  313. byte[] addr;
  314. boolean allow;
  315. int bits;
  316. BufferedReader br = new BufferedReader(new FileReader(aclFile));
  317. List newAcls = new ArrayList();
  318. try {
  319. while ((line = br.readLine()) != null) {
  320. linenum++;
  321. line = line.trim();
  322. if (line.length() < 1) {
  323. continue;
  324. }
  325. if (line.charAt(0) == '#') {
  326. continue;
  327. }
  328. toker = new StringTokenizer(line);
  329. try {
  330. if (toker.countTokens() != 2) {
  331. throw new InternalException();
  332. }
  333. ruleTypeString = toker.nextToken();
  334. addrString = toker.nextToken();
  335. slashIndex = addrString.indexOf('/');
  336. if (slashIndex > -1) {
  337. bitString = addrString.substring(slashIndex + 1);
  338. addrString = addrString.substring(0, slashIndex);
  339. }
  340. addr = InetAddress.getByName(addrString).getAddress();
  341. bits = (bitString == null) ? (addr.length * 8)
  342. : Integer.parseInt(bitString);
  343. if (ruleTypeString.equalsIgnoreCase("allow")) {
  344. allow = true;
  345. } else if (ruleTypeString.equalsIgnoreCase("permit")) {
  346. allow = true;
  347. } else if (ruleTypeString.equalsIgnoreCase("accept")) {
  348. allow = true;
  349. } else if (ruleTypeString.equalsIgnoreCase("prohibit")) {
  350. allow = false;
  351. } else if (ruleTypeString.equalsIgnoreCase("deny")) {
  352. allow = false;
  353. } else if (ruleTypeString.equalsIgnoreCase("reject")) {
  354. allow = false;
  355. } else {
  356. throw new InternalException();
  357. }
  358. } catch (NumberFormatException nfe) {
  359. throw new AclFormatException("Syntax error at ACL file '"
  360. + aclFile.getAbsolutePath()
  361. + "', line " + linenum);
  362. } catch (InternalException ie) {
  363. throw new AclFormatException("Syntax error at ACL file '"
  364. + aclFile.getAbsolutePath()
  365. + "', line " + linenum);
  366. }
  367. try {
  368. newAcls.add(new AclEntry(addr, bits, allow));
  369. } catch (AclFormatException afe) {
  370. throw new AclFormatException("Syntax error at ACL file '"
  371. + aclFile.getAbsolutePath()
  372. + "', line " + linenum + ": "
  373. + afe.getMessage());
  374. }
  375. }
  376. } finally {
  377. br.close();
  378. }
  379. newAcls.add(PROHIBIT_ALL_IPV4);
  380. newAcls.add(PROHIBIT_ALL_IPV6);
  381. lastLoadTime = new java.util.Date().getTime();
  382. return newAcls;
  383. }
  384. /**
  385. * Utility method that allows interactive testing of individal
  386. * ACL records, as well as the net effect of the ACL record list.
  387. *
  388. * Run "java -cp path/to/hsqldb.jar org.hsqldb.server.ServerAcl --help"
  389. * for Syntax help.
  390. */
  391. public static void main(String[] sa)
  392. throws AclFormatException, IOException {
  393. if (sa.length > 1) {
  394. throw new RuntimeException("Try: java -cp path/to/hsqldb.jar "
  395. + ServerAcl.class.getName()
  396. + " --help");
  397. }
  398. if (sa.length > 0 && sa[0].equals("--help")) {
  399. System.err.println("SYNTAX: java -cp path/to/hsqldb.jar "
  400. + ServerAcl.class.getName()
  401. + " [filepath.txt]");
  402. System.err.println("ACL file path defaults to 'acl.txt' in the "
  403. + "current directory.");
  404. System.exit(0);
  405. }
  406. ServerAcl serverAcl = new ServerAcl(new File((sa.length == 0)
  407. ? "acl.txt"
  408. : sa[0]));
  409. serverAcl.setPrintWriter(new PrintWriter(System.out));
  410. System.out.println(serverAcl.toString());
  411. BufferedReader br =
  412. new BufferedReader(new InputStreamReader(System.in));
  413. System.out.println("Enter hostnames or IP addresses to be tested "
  414. + "(one per line).");
  415. String s;
  416. while ((s = br.readLine()) != null) {
  417. s = s.trim();
  418. if (s.length() < 1) {
  419. continue;
  420. }
  421. System.out.println(Boolean.toString(serverAcl.permitAccess(s)));
  422. }
  423. }
  424. }