From bb8588a00e17e954aeeaa5119b706ac904c9d10f Mon Sep 17 00:00:00 2001 From: gm2552 Date: Mon, 19 Mar 2018 08:50:52 -0700 Subject: [PATCH] Adding tag DNS 2.0.1 --- java/dns/src/site/apt/releaseNotes.apt | 33 + java/tags/dns-2.0.1/pom.xml | 660 +++++++++++ .../dns-2.0.1/report/findbugs-exclude.xml | 9 + java/tags/dns-2.0.1/src/books/users-guide.xml | 40 + .../books/users-guide/deploy-intro.confluence | 27 + .../users-guide/dns-godad-config.confluence | 6 + .../users-guide/dns-proto-config.confluence | 22 + .../users-guide/dns-record-config.confluence | 152 +++ .../users-guide/dns-serv-depl.confluence | 166 +++ .../users-guide/images/WSConfigARecords.png | Bin 0 -> 6786 bytes .../users-guide/images/WSConfigDNSHome.png | Bin 0 -> 37721 bytes .../images/WSConfigDomainSearch.png | Bin 0 -> 28091 bytes .../users-guide/images/WSConfigMXRecords.png | Bin 0 -> 8232 bytes .../users-guide/images/WSConfigSOARecords.png | Bin 0 -> 17074 bytes .../users-guide/images/dnsconfigsetting.png | Bin 0 -> 42009 bytes .../books/users-guide/images/winservices.png | Bin 0 -> 34220 bytes .../src/books/users-guide/preface.apt | 11 + .../src/descriptors/distribution.xml | 19 + java/tags/dns-2.0.1/src/logs/wrapper.txt | 0 .../org/nhindirect/dns/AbstractDNSStore.java | 268 +++++ .../nhindirect/dns/ConfigServiceDNSStore.java | 412 +++++++ .../java/org/nhindirect/dns/DNSError.java | 72 ++ .../java/org/nhindirect/dns/DNSException.java | 95 ++ .../java/org/nhindirect/dns/DNSResponder.java | 189 +++ .../org/nhindirect/dns/DNSResponderTCP.java | 58 + .../org/nhindirect/dns/DNSResponderUDP.java | 58 + .../java/org/nhindirect/dns/DNSServer.java | 213 ++++ .../org/nhindirect/dns/DNSServerFactory.java | 98 ++ .../org/nhindirect/dns/DNSServerMBean.java | 50 + .../org/nhindirect/dns/DNSServerSettings.java | 106 ++ .../org/nhindirect/dns/DNSSocketServer.java | 321 +++++ .../nhindirect/dns/DNSSocketServerMBean.java | 68 ++ .../java/org/nhindirect/dns/DNSStore.java | 43 + .../org/nhindirect/dns/ProxyDNSStore.java | 154 +++ .../nhindirect/dns/RESTServiceDNSStore.java | 384 ++++++ .../nhindirect/dns/SocketServerSettings.java | 206 ++++ .../java/org/nhindirect/dns/TCPServer.java | 318 +++++ .../java/org/nhindirect/dns/UDPServer.java | 287 +++++ .../dns/annotation/ConfigServiceURL.java | 41 + .../dns/annotation/package-info.java | 4 + .../dns/config/DNSServerConfig.java | 40 + .../dns/config/RESTDNSServerConfig.java | 115 ++ .../dns/config/WSDNSServerConfig.java | 137 +++ .../nhindirect/dns/config/package-info.java | 4 + .../dns/module/DNSServerConfigModule.java | 92 ++ .../dns/module/DNSServerModule.java | 112 ++ .../nhindirect/dns/module/package-info.java | 4 + .../java/org/nhindirect/dns/package-info.java | 4 + .../AbstractConfigDNSStoreProvider.java | 29 + .../BasicDNSServerSettingsProvider.java | 68 ++ .../ConfigServiceDNSStoreProvider.java | 50 + .../ConfigServiceRESTDNSStoreProvider.java | 36 + .../provider/RESTDNSServerConfigProvider.java | 38 + .../provider/WSDNSServerConfigProvider.java | 67 ++ .../nhindirect/dns/provider/package-info.java | 4 + .../dns/service/DNSServerService.java | 151 +++ .../dns/service/SimpleServiceRunner.java | 249 ++++ .../nhindirect/dns/service/package-info.java | 4 + .../nhindirect/dns/tools/CertCommands.java | 221 ++++ .../org/nhindirect/dns/tools/DNSManager.java | 153 +++ .../dns/tools/DNSRecordCommands.java | 690 +++++++++++ .../nhindirect/dns/tools/DNSRecordParser.java | 163 +++ .../dns/tools/DNSRecordPrinter.java | 38 + .../dns/tools/DefaultDNSRecordPrinter.java | 267 +++++ .../nhindirect/dns/tools/package-info.java | 4 + .../tools/printers/AbstractRecordPrinter.java | 139 +++ .../dns/tools/printers/CertRecordPrinter.java | 106 ++ .../dns/tools/printers/RecordPrinter.java | 10 + .../nhindirect/dns/tools/utils/Action.java | 40 + .../nhindirect/dns/tools/utils/Command.java | 51 + .../dns/tools/utils/CommandDef.java | 89 ++ .../dns/tools/utils/CommandUsage.java | 34 + .../nhindirect/dns/tools/utils/Commands.java | 558 +++++++++ .../dns/tools/utils/StringArrayUtil.java | 159 +++ .../org/nhindirect/dns/utils/CertUtils.java | 256 ++++ .../dns-2.0.1/src/site/apt/releaseNotes.apt | 211 ++++ .../dns-2.0.1/src/site/resources/css/site.css | 0 .../src/site/resources/images/logo.png | Bin 0 -> 13648 bytes java/tags/dns-2.0.1/src/site/site.xml | 26 + java/tags/dns-2.0.1/src/site/xdoc/index.xml | 29 + ...gServiceDNSStore_configCertPolicyTest.java | 127 ++ ...NSStore_isCertCompliantWithPolicyTest.java | 142 +++ .../org/nhindirect/dns/DNSConnectionTest.java | 248 ++++ .../dns/DNSServer_Function_Test.java | 1043 +++++++++++++++++ .../java/org/nhindirect/dns/MockDNSStore.java | 20 + .../provider/MockConfigDNSStoreProvider.java | 24 + .../dns/provider/MockDNSStoreProvider.java | 18 + .../DNSServerService_constructTest.java | 154 +++ .../tools/CertCommands_addIPKIXCert_Test.java | 174 +++ .../CertCommands_importPrivateCert_Test.java | 250 ++++ .../CertCommands_importPublicCert_Test.java | 195 +++ .../CertCommands_listCertsByAddress_Test.java | 329 ++++++ .../tools/CertCommands_listCerts_Test.java | 247 ++++ .../tools/CertCommands_removeCert_Test.java | 248 ++++ .../dns/tools/CertRecordCounterPrinter.java | 36 + .../dns/tools/DNSManager_functional_Test.java | 615 ++++++++++ .../DNSRecordCommands_addARecords_Test.java | 277 +++++ .../DNSRecordCommands_addMXRecords_Test.java | 335 ++++++ .../DNSRecordCommands_addSAORecords_Test.java | 247 ++++ ...DNSRecordCommands_ensureARecords_Test.java | 299 +++++ ...NSRecordCommands_ensureMXRecords_Test.java | 320 +++++ ...SRecordCommands_ensureSAORecords_Test.java | 231 ++++ .../tools/DNSRecordCommands_getAll_Test.java | 235 ++++ .../DNSRecordCommands_getByRecordId_Test.java | 420 +++++++ .../DNSRecordCommands_importRecords_Test.java | 338 ++++++ .../DNSRecordCommands_matchARecords_Test.java | 347 ++++++ .../DNSRecordCommands_matchAllTypes_Test.java | 399 +++++++ ...DNSRecordCommands_matchMXRecords_Test.java | 363 ++++++ ...NSRecordCommands_matchSOARecords_Test.java | 366 ++++++ .../DNSRecordCommands_removeRecords_Test.java | 368 ++++++ .../dns/tools/MockDNSRecordPrinter.java | 40 + .../org/nhindirect/dns/util/BaseTestPlan.java | 44 + .../dns/util/ConfigServiceRunner.java | 64 + .../nhindirect/dns/util/DNSRecordUtil.java | 142 +++ .../java/org/nhindirect/dns/util/IPUtils.java | 40 + .../src/test/resources/certs/bob.der | Bin 0 -> 899 bytes .../src/test/resources/certs/certCheckA.p12 | Bin 0 -> 2938 bytes .../src/test/resources/certs/gm2552.der | Bin 0 -> 791 bytes .../src/test/resources/certs/mshost.der | Bin 0 -> 908 bytes .../src/test/resources/certs/ryan.der | Bin 0 -> 1088 bytes .../src/test/resources/certs/umesh.der | Bin 0 -> 1077 bytes .../resources/dnsrecords/example.domain.com.a | Bin 0 -> 34 bytes .../dnsrecords/example.domain.com.corrupt | 1 + .../dnsrecords/example.domain.com.mx | Bin 0 -> 58 bytes .../dnsrecords/example.domain.com.soa | Bin 0 -> 101 bytes 125 files changed, 17784 insertions(+) create mode 100644 java/tags/dns-2.0.1/pom.xml create mode 100644 java/tags/dns-2.0.1/report/findbugs-exclude.xml create mode 100644 java/tags/dns-2.0.1/src/books/users-guide.xml create mode 100644 java/tags/dns-2.0.1/src/books/users-guide/deploy-intro.confluence create mode 100644 java/tags/dns-2.0.1/src/books/users-guide/dns-godad-config.confluence create mode 100644 java/tags/dns-2.0.1/src/books/users-guide/dns-proto-config.confluence create mode 100644 java/tags/dns-2.0.1/src/books/users-guide/dns-record-config.confluence create mode 100644 java/tags/dns-2.0.1/src/books/users-guide/dns-serv-depl.confluence create mode 100644 java/tags/dns-2.0.1/src/books/users-guide/images/WSConfigARecords.png create mode 100644 java/tags/dns-2.0.1/src/books/users-guide/images/WSConfigDNSHome.png create mode 100644 java/tags/dns-2.0.1/src/books/users-guide/images/WSConfigDomainSearch.png create mode 100644 java/tags/dns-2.0.1/src/books/users-guide/images/WSConfigMXRecords.png create mode 100644 java/tags/dns-2.0.1/src/books/users-guide/images/WSConfigSOARecords.png create mode 100644 java/tags/dns-2.0.1/src/books/users-guide/images/dnsconfigsetting.png create mode 100644 java/tags/dns-2.0.1/src/books/users-guide/images/winservices.png create mode 100644 java/tags/dns-2.0.1/src/books/users-guide/preface.apt create mode 100644 java/tags/dns-2.0.1/src/descriptors/distribution.xml create mode 100644 java/tags/dns-2.0.1/src/logs/wrapper.txt create mode 100644 java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/AbstractDNSStore.java create mode 100644 java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/ConfigServiceDNSStore.java create mode 100644 java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/DNSError.java create mode 100644 java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/DNSException.java create mode 100644 java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/DNSResponder.java create mode 100644 java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/DNSResponderTCP.java create mode 100644 java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/DNSResponderUDP.java create mode 100644 java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/DNSServer.java create mode 100644 java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/DNSServerFactory.java create mode 100644 java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/DNSServerMBean.java create mode 100644 java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/DNSServerSettings.java create mode 100644 java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/DNSSocketServer.java create mode 100644 java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/DNSSocketServerMBean.java create mode 100644 java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/DNSStore.java create mode 100644 java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/ProxyDNSStore.java create mode 100644 java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/RESTServiceDNSStore.java create mode 100644 java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/SocketServerSettings.java create mode 100644 java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/TCPServer.java create mode 100644 java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/UDPServer.java create mode 100644 java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/annotation/ConfigServiceURL.java create mode 100644 java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/annotation/package-info.java create mode 100644 java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/config/DNSServerConfig.java create mode 100644 java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/config/RESTDNSServerConfig.java create mode 100644 java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/config/WSDNSServerConfig.java create mode 100644 java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/config/package-info.java create mode 100644 java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/module/DNSServerConfigModule.java create mode 100644 java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/module/DNSServerModule.java create mode 100644 java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/module/package-info.java create mode 100644 java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/package-info.java create mode 100644 java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/provider/AbstractConfigDNSStoreProvider.java create mode 100644 java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/provider/BasicDNSServerSettingsProvider.java create mode 100644 java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/provider/ConfigServiceDNSStoreProvider.java create mode 100644 java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/provider/ConfigServiceRESTDNSStoreProvider.java create mode 100644 java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/provider/RESTDNSServerConfigProvider.java create mode 100644 java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/provider/WSDNSServerConfigProvider.java create mode 100644 java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/provider/package-info.java create mode 100644 java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/service/DNSServerService.java create mode 100644 java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/service/SimpleServiceRunner.java create mode 100644 java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/service/package-info.java create mode 100644 java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/tools/CertCommands.java create mode 100644 java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/tools/DNSManager.java create mode 100644 java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/tools/DNSRecordCommands.java create mode 100644 java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/tools/DNSRecordParser.java create mode 100644 java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/tools/DNSRecordPrinter.java create mode 100644 java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/tools/DefaultDNSRecordPrinter.java create mode 100644 java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/tools/package-info.java create mode 100644 java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/tools/printers/AbstractRecordPrinter.java create mode 100644 java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/tools/printers/CertRecordPrinter.java create mode 100644 java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/tools/printers/RecordPrinter.java create mode 100644 java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/tools/utils/Action.java create mode 100644 java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/tools/utils/Command.java create mode 100644 java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/tools/utils/CommandDef.java create mode 100644 java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/tools/utils/CommandUsage.java create mode 100644 java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/tools/utils/Commands.java create mode 100644 java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/tools/utils/StringArrayUtil.java create mode 100644 java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/utils/CertUtils.java create mode 100644 java/tags/dns-2.0.1/src/site/apt/releaseNotes.apt create mode 100644 java/tags/dns-2.0.1/src/site/resources/css/site.css create mode 100644 java/tags/dns-2.0.1/src/site/resources/images/logo.png create mode 100644 java/tags/dns-2.0.1/src/site/site.xml create mode 100644 java/tags/dns-2.0.1/src/site/xdoc/index.xml create mode 100644 java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/ConfigServiceDNSStore_configCertPolicyTest.java create mode 100644 java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/ConfigServiceDNSStore_isCertCompliantWithPolicyTest.java create mode 100644 java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/DNSConnectionTest.java create mode 100644 java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/DNSServer_Function_Test.java create mode 100644 java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/MockDNSStore.java create mode 100644 java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/provider/MockConfigDNSStoreProvider.java create mode 100644 java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/provider/MockDNSStoreProvider.java create mode 100644 java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/service/DNSServerService_constructTest.java create mode 100644 java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/tools/CertCommands_addIPKIXCert_Test.java create mode 100644 java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/tools/CertCommands_importPrivateCert_Test.java create mode 100644 java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/tools/CertCommands_importPublicCert_Test.java create mode 100644 java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/tools/CertCommands_listCertsByAddress_Test.java create mode 100644 java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/tools/CertCommands_listCerts_Test.java create mode 100644 java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/tools/CertCommands_removeCert_Test.java create mode 100644 java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/tools/CertRecordCounterPrinter.java create mode 100644 java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/tools/DNSManager_functional_Test.java create mode 100644 java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/tools/DNSRecordCommands_addARecords_Test.java create mode 100644 java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/tools/DNSRecordCommands_addMXRecords_Test.java create mode 100644 java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/tools/DNSRecordCommands_addSAORecords_Test.java create mode 100644 java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/tools/DNSRecordCommands_ensureARecords_Test.java create mode 100644 java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/tools/DNSRecordCommands_ensureMXRecords_Test.java create mode 100644 java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/tools/DNSRecordCommands_ensureSAORecords_Test.java create mode 100644 java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/tools/DNSRecordCommands_getAll_Test.java create mode 100644 java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/tools/DNSRecordCommands_getByRecordId_Test.java create mode 100644 java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/tools/DNSRecordCommands_importRecords_Test.java create mode 100644 java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/tools/DNSRecordCommands_matchARecords_Test.java create mode 100644 java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/tools/DNSRecordCommands_matchAllTypes_Test.java create mode 100644 java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/tools/DNSRecordCommands_matchMXRecords_Test.java create mode 100644 java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/tools/DNSRecordCommands_matchSOARecords_Test.java create mode 100644 java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/tools/DNSRecordCommands_removeRecords_Test.java create mode 100644 java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/tools/MockDNSRecordPrinter.java create mode 100644 java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/util/BaseTestPlan.java create mode 100644 java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/util/ConfigServiceRunner.java create mode 100644 java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/util/DNSRecordUtil.java create mode 100644 java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/util/IPUtils.java create mode 100644 java/tags/dns-2.0.1/src/test/resources/certs/bob.der create mode 100644 java/tags/dns-2.0.1/src/test/resources/certs/certCheckA.p12 create mode 100644 java/tags/dns-2.0.1/src/test/resources/certs/gm2552.der create mode 100644 java/tags/dns-2.0.1/src/test/resources/certs/mshost.der create mode 100644 java/tags/dns-2.0.1/src/test/resources/certs/ryan.der create mode 100644 java/tags/dns-2.0.1/src/test/resources/certs/umesh.der create mode 100644 java/tags/dns-2.0.1/src/test/resources/dnsrecords/example.domain.com.a create mode 100644 java/tags/dns-2.0.1/src/test/resources/dnsrecords/example.domain.com.corrupt create mode 100644 java/tags/dns-2.0.1/src/test/resources/dnsrecords/example.domain.com.mx create mode 100644 java/tags/dns-2.0.1/src/test/resources/dnsrecords/example.domain.com.soa diff --git a/java/dns/src/site/apt/releaseNotes.apt b/java/dns/src/site/apt/releaseNotes.apt index e07a0a53b..70ccd9991 100644 --- a/java/dns/src/site/apt/releaseNotes.apt +++ b/java/dns/src/site/apt/releaseNotes.apt @@ -6,6 +6,39 @@ Greg Meyer --- +{2.0.1} + + Changes included with release 2.0.1 + + [] + + Enhancements + + * N/A + + [] + + Bug Fixes + + * Switching log statements to out text displayable version of the DNS query type name (instead of the RFC int value). + +{2.0} + + Changes included with release 2.0 + + [] + + Enhancements + + * Updated to support using REST API for config service. + + [] + + Bug Fixes + + * N/A + + {1.5.2} Changes included with release 1.5.2 diff --git a/java/tags/dns-2.0.1/pom.xml b/java/tags/dns-2.0.1/pom.xml new file mode 100644 index 000000000..f53dd27d1 --- /dev/null +++ b/java/tags/dns-2.0.1/pom.xml @@ -0,0 +1,660 @@ + + + org.nhind + 4.0.0 + dns + Direct Project DNS services + 2.0.1 + Direct Project DNS services + 2010 + http://api.nhindirect.org/x/www/api.nhindirect.org/java/site/dns/${project.version} + + + Greg Meyer + GM2552 + gm2552@cerner.com + + owner + + + + + The Direct Project + http://nhindirect.org + + + 2.0.4 + + + http://code.google.com/p/nhin-d/source/browse/#hg/java/dns + scm:hg:https://nhin-d.googlecode.com/hg/nhin-d/java/dns + + + Google Code + http://code.google.com/p/nhin-d/issues/list + + + + New BSD License + http://nhindirect.org/BSDLicense + + + + + com.google.inject + guice + 2.0 + + + commons-logging + commons-logging + 1.1.1 + + + commons-io + commons-io + 1.4 + + + junit + junit + 3.8.2 + test + + + bouncycastle + bcprov-jdk15 + 140 + + + dnsjava + dnsjava + 2.0.8 + + + org.nhind + config-service-client + 2.0 + + + org.springframework + spring-context + + + + + org.nhind + config-model + 1.1 + + + org.nhind + direct-policy + 1.0.1 + + + org.nhind + direct-common + 2.0 + + + org.nhind + agent + 2.1 + + + org.apache.james + apache-jsieve-mailet + + + javax.activation + activation + + + + + org.nhind + config-store + 2.0.0 + + + org.hibernate + hibernate + + + org.hibernate + hibernate-core + + + test + + + org.apache.mina + mina-core + 1.0.2 + test + + + org.mockito + mockito-all + 1.8.5 + test + + + org.mortbay.jetty + jetty-servlet-tester + 6.1.14 + test + + + javax.activation + activation + + + + + + + + org.apache.maven.wagon + wagon-webdav + RELEASE + + + org.apache.maven.wagon + wagon-ssh-external + 1.0-beta-6 + + + org.apache.maven.wagon + wagon-ssh + 1.0-beta-6 + + + + + src/main/resources + + + lib + ${project.basedir}/lib + + + + + src/test/resources + + + lib + ${project.basedir}/lib + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + org.apache.maven.plugins + maven-jxr-plugin + + + org.apache.maven.plugins + maven-dependency-plugin + + + copy + compile + + copy + + + + + org.nhind + config-service + 2.2 + war + true + config-service.war + + + + ${project.basedir}/war + + true + true + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + testCompile + + compile + + + + true + true + true + UTF-8 + 1.8 + 1.8 + + + + org.apache.maven.plugins + maven-source-plugin + 2.0.3 + + + + jar + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + true + + + + + + org.apache.maven.plugins + maven-jar-plugin + 2.2 + + + + test-jar + + + + + + org.codehaus.mojo + appassembler-maven-plugin + 1.3.1 + + + generate-jsw-scripts + package + + ${project.build.directory}/appassembler + jsw/DirectDNSServer/lib + + .sh + + jsw/DirectDNSServer/lib + flat + + 256M + 1024M + + java.security.policy=conf/policy.all + org.apache.commons.logging.Log=org.apache.commons.logging.impl.SimpleLog + org.apache.commons.logging.simplelog.defaultlog=info + org.nhindirect.dns.CertPolicyName=DNSCertPolicy + + + + + DirectDNSServer + org.nhindirect.dns.service.SimpleServiceRunner + + -p + 53 + -b + 0.0.0.0 + -u + http://localhost:8081/config-service/ConfigurationService + -m + SERVER + + + jsw + + + + jsw + + linux-x86-32 + linux-x86-64 + windows-x86-32 + solaris-x86-32 + macosx-universal-32 + + + + set.default.REPO_DIR + lib + + + wrapper.logfile + logs/wrapper.txt + + + + + + + + + + generate-daemons + create-repository + + + + + ${project.build.directory}/appassembler/app/DNSMgmtConsole + lib + flat + -Dorg.apache.commons.logging.Log=org.apache.commons.logging.impl.SimpleLog -Dorg.apache.commons.logging.simplelog.defaultlog=error + + + + org.nhindirect.dns.tools.DNSManager + DNSMgmtConsole + + + + + + assemble + + + + + + + org.apache.maven.plugins + maven-antrun-plugin + + + create-logs + package + + + + + + + + + run + + + + books + pre-site + + + + + + + + + + + + run + + + + + + org.apache.maven.doxia + doxia-maven-plugin + 1.1.3 + + + pre-site + + render-books + + + + + + + src/books/users-guide + src/books/users-guide.xml + + + xdoc + + + + + + + + org.codehaus.plexus + plexus-utils + 1.5.12 + + + org.apache.maven.doxia + doxia-module-confluence + 1.1.3 + + + + + org.apache.maven.plugins + maven-assembly-plugin + 2.2 + + + package + + single + + + false + false + dnsServices-${project.version} + + src/descriptors/distribution.xml + + + + + + + + + + + org.apache.maven.plugins + maven-release-plugin + 2.0 + + scm:hg:https://nhin-d.googlecode.com/hg/nhin-d/java/tags + + + + org.apache.maven.plugins + maven-site-plugin + 2.1.1 + + + commons-httpclient + commons-httpclient + 3.1 + + + commons-logging + commons-logging + + + + + + + + + + + + org.apache.maven.plugins + maven-site-plugin + 2.1.1 + + + org.apache.maven.plugins + maven-project-info-reports-plugin + 2.2 + + + org.apache.maven.plugins + maven-javadoc-plugin + 2.6.1 + + -Xdoclint:none + UTF-8 + UTF-8 + true + true + true + 1.8 + public + + org.nhindirect.dns.tools.utils + + + + + org.apache.maven.plugins + maven-pmd-plugin + + 1.8 + + + + org.apache.maven.plugins + maven-surefire-report-plugin + + + org.apache.maven.plugins + maven-jxr-plugin + + + org.codehaus.mojo + clirr-maven-plugin + + info + + + + org.codehaus.mojo + findbugs-maven-plugin + 1.2 + + Max + ${project.basedir}/report/findbugs-exclude.xml + + + + org.codehaus.mojo + taglist-maven-plugin + + + FIXME + TODO + WARN + @deprecated + + + + + com.atlassian.maven.plugins + maven-clover2-plugin + 3.0.2 + + + ${project.basedir}/../licenses/clover.license + + + + + + + + nhind-site + NHIN Direct API publication site + sftp://api.nhindirect.org/x/www/api.nhindirect.org/java/site/dns/${project.version} + + + sonatype-snapshot + Sonatype OSS Maven SNAPSHOT Repository + https://oss.sonatype.org/content/repositories/snapshots/ + false + + + sonatype-release + Sonatype OSS Maven Release Repositor + https://oss.sonatype.org/service/local/staging/deploy/maven2/ + false + + + diff --git a/java/tags/dns-2.0.1/report/findbugs-exclude.xml b/java/tags/dns-2.0.1/report/findbugs-exclude.xml new file mode 100644 index 000000000..25a582583 --- /dev/null +++ b/java/tags/dns-2.0.1/report/findbugs-exclude.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/java/tags/dns-2.0.1/src/books/users-guide.xml b/java/tags/dns-2.0.1/src/books/users-guide.xml new file mode 100644 index 000000000..736e3be3a --- /dev/null +++ b/java/tags/dns-2.0.1/src/books/users-guide.xml @@ -0,0 +1,40 @@ + + + + users-guide + DNS Services Users Guide + + + preface + Preface + +
+ preface +
+
+
+ + deploy + Deployment Guide + +
+ deploy-intro +
+
+ dns-serv-depl +
+
+ dns-proto-config +
+
+ dns-record-config +
+
+ dns-godad-config +
+
+
+
+
\ No newline at end of file diff --git a/java/tags/dns-2.0.1/src/books/users-guide/deploy-intro.confluence b/java/tags/dns-2.0.1/src/books/users-guide/deploy-intro.confluence new file mode 100644 index 000000000..696d07e64 --- /dev/null +++ b/java/tags/dns-2.0.1/src/books/users-guide/deploy-intro.confluence @@ -0,0 +1,27 @@ +h1. About this Chapter + +This chapters describes the different deployment and configuration options for the DNS services. + +h2. Purpose and Overview + +The Direct Project Simple Health [Transport|http://wiki.directproject.org/Simple+Health+Transport] specification outlines requirements for using DNS to resolve public certificates for the purpose of message encryption. Although the requirements are not enforced with the MUST verbiage, the underlying goal is to define a universal and ubiquitous method to distribute and resolve public certificates. + +Several open source and commercial DNS services are already widely available, however they differ in both their support of the full DNS specification and the tooling available to configure and manage the DNS solution. The challenge is finding a solution the meets the needs of the domain both in terms of functional support and ease of use (good tooling). So why does the Direct Project provides its own DNS solution when are there already so many viable options. The simple answer is the use of the CERT record type. Investigation has shown that many popular, commercially available DNS services (both organizational and third party hosting, ex: GoDaddy) do not support CERT records. Conversely many of the open source services support a broader range of the DNS spec, however tooling and configuration support is limited. In some solutions, tooling is limited to editing a raw configuration file with a plain text editor. Text editor support is not a viable solution for large deployments where thousands of entries may exist. + +The Direct Project DNS services are not intended to be a one stop shop for all DNS needs, but to compliment existing DNS service and fill the functional gaps not provided by existing solutions. In practice, the functional abilities of the Direct DNS services are limited to meet a small number of use cases. Specifically they provide a simple solution to respond to public cert requests (and a few other request types) and tooling to manage certificate storage and DNS record entries. In addition the services not intended to host primary DNS zones; instead they are deployed in a sub zone that is generally intended only for Direct Project implementations. + +h2. Service Deployment + +DNS deployment consists of installing the services using operating system specific service installation methods and configuring the location of the DNS record store. + +* [DNS Service Deployment|./dns-serv-depl.html] + +h2. Service Configuration + +Configuration is broken into two logical part: configuring the DNS specific protocol parameters and configuring/managing DNS records. The latter configuration may be dependent on the DNS hosting solution of the primary domain name. + +* [DNS Protocol Configuration|./dns-proto-config.html] + +* [DNS Record Configuration|./dns-record-config.html] + +* [GoDaddy Domain Hosting|./dns-godad-config.html] \ No newline at end of file diff --git a/java/tags/dns-2.0.1/src/books/users-guide/dns-godad-config.confluence b/java/tags/dns-2.0.1/src/books/users-guide/dns-godad-config.confluence new file mode 100644 index 000000000..97217ef9a --- /dev/null +++ b/java/tags/dns-2.0.1/src/books/users-guide/dns-godad-config.confluence @@ -0,0 +1,6 @@ +h1. Integration With GoDaddy + +[GoDaddy|http://www.godaddy.com] is a popular domain hosting service. You can use your existing GoDaddy domain to host a Direct message domain. Typically you will create a sub domain/zone from your registered domain and configure GoDaddy to use your DNS server as the name server. The following links describe creating a sub domain from your existing domain and how to configure GoDaddy to use your name server with the sub domain. + +* [Direct Project DNS Configuration Guide|http://wiki.directproject.org/DNS+Configuration+Guide] +* [DNS Configuration On GoDaddy|http://wiki.directproject.org/Configuring+DNS+on+GoDaddy] \ No newline at end of file diff --git a/java/tags/dns-2.0.1/src/books/users-guide/dns-proto-config.confluence b/java/tags/dns-2.0.1/src/books/users-guide/dns-proto-config.confluence new file mode 100644 index 000000000..ea1146891 --- /dev/null +++ b/java/tags/dns-2.0.1/src/books/users-guide/dns-proto-config.confluence @@ -0,0 +1,22 @@ +h1. DNS Protocol Configuration + +The DNS server has several configuration parameters for tuning the DNS communication protocol. Some of the basic settings are configurable in the services configuration [file|./dns-serv-depl.html], however the service can be tuned at a more granular level using the configuration UI web tool. Settings in the configuration UI take precedence over those in the configuration file. + +h2. Protocol Configuration Parameters + +Protocol configuration parameters are set using the same section of the configuration UI web tool as SMTP server [settings|../../../gateway/4.0/users-guide/smtp-depl-wsconfig#Anchors]. + +!images/dnsconfigsetting.png! + + +||Setting||Description|| +|DNSServerBindings|The IP addressed on the local machine that the service will bind to. Multiple IP addresses are separated with a comma. The default is 0.0.0.0 which means the service will bind to all IP addresses on the machine (including the loopback address of 127.0.0.1)| +|DNSServerPort|The IP port that the DNS server will use for listening for DNS queries. The default is 53. \\NOTE: Some operating systems may require the service to run with elevated account privileges to open ports in this range.| +|DNSServerMaxRequestSize|The maximum size in bytes for an query request. The default is 16K.| +|DNSServerMaxConnectionBacklog|The maximum number of connections that are in the IP socket accept backlog. Socket backlog is only relevant for TCP session based connections. Setting this value to high can overload the IP stack or result in DNS client timeouts. The default value is 64.| +|DNSServerMaxActiveRequests|The maximum number of concurrent requests that can be processed by the server at any give time. Setting this value to high may result in overloading the system. Setting this value to low can limit throughput. The default is 64.| +|DNSServerMaxOutstandingRequests|The maximum number of requests that can be accepted by the server, but not yet committed to a processing thread. Setting this value to high may result in DNS clients timing out due to outstanding requests waiting to long for a processing thread. The default is 16 requests.| +|DNSServerMaxRequestBuffer|The maximum size request buffer size in bytes. The default value is 1024 bytes.| +|DNSServerSendTimeout|The socket timeout in milliseconds for sending responses. Setting this value to high can result in performance degradation if multiple clients abandon their sessions. Setting this value to low can result in clients not receiving responses in high latency environments. The default value is 5000 milliseconds.| +|DNSServerRecieveTimeout|The socket timeout in milliseconds for receiving or reading request. Setting this value to high can result in performance degradation if multiple clients abandon their sessions. Setting this value to low can result in the server not fully reading request data in high latency environments. The default value is 5000 milliseconds.| +|DNSServerSocketCloseTimeout|The timeout in milliseconds for closing a socket connection. The default value is 5000 milliseconds.| \ No newline at end of file diff --git a/java/tags/dns-2.0.1/src/books/users-guide/dns-record-config.confluence b/java/tags/dns-2.0.1/src/books/users-guide/dns-record-config.confluence new file mode 100644 index 000000000..9ea175ecc --- /dev/null +++ b/java/tags/dns-2.0.1/src/books/users-guide/dns-record-config.confluence @@ -0,0 +1,152 @@ +h1. DNS Record Configuration + +The primary purpose of the DNS service is to respond to DNS queries with appropriate DNS record responses. By design, the DNS service can store any type of DNS record defined by the DNS RFC, however it is tuned only to respond to a few types of records. + +The DNS service stores all DNS records in the Direct Project configurations services and accesses them over the web service interface. The the public facing DNS interface does not allow for records to be added or changed in anyway. Instead, the configuration UI tool is used to manage and maintain DNS records. + +h2. Managing Records + +The configuration UI and config management command console tools are used to add, update, and delete all record types. + +h3. Config UI + +The Configuration UI tool is used to managed DNS records in the web UI. To managed records load the Configuration UI tool is a browser, login, and press the DNS Entries button. + +!images/WSConfigDomainSearch.png! + +!images/WSConfigDNSHome.png! + +h3. Config Management Console + +*NOTE* The DNS Management console has been deprecated and all DNS configuration commands are now available in the Config Management Console. + +The Config Management console is command line tool for managing all configuration aspects of the DirectProject Java reference implementation including DNS records. To run the console: + +# Launch a command shell and navigate to the ConfigMgmtConsole/bin directory. +# Run the command _ConfigMgmtConsole_ (./ConfigMgmtConsole for unix based systems). + +The console connects to the Direct Project configuration service and manages records using the configuration service proxy module. By default it uses the url _http://localhost:8081/config-service/ConfigurationService_, but can be overridden using the configURL command line parameter. + +{code} + ConfigMgmtConsole configURL http://confserver/config-service/ConfigurationService +{code} + +*NOTE* The configURL must be the first parameter on the command line to change the configuration service URL. + +The management console can either take command directly on the command line or run interactively. If no arguments or commands are passed on the command line (with the exception of the configURL), then console runs interactively. For a list of supported commands, simple type _help all_ either as command line arguments or when running interactively. + +h3. SOA Records + +The [SOA|http://support.microsoft.com/kb/163971] (start of authority) record defines (amongst other attributes) the host that is the authoritative server for a DNS zone/domain. Each domain managed by the DNS server should have a SOA record associated with it. + +*Config UI* + +SOA records are added by entering the required SOA record information in the Config UI tool on the _DNS Resolver Configuration_ page. + +!images/WSConfigSOARecords.png! + +*Management Console* + +SOA records are added using either of the following commands: +* DNS_SOA_ADD +* DNS_SOA_ENSURE + +{code} + Add a new SOA dns record if an identical one does not exist. + domainname primarysourcedomain responsibleemail serialnumber ttl [refresh] [retry] [expire] [minimum] + domainname: The domain name of the name server that was the primary source for this zone + responsibleemail: Email mailbox of the hostmaster + serialnumber: Version number of the original copy of the zone. + ttl: time to live in seconds, 32bit int + [refresh]: Number of seconds before the zone should be refreshed. + [retry]: Number of seconds before failed refresh should be retried. + [expire]: Number of seconds before records should be expired if not refreshed + [minimum]: Minimum TTL for this zone. +{code} + +h3. A Records + +A records maps a host name to an IP4 address. + +*Config UI* + +A records are added by entering the required A record information in the Config UI tool on the _DNS Resolver Configuration_ page. + +!images/WSConfigARecords.png! + +*Management Console* + +A records are added using either of the following commands: +* DNS_ANAME_ADD +* DNS_ANAME_ENSURE + +{code} + Add a new A dns record if an identical one does not exist. + hostname ipaddress ttl + hostname: host name for the record + ipaddress: IP address in dot notation + ttl: time to live in seconds, 32bit int +{code} + +h3. MX Records + +MX records map a domain to one of more servers or message transfer agents for that domain. The records are returned are generally are cononical names which means they must have A or CNAME records in the DNS server. + +*Config UI* + +MX records are added by entering the required MX record information in the Config UI tool on the _DNS Resolver Configuration_ page. + +!images/WSConfigMXRecords.png! + +*Management Console* + +A records are added using either of the following commands: +* DNS_MX_ADD +* DNS_MX_ENSURE + +{code} + Adds a new MX dns record if an identical one does't already exist. + domainname exchange ttl [priority] + domainname: email domain name for the record + exchange: smtp server host name for the domain + ttl: time to live in seconds + [priority]: short value indicating preference of the record +{code} + +h3. CERT Records +[Cert|http://tools.ietf.org/html/rfc4398] records store a certificate with a DNS name entry. The Direct Project DNS server only stores x.509 public certificates. *NOTE* The DNS server does not automatically resolve organizational level certificate resolution as defined by the security and trust specification; it only responds with the exact matches to a DNS query. The DNS client is responsibly for sending an org level query if the server does not respond with certificate to user level certificate query. + +Unlike all other DNS record types, the DNS service uses the configuration services certificate store to find certificates. Certificates are added to the store using the certificates button in the configuration UI web tool. See the SMTP web deployment [guide|../../../gateway/4.0/users-guide/smtp-depl-wsconfig#PrivateCertStore] for instruction on importing certificates into the configuration UI tool. *NOTE* Even though certificates in your domain will have the private key stored with the certificates, the DNS service only returns public certificate information for CERT record queries. + +h2. Required DNS Configuration + +Typically your domain registration service will allow you to define an NS (name service) record that will point the server running your DNS service. Your DNS service is then responsible for handling all queries for your domain(s). Depending on your deployment, you will need the following minimal set of DNS records for your Direct messaging implementation: + +* An SOA record for each domain managed by your server. +* An A record for each of you email servers. +* An MX record for each of you email servers server a domain. +* Either an org level certificate for each domain or individual certificates per email address (this should already be configured as part of you SMTP agent/gateway installation). + +h2. Policy Enforcement + +It is possible for multiple certificates to exist for a specific address of domain. This is especially true in circumstances when single use certificates are used (i.e. one certificate for encryption and one certificate for digital signatures). In these cases, it likely that you will not want to publish all certificates for a addresses or domains over DNS. The Direct DNS server allow you to filter published certificate using the Policy Enablement [module|../../../direct-policy/1.0/users-guide/index.html]. + +The Direct DNS server's configuration file (conf/wrapper.conf) defines a system parameter named _org.nhindirect.dns.CertPolicyName_ with a default value of _DNSCertPolicy_. This policy name indicates the name of the policy that the server will use for filtering certificates. If this parameter does not exist or references a policy that does not exist in the system, the server will fall back to not filtering certificates. + +h3. Policy Configuration + +To configure a policy for certificate filtering, first define a policy using the appropriate [syntax|../../../direct-policy/1.0/users-guide/tools-simpleTextV1.html#Simple_Text_Lexicon_Version_I]. As an example, let's say you want to only publish certificates that assert the key encipherment bit (which is probably the primary use case for using policy enforcement in the DNS server. Below is the syntax for this policy: + +{code} +(X509.TBS.EXTENSION.KeyUsage & 32) > 0 +{code} + +Now let's say this policy is saved in a file named KeyEnc.pol. To add this policy to the system, run the Management Console application and execute the following sample command. + +{code} +IMPORTPOLICY DNSCertPolicy KeyEnc.pol +{code} + +This will create a policy named _DNSCertPolicy_ which contains the policy defined in the file KeyEnc.pol. When the DNS server launches, it will look for a policy with the name _DNSCertPolicy_ (remember the policy name is configured with system parameter org.nhindirect.dns.CertPolicyName in the wrapper.conf file) in the system and will apply the policy to all certificates that queried. + +If you choose, you can change the name of the policy that you want the DNS server to use by changing the value of the _org.nhindirect.dns.CertPolicyName_ parameter in the wrapper.conf file. \ No newline at end of file diff --git a/java/tags/dns-2.0.1/src/books/users-guide/dns-serv-depl.confluence b/java/tags/dns-2.0.1/src/books/users-guide/dns-serv-depl.confluence new file mode 100644 index 000000000..78ced12a1 --- /dev/null +++ b/java/tags/dns-2.0.1/src/books/users-guide/dns-serv-depl.confluence @@ -0,0 +1,166 @@ +h1. DNS Service Deployment + +The DNS services are deployable on a number of different operating environments and can be launched either interactively (for debugging) or as a background service. Although each OS configures and runs background services differently, the DNS service package contains OS specific script files that normalize the deployment process. + +h2. Service Package Layout + +The DNS services are assembled and packaged as a zipped tar ball. In side the package, the services are bundled using in the following directory structure. + +{code} + +-- DirectDNSServer + +-- bin + +-- conf + +-- lib + +-- logs +{code} + +*Directory Contents* + +* bin - Contains installation and service launching/shutdown scripts. +* conf - Contains service configuration files. +* lib - Contains the binary code packages. +* logs - Contains service execution log files. + +h2. Service Installation + +Installation of the DNS services is slightly different for each operating system, however each case uses the same assembly. To install, first download the Direct Project [assembly|http://repo2.maven.org/maven2/org/nhind/direct-project-stock] and unpack the contents into the desired location using your archiver of choice (tar, WinZip, WinRar, File Roller, etc). + +h3. Windows Installation + +The DNS server runs as Windows service on Windows deployments. To install as a service: + +# Launch a command shell and navigate to the DirectDNSServer/bin directory. +# Run the command _DirectDNSServer install_ + +This will install as the server as a windows service that starts automatically on system boot up. You should be able to see the service in the Windows service control panel (services.msc). + +!images/winservices.png! + +To start the server, use one of the following methods: + +* Right click on the service in the service control panel and click _start_. +* In a command shell, run the command _DirectDNSServer start_. + +To stop the server, use one of the following methods: + +* Right click on the service in the service control panel and click _stop_. +* In a command shell, run the command _DirectDNSServer stop_. + +To uninstall the server: + +# Stop the service using of the method described above. +# Launch a command shell and navigate to the DirectDNSServer/bin directory. +# Run the command _DirectDNSServer remove_ + +*NOTE:* The service runs as process named _wrapper-windows-x86-32.exe_. If for some reason the service hangs or will not stop, you can terminate it manually by killing this process. + +h3. Linux Installation + +The DNS server runs as a background process on Linux based systems and can be optionally configured to run as a service daemon. To start the service manually: + +# Open a terminal shell and navigate to the DirectDNSServer/bin directory. +# Run the command _./DirectDNSServer start_ + +*NOTE:* If you get an error of "Permissioned denied" you will need to set the executable flag on the script files: + +{code} + chmod +x DirectDNSServer + chmod +x wrapper-linux-x86-32 (or wrapper-linux-x86-64 depending if you are using a 64 or bit linux installation). +{code} + +To stop the server: + +# Open a terminal shell and navigate to the DirectDNSServer/bin directory. +# Run the command _DirectDNSServer stop_. + +You can also optionally configure the server as a service. There are different ways to do this depending on your linux distribution. On possibility is to create a script file in the /etc/init.d directory. + +Assuming you have deployed the server in the /opt directory and you are running Ubuntu, create the file /etc/init.d/DirectDNSServer using the editor of you choice paste the following content: + +{code} + # DirectDNSServer auto-start + # + # description: Auto-starts the DirectDNSServer + + case $1 in + start) + sh /opt/DirectDNSServer/bin/DirectDNSServer start + ;; + stop) + sh /opt/DirectDNSServer/bin/DirectDNSServer stop + ;; + restart) + sh /opt/DirectDNSServer/bin/DirectDNSServer start + sh /opt/DirectDNSServer/bin/DirectDNSServer stop + ;; + + esac + exit 0 +{code} + +Make the script executable using the following command: + +{code} +sudo chmod 755 /etc/init.d/DirectDNSServer +{code} + +You can then start the service by running the command: + +{code} +service DirectDNSServer start +{code} + +Conversely you can stop the service by running the command: + +{code} +service DirectDNSServer stop +{code} + +h3. Running Interactively + +For debugging or troubleshooting purposes, you may need to run the service interactively. Running interactively is the same across all platforms. + +# Open a terminal shell and navigate to the DirectDNSServer/bin directory. +# Run the command _DirectDNSServer console_. + +The service will output all logging to the current console and the log file. To terminate the interactive service, simply press _CTRL+C_ (Control C). + +h2. Service Deployment Configuration + +The service deployment is configured using a file named _wrapper.conf_ found in the ./conf directory of the service's directory structure. The service script files read this configuration file to set runtime attributes such as classpath, logging locations and thresholds, JVM arguments, and application arguments. The service itself is just a plain Java application, but is wrapped by a series of deployment classes that are intialized and launched by the service script. + +The configuration file in most cases does not need a lot of modification, however there a few settings that will need adjustment depending on your deployment. + +{code} +# Java Additional Parameters +wrapper.java.additional.1=-Djava.security.policy=conf/policy.all +wrapper.java.additional.2=-Dorg.apache.commons.logging.Log=org.apache.commons.logging.impl.SimpleLog +wrapper.java.additional.3=-Dorg.apache.commons.logging.simplelog.defaultlog=info +. +. +. +# Application parameters. Add parameters as needed starting from 1 +wrapper.app.parameter.1=org.nhindirect.dns.service.SimpleServiceRunner +wrapper.app.parameter.2=-p +wrapper.app.parameter.3=53 +wrapper.app.parameter.4=-b +wrapper.app.parameter.5=0.0.0.0 +wrapper.app.parameter.6=-u +wrapper.app.parameter.7=http://localhost:8081/config-service/ConfigurationService +wrapper.app.parameter.8=-m +wrapper.app.parameter.9=SERVER +{code} + +Typical settings that may need adjustment. + +||Setting||Description|| +|-Dorg.apache.commons.logging.simplelog.defaultlog|The logging threshold. Valid value: \\ \\fatal: Severe errors that cause premature termination\\error: Other runtime errors or unexpected conditions.\\warn: Use of deprecated APIs, poor use of API, 'almost' errors, other runtime situations that are undesirable or unexpected, but not necessarily "wrong".\\info (default): Interesting runtime events such as startup/shutdown.\\debug: Detailed information on flow of through the system.\\trace: Even more detailed information such as entering and exiting methods.| +|wrapper.app.parameter.2=-p|The IP port that the DNS server will use for listening for DNS queries. The default is 53. \\NOTE: Some operating systems may require the service to run with elevated account privileges to open ports in this range.| +|wrapper.app.parameter.4=-b|The IP addressed on the local machine that the service will bind to. Multiple IP addresses are separated with a comma. The default is 0.0.0.0 which means the service will bind to all IP addresses on the machine (including the loopback address of 127.0.0.1)| +|wrapper.app.parameter.6=-u|This is the URL of the location of the DNS records. Generally this will be the URL of the Direct Project configuration web service.| + +Some of these parameters can be over ridden with settings in the configuration service. See the protocol [configuration|./dns-proto-config.html] for more details. + +h2. Service Logging + +The service logs are written to the file _wrapper.txt_ found in the ./logs directory of the service's directory structure. Logging threshold configuration is described in the previous section. \ No newline at end of file diff --git a/java/tags/dns-2.0.1/src/books/users-guide/images/WSConfigARecords.png b/java/tags/dns-2.0.1/src/books/users-guide/images/WSConfigARecords.png new file mode 100644 index 0000000000000000000000000000000000000000..0ee8bc906ae1b0ac6a4b45751ebe69993c282951 GIT binary patch literal 6786 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 wxT>=Ct#ogWAp=gQ( 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 T34Z)Qr^!^Fs4F5tFW&wKE#j>Z literal 0 HcmV?d00001 diff --git a/java/tags/dns-2.0.1/src/books/users-guide/images/WSConfigMXRecords.png b/java/tags/dns-2.0.1/src/books/users-guide/images/WSConfigMXRecords.png new file mode 100644 index 0000000000000000000000000000000000000000..825dc28d4d9b18f10453f150374795e9e004a1fd GIT binary patch literal 8232 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 literal 0 HcmV?d00001 diff --git a/java/tags/dns-2.0.1/src/books/users-guide/images/WSConfigSOARecords.png b/java/tags/dns-2.0.1/src/books/users-guide/images/WSConfigSOARecords.png new file mode 100644 index 0000000000000000000000000000000000000000..419313ea11408453aa1df5ce9b79df33ad0490e1 GIT binary patch literal 17074 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 literal 0 HcmV?d00001 diff --git a/java/tags/dns-2.0.1/src/books/users-guide/images/dnsconfigsetting.png b/java/tags/dns-2.0.1/src/books/users-guide/images/dnsconfigsetting.png new file mode 100644 index 0000000000000000000000000000000000000000..3f773620a37ad3560e51e8a4bc4fd1a2b492f690 GIT binary patch literal 42009 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 z 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 + assembly + DirectDNSServices + + tar.gz + + + + ${project.build.directory}/appassembler/jsw/DirectDNSServer + /DirectDNSServer + + + ${project.build.directory}/appassembler/app/DNSMgmtConsole + /DNSMgmtConsole + + + diff --git a/java/tags/dns-2.0.1/src/logs/wrapper.txt b/java/tags/dns-2.0.1/src/logs/wrapper.txt new file mode 100644 index 000000000..e69de29bb diff --git a/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/AbstractDNSStore.java b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/AbstractDNSStore.java new file mode 100644 index 000000000..0d772823b --- /dev/null +++ b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/AbstractDNSStore.java @@ -0,0 +1,268 @@ +package org.nhindirect.dns; + +import java.security.Security; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.nhindirect.policy.PolicyExpression; +import org.nhindirect.policy.PolicyFilter; +import org.xbill.DNS.DClass; +import org.xbill.DNS.Flags; +import org.xbill.DNS.Header; +import org.xbill.DNS.InvalidTypeException; +import org.xbill.DNS.Message; +import org.xbill.DNS.Name; +import org.xbill.DNS.Opcode; +import org.xbill.DNS.RRset; +import org.xbill.DNS.Rcode; +import org.xbill.DNS.Record; +import org.xbill.DNS.Section; +import org.xbill.DNS.Type; + +public abstract class AbstractDNSStore implements DNSStore +{ + protected static final Log LOGGER = LogFactory.getFactory().getInstance(AbstractDNSStore.class); + + protected static final String DNS_CERT_POLICY_NAME_VAR = "org.nhindirect.dns.CertPolicyName"; + + protected static final String DEFAULT_JCE_PROVIDER_STRING = "BC"; + protected static final String JCE_PROVIDER_STRING_SYS_PARAM = "org.nhindirect.dns.JCEProviderName"; + + protected Map soaRecords = null; + + protected PolicyFilter polFilter = null; + protected PolicyExpression polExpression = null; + + static + { + Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider()); + } + + /** + * Gets the configured JCE crypto provider string for crypto operations. This is configured using the + * -Dorg.nhindirect.dns.JCEProviderName JVM parameters. If the parameter is not set or is empty, + * then the default string "BC" (BouncyCastle provider) is returned. By default the agent installs the BouncyCastle provider. + * @return The name of the JCE provider string. + */ + public static String getJCEProviderName() + { + String retVal = System.getProperty(JCE_PROVIDER_STRING_SYS_PARAM); + + if (retVal == null || retVal.isEmpty()) + retVal = DEFAULT_JCE_PROVIDER_STRING; + + return retVal; + } + + /** + * Overrides the configured JCE crypto provider string. If the name is empty or null, the default string "BC" (BouncyCastle provider) + * is used. + * @param name The name of the JCE provider. + */ + public static void setJCEProviderName(String name) + { + if (name == null || name.isEmpty()) + System.setProperty(JCE_PROVIDER_STRING_SYS_PARAM, DEFAULT_JCE_PROVIDER_STRING); + else + System.setProperty(JCE_PROVIDER_STRING_SYS_PARAM, name); + } + + /** + * {@inheritDoc} + */ + @SuppressWarnings("unchecked") + @Override + public Message get(Message request) throws DNSException + { + LOGGER.trace("get(Message) Entered"); + /* for testing time out cases + try + { + Thread.sleep(1000000); + } + catch (Exception e) + { + + } + */ + if (request == null) + throw new DNSException(DNSError.newError(Rcode.FORMERR)); + + Header header = request.getHeader(); + if (header.getFlag(Flags.QR) || header.getRcode() != Rcode.NOERROR) + throw new DNSException(DNSError.newError(Rcode.FORMERR)); + + if (header.getOpcode() != Opcode.QUERY) + throw new DNSException(DNSError.newError(Rcode.NOTIMP)); + + Record queryRecord = request.getQuestion(); + + if (queryRecord == null || queryRecord.getDClass() != DClass.IN) + { + throw new DNSException(DNSError.newError(Rcode.NOTIMP)); + } + + Name name = queryRecord.getName(); + int type = queryRecord.getType(); + String typeString = null; + try { + typeString = Type.string(type); + } catch(InvalidTypeException e) { + } + + if (LOGGER.isDebugEnabled()) + { + StringBuilder builder = new StringBuilder("Received Query Request:"); + builder.append("\r\n\tName: " + name.toString()); + builder.append("\r\n\tType: " + (typeString == null ? type : typeString)); + builder.append("\r\n\tDClass: " + queryRecord.getDClass()); + LOGGER.debug(builder.toString()); + } + + LOGGER.info("Process record for DNS request type " + (typeString == null ? type : typeString) + " and name " + name.toString()); + + Collection lookupRecords= null; + switch (type) + { + case Type.A: + case Type.MX: + case Type.SOA: + case Type.SRV: + case Type.NS: + case Type.CNAME: + { + try + { + final RRset set = processGenericRecordRequest(name.toString(), type); + + if (set != null) + { + lookupRecords = new ArrayList(); + Iterator iter = set.rrs(); + while (iter.hasNext()) + lookupRecords.add(iter.next()); + } + + } + catch (Exception e) + { + throw new DNSException(DNSError.newError(Rcode.SERVFAIL), "DNS service proxy call failed: " + e.getMessage(), e); + } + break; + } + case Type.CERT: + { + final RRset set = processCERTRecordRequest(name.toString()); + + if (set != null) + { + lookupRecords = new ArrayList(); + Iterator iter = set.rrs(); + while (iter.hasNext()) + lookupRecords.add(iter.next()); + } + + break; + } + case Type.ANY: + { + + Collection genRecs = processGenericANYRecordRequest(name.toString()); + RRset certRecs = processCERTRecordRequest(name.toString()); + + if (genRecs != null || certRecs != null) + { + lookupRecords = new ArrayList(); + if (genRecs != null) + lookupRecords.addAll(genRecs); + + if (certRecs != null) + { + Iterator iter = certRecs.rrs(); + while (iter.hasNext()) + lookupRecords.add(iter.next()); + } + } + + break; + } + default: + { + LOGGER.debug("Query Type " + (typeString == null ? type : typeString) + " not implemented"); + throw new DNSException(DNSError.newError(Rcode.NOTIMP), "Query Type " + (typeString == null ? type : typeString) + " not implemented"); + } + } + + + if (lookupRecords == null || lookupRecords.size() == 0) + { + LOGGER.debug("No records found."); + return null; + } + + final Message response = new Message(request.getHeader().getID()); + response.getHeader().setFlag(Flags.QR); + if (request.getHeader().getFlag(Flags.RD)) + response.getHeader().setFlag(Flags.RD); + response.addRecord(queryRecord, Section.QUESTION); + + + final Iterator iter = lookupRecords.iterator(); + while (iter.hasNext()) + response.addRecord(iter.next(), Section.ANSWER); + + // we are authoritative only + response.getHeader().setFlag(Flags.AA); + // look for an SOA record + final Record soaRecord = checkForSoaRecord(name.toString()); + if (soaRecord != null) + response.addRecord(soaRecord, Section.AUTHORITY); + + LOGGER.trace("get(Message) Exit"); + + return response; + } + + /** + * Processes all DNS requests except CERT records. + * @param name The record name. + * @param type The record type. + * @return Returns a set of record responses to the request. + * @throws DNSException + */ + protected abstract RRset processGenericRecordRequest(String name, int type) throws DNSException; + + /** + * Processes all DNS CERT requests. + * @param name The record name. In many cases this a email address. + * @return Returns a set of record responses to the request. + * @throws DNSException + */ + protected abstract RRset processCERTRecordRequest(String name) throws DNSException; + + protected abstract Collection processGenericANYRecordRequest(String name) throws DNSException; + + protected abstract Record checkForSoaRecord(String questionName); + + protected boolean isCertCompliantWithPolicy(X509Certificate cert) + { + // if no policy has been set, then always return true + if (this.polFilter == null) + return true; + + try + { + return this.polFilter.isCompliant(cert, this.polExpression); + } + catch (Exception e) + { + LOGGER.warn("Error testing certificate for policy compliance. Default to compliant.", e); + return true; + } + } +} diff --git a/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/ConfigServiceDNSStore.java b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/ConfigServiceDNSStore.java new file mode 100644 index 000000000..29614e141 --- /dev/null +++ b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/ConfigServiceDNSStore.java @@ -0,0 +1,412 @@ +/* + Copyright (c) 2010, Direct Project + All rights reserved. + + Authors: + Umesh Madan umeshma@microsoft.com + Chris Lomonico chris.lomonico@surescripts.com + Greg Meyer gm2552@cerner.com + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +Neither the name of The Direct Project (directproject.org) nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +package org.nhindirect.dns; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.net.URL; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.security.cert.X509Certificate; +import java.security.interfaces.RSAKey; + + +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang.StringUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.nhind.config.Certificate; +import org.nhind.config.ConfigurationServiceProxy; +import org.nhind.config.DnsRecord; +import org.nhindirect.config.model.exceptions.CertificateConversionException; +import org.nhindirect.config.model.utils.CertUtils; +import org.nhindirect.dns.annotation.ConfigServiceURL; +import org.nhindirect.policy.PolicyFilterFactory; +import org.nhindirect.policy.PolicyLexicon; +import org.nhindirect.policy.PolicyLexiconParser; +import org.nhindirect.policy.PolicyLexiconParserFactory; +import org.nhindirect.policy.x509.SignatureAlgorithmIdentifier; +import org.xbill.DNS.CERTRecord; +import org.xbill.DNS.DClass; +import org.xbill.DNS.Name; +import org.xbill.DNS.RRset; +import org.xbill.DNS.Rcode; +import org.xbill.DNS.Record; +import org.xbill.DNS.Type; + + +import com.google.inject.Inject; + +/** + * Implementation of the the {@link DNStore} interface that uses the Direct Project configuration web service to store + * DNS records. + * @author Greg Meyer + * @since 1.0 + */ +public class ConfigServiceDNSStore extends AbstractDNSStore +{ + protected static final Log LOGGER = LogFactory.getFactory().getInstance(ConfigServiceDNSStore.class); + + final ConfigurationServiceProxy proxy; + + /** + * Creates a store using the provided URL to lookup DNS records in the configuration service. + * @param serviceURL The URL of the configuration service. + */ + @Inject + public ConfigServiceDNSStore(@ConfigServiceURL URL serviceURL) + { + proxy = new ConfigurationServiceProxy(serviceURL.toString()); + + try + { + configCertPolicy(); + } + catch (DNSException e) + { + throw new IllegalStateException(e); + } + + } + + /** + * Checks to see if a certificate policy has been configured. + */ + protected void configCertPolicy() throws DNSException + { + // check to see if there is a certificate policy set + final String polName = System.getProperty(DNS_CERT_POLICY_NAME_VAR); + if (!StringUtils.isEmpty(polName)) + { + InputStream inStream = null; + LOGGER.info("Certificate policy name " + polName + " has been configured."); + try + { + // get the policy by name + final org.nhind.config.CertPolicy policy = proxy.getPolicyByName(polName); + if (policy == null) + { + LOGGER.warn("Certificate policy " + polName + " could not be found in the system. Falling back to no policy."); + return; + } + + // now compile the policy into an expression + final PolicyLexiconParser parser = PolicyLexiconParserFactory.getInstance(PolicyLexicon.valueOf(policy.getLexicon().getValue())); + inStream = new ByteArrayInputStream(policy.getPolicyData()); + this.polExpression = parser.parse(inStream); + + // now create the filter + this.polFilter = PolicyFilterFactory.getInstance(); + + } + catch (Exception e) + { + // it's OK if can't find the certificate policy that was configured, we'll just log a warning + // it's also OK if we can't download or parse the policy, but we need to log the error + LOGGER.warn("Error loading and compling certificate policy " + polName + ". Will fallback to no policy filter.", e); + } + finally + { + IOUtils.closeQuietly(inStream); + } + } + else + LOGGER.info("No certificate policy has been configured."); + } + + /** + * {@inheritDoc} + */ + @Override + protected RRset processGenericRecordRequest(String name, int type) throws DNSException + { + DnsRecord records[]; + + try + { + records = proxy.getDNSByNameAndType(name, type); + } + catch (Exception e) + { + throw new DNSException(DNSError.newError(Rcode.SERVFAIL), "DNS service proxy call for DNS records failed: " + e.getMessage(), e); + } + + if (records == null || records.length == 0) + return null; + + RRset retVal = new RRset(); + try + { + for (DnsRecord record : records) + { + Record rec = Record.newRecord(Name.fromString(record.getName()), record.getType(), + record.getDclass(), record.getTtl(), record.getData()); + + retVal.addRR(rec); + } + } + catch (Exception e) + { + throw new DNSException(DNSError.newError(Rcode.SERVFAIL), "Failure while parsing generic record data: " + e.getMessage(), e); + } + + return retVal; + } + + @Override + protected Collection processGenericANYRecordRequest(String name) throws DNSException + { + DnsRecord records[]; + + try + { + records = proxy.getDNSByNameAndType(name, Type.ANY); + } + catch (Exception e) + { + throw new DNSException(DNSError.newError(Rcode.SERVFAIL), "DNS service proxy call for DNS records failed: " + e.getMessage(), e); + } + + if (records == null || records.length == 0) + return null; + + Collection retVal = new ArrayList(); + try + { + for (DnsRecord record : records) + { + Record rec = Record.newRecord(Name.fromString(record.getName()), record.getType(), + record.getDclass(), record.getTtl(), record.getData()); + + retVal.add(rec); + } + } + catch (Exception e) + { + throw new DNSException(DNSError.newError(Rcode.SERVFAIL), "Failure while parsing generic record data: " + e.getMessage(), e); + } + + return retVal; + } + + + /** + * {@inheritDoc} + */ + @SuppressWarnings("unused") + @Override + protected RRset processCERTRecordRequest(String name) throws DNSException + { + if (name.endsWith(".")) + name = name.substring(0, name.length() - 1); + + Certificate[] certs; + + // use the certificate configuration service + try + { + certs = proxy.getCertificatesForOwner(name, null); + } + catch (Exception e) + { + throw new DNSException(DNSError.newError(Rcode.SERVFAIL), "DNS service proxy call for certificates failed: " + e.getMessage(), e); + } + + if (certs == null || certs.length == 0) + { + // unless the call above was for an org level cert, it will probably always fail because the + // "name" parameter has had all instances of "@" replaced with ".". The certificate service + // stores owners using "@". + // This is horrible, but try hitting the cert service replacing each "." with "@" one by one. + // Start at the beginning of the address because this is more than likely where the "@" character + // will be. + int previousIndex = 0; + int replaceIndex = 0; + while ((replaceIndex = name.indexOf(".", previousIndex)) > -1) + { + char[] chars = name.toCharArray(); + chars[replaceIndex] = '@'; + try + { + certs = proxy.getCertificatesForOwner(String.copyValueOf(chars), null); + } + catch (Exception e) + { + throw new DNSException(DNSError.newError(Rcode.SERVFAIL), "DNS service proxy call for certificates failed: " + e.getMessage(), e); + } + if (certs != null && certs.length > 0) + break; + + if (replaceIndex >= (name.length() - 1)) + break; + + previousIndex = replaceIndex + 1; + } + } + + if (certs == null || certs.length == 0) + return null; + + if (!name.endsWith(".")) + name += "."; + + RRset retVal = new RRset(); + try + { + for (Certificate cert : certs) + { + int certRecordType = CERTRecord.PKIX; + byte[] retData = null; + + X509Certificate xCert = null; + try + { + // need to convert to cert container because this might be + // a certificate with wrapped private key data + final CertUtils.CertContainer cont = CertUtils.toCertContainer(cert.getData()); + xCert = cont.getCert(); + // check if this is a compliant certificate with the configured policy... if not, move on + if (!isCertCompliantWithPolicy(xCert)) + continue; + + retData = xCert.getEncoded(); + } + catch (CertificateConversionException e) + { + // probably not a Certificate... might be a URL + } + + + if (xCert == null) + { + // see if it's a URL + try + { + retData = cert.getData(); + URL url = new URL(new String(retData)); + certRecordType = CERTRecord.URI; + } + catch (Exception e) + { + throw new DNSException(DNSError.newError(Rcode.SERVFAIL), "Failure while parsing CERT record data: " + e.getMessage(), e); + } + } + + int keyTag = 0; + int alg = 0; + if (xCert != null && xCert.getPublicKey() instanceof RSAKey) + { + RSAKey key = (RSAKey)xCert.getPublicKey(); + byte[] modulus = key.getModulus().toByteArray(); + + keyTag = (modulus[modulus.length - 2] << 8) & 0xFF00; + + keyTag |= modulus[modulus.length - 1] & 0xFF; + if (xCert.getSigAlgOID().equalsIgnoreCase(SignatureAlgorithmIdentifier.SHA1RSA.getId())){ + alg = 5; // RFC 4034 Appendix A.1 + } else if (xCert.getSigAlgOID().equalsIgnoreCase(SignatureAlgorithmIdentifier.SHA256RSA.getId())){ + alg = 8; // RFC 5702 3.1 + } else if (xCert.getSigAlgOID().equalsIgnoreCase(SignatureAlgorithmIdentifier.SHA1DSA.getId())){ + alg = 3; // RFC 4034 Appendix A.1 + } else if (xCert.getSigAlgOID().equalsIgnoreCase(SignatureAlgorithmIdentifier.MD5RSA.getId())){ + alg = 1; // RFC 4034 Appendix A.1 + } else{ + alg = 5; + } + } + + CERTRecord rec = new CERTRecord(Name.fromString(name), DClass.IN, 86400L, certRecordType, keyTag, + alg /*public key alg, RFC 4034*/, retData); + + retVal.addRR(rec); + } + } + catch (Exception e) + { + throw new DNSException(DNSError.newError(Rcode.SERVFAIL), "Failure while parsing CERT record data: " + e.getMessage(), e); + } + + // because of policy filtering, it's possible that we could have filtered out every cert + // resulting in an empty RR set + return (retVal.size() == 0) ? null : retVal; + } + + /* + * Look for SOA records corresponding to the request + * TODO: Add cache coherency to SOA records? + */ + @Override + protected synchronized Record checkForSoaRecord(String questionName) + { + if (!questionName.endsWith(".")) + questionName += "."; + + if (soaRecords == null) + { + DnsRecord[] getRecs = null; + // load all SOA records... + try + { + getRecs = proxy.getDNSByType(Type.SOA); + + if (getRecs == null || getRecs.length == 0) + soaRecords = Collections.emptyMap(); + else + { + soaRecords = new HashMap(); + + for (DnsRecord rec : getRecs) + { + Record newRec = Record.newRecord(Name.fromString(rec.getName()), Type.SOA, + rec.getDclass(), rec.getTtl(), rec.getData()); + + soaRecords.put(newRec.getName().toString(), newRec); + } + } + } + catch (Exception e) + { + LOGGER.error("Failed to load SOA records from config service."); + } + } + + Record retVal = null; + if (soaRecords.size() > 0) + { + // look for the record by question name + + retVal = soaRecords.get(questionName); + if (retVal == null) + { + // start taking apart the question name . by . + int index = -1; + while ((index = questionName.indexOf(".")) > 0 && index < (questionName.length() - 1)) + { + questionName = questionName.substring(index + 1); + retVal = soaRecords.get(questionName); + if (retVal != null) + break; + } + } + } + + return retVal; + } +} diff --git a/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/DNSError.java b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/DNSError.java new file mode 100644 index 000000000..7a0e1b5ea --- /dev/null +++ b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/DNSError.java @@ -0,0 +1,72 @@ +/* +Copyright (c) 2010, NHIN Direct Project +All rights reserved. + +Authors: + Greg Meyer gm2552@cerner.com + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the distribution. Neither the name of the The NHIN Direct Project (nhindirect.org). +nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS +BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +THE POSSIBILITY OF SUCH DAMAGE. +*/ + +package org.nhindirect.dns; + +/** + * Container for a DNS error code. + * @author Greg Meyer + * + * @param The error type. Typically a long or integer describing an error code. + * @since 1.0 + */ +public class DNSError +{ + /** + * Creates a new DNSError with an internal error code. + * @param The error Type. Typically a long or integer describing an error code. + * @param error The error describing the error condition. + * @return + */ + public static DNSError newError(T error) + { + return new DNSError(error); + } + + private final T error; + + /** + * Constructs a new DNSError. + * @param error The error describing the error condition. + */ + public DNSError(T error) + { + this.error = error; + } + + /** + * Gets the internal error. The error is typically a long or integer describing an error code. + * @return The internal error. + */ + public T getError() + { + return error; + } + + /** + * {@inheritDoc} + */ + @Override + public String toString() + { + return error.toString(); + } +} diff --git a/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/DNSException.java b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/DNSException.java new file mode 100644 index 000000000..ed930cf2f --- /dev/null +++ b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/DNSException.java @@ -0,0 +1,95 @@ +/* +Copyright (c) 2010, NHIN Direct Project +All rights reserved. + +Authors: + Greg Meyer gm2552@cerner.com + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the distribution. Neither the name of the The NHIN Direct Project (nhindirect.org). +nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS +BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +THE POSSIBILITY OF SUCH DAMAGE. +*/ + +package org.nhindirect.dns; + +/** + * Exception for DNS server errors. + * @author Greg Meyer + * @since 1.0 + */ +public class DNSException extends Exception +{ + + static final long serialVersionUID = 1852021431664662496L; + + + private DNSError error; + + /** + * Construct an exception with an error message. + * @param error The error message. + */ + public DNSException(String error) + { + super(error); + } + + /** + * Construct an exception with a given DNS error. + * @param error The DNS error. + */ + public DNSException(DNSError error) + { + this(error, ""); + } + + /** + * Constructs an exception with a message and the DNS error. + * @param error The DNS error + * @param msg The exception message. + */ + public DNSException(DNSError error, String message) + { + this(error,message,null); + } + + /** + * Constructs an exception with the DNS error and the exception that caused the error. + * @param error The DNS error. + * @param innerException The exception that caused the error. + */ + public DNSException(DNSError error, Exception innerException) + { + this(error, "", innerException); + } + + /** + * Constructs an exception with the DNS error, a message, and the exception that caused the error. + * @param error The DNS error. + * @param msg The exception message. + * @param innerException The exception that caused the error. + */ + public DNSException(DNSError error, String message, Exception innerException) + { + super(message, innerException); + this.error = error; + } + + /** + * Gets the internal DNSError. + * @return The internal DNSError. + */ + public DNSError getError() + { + return this.error; + } +} diff --git a/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/DNSResponder.java b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/DNSResponder.java new file mode 100644 index 000000000..f8eb00d36 --- /dev/null +++ b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/DNSResponder.java @@ -0,0 +1,189 @@ +/* + Copyright (c) 2010, Direct Project + All rights reserved. + + Authors: + Umesh Madan umeshma@microsoft.com + Chris Lomonico chris.lomonico@surescripts.com + Greg Meyer gm2552@cerner.com + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +Neither the name of The Direct Project (directproject.org) nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +package org.nhindirect.dns; + +import java.io.IOException; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.xbill.DNS.Flags; +import org.xbill.DNS.Header; +import org.xbill.DNS.Message; +import org.xbill.DNS.Rcode; +import org.xbill.DNS.Section; + +/** + * Abstract DNSResponder for DNS requests. It implements common methods for calling the DNS store and handling error conditions. Protocol specific + * (UDP, TCP, etc) messaging handling is implemented in concrete implementations. + * @author Greg Meyer + * @since 1.0 + */ +public abstract class DNSResponder +{ + private static final Log LOGGER = LogFactory.getFactory().getInstance(DNSResponder.class); + + protected DNSServerSettings settings; + protected DNSStore store; + + /** + * Creates a DNS responder using the provided settings and DNS store. The responder will not handle requests + * until {@link #start()} is called. + * @param settings The DNS server settings. + * @param store The DNS store that holds the DNS record information. + * @throws DNSException + */ + public DNSResponder(DNSServerSettings settings, DNSStore store) throws DNSException + { + this.settings = settings; + this.store = store; + } + + /** + * Starts the responder. Concrete implementation bind their protocol specific handlers and start accepting DNS requests. + * @throws DNSException + */ + public abstract void start() throws DNSException; + + /** + * Stops the responder. The responder will not londer accept DNS requests after stop has been called. + * @throws DNSException + */ + public abstract void stop() throws DNSException; + + /** + * Processes a DNS request and returns a DNS response. The request is in raw DNS wire protocol format. + * @param rawMessage The raw DNS wire protocol format of the request. + * @return A response to the DNS request. + * @throws DNSException + */ + public Message processRequest(byte[] rawMessage) throws DNSException + { + if (rawMessage == null || rawMessage.length == 0) + throw new DNSException(DNSError.newError(Rcode.FORMERR), "Message cannot be null or empty."); + + Message msg; + try + { + msg = new Message(rawMessage); + } + catch (IOException e) + { + throw new DNSException(DNSError.newError(Rcode.FORMERR), "IO Exception reading raw message.", e); + } + + return processRequest(msg); + } + + /** + * Processes a DNS request and returns a DNS response. + * @param request The DNS request message. + * @return A response to the DNS request. + */ + public Message processRequest(Message request) + { + if (request == null) + throw new IllegalArgumentException("Missing request. Request cannot be null."); + + Message response; + try + { + response = store.get(request); + if (response == null || response.getHeader() == null) + { + + response = processError(request, DNSError.newError(Rcode.NXDOMAIN)); + } + else if (response.getHeader().getRcode() != Rcode.NOERROR) + response = processError(request, DNSError.newError(response.getHeader().getRcode())); + } + catch (DNSException e) + { + // don't log as an error if it's just a non implemented query type + if (!e.getError().getError().equals(Rcode.NOTIMP)) + { + LOGGER.error("Error processing DNS request: " + e.getMessage(), e); + } + response = processError(request, e.getError()); + } + + return response; + } + + /** + * Processes a DNS error condition and creates an appropriate DNS response. + * @param request The original DNS request. + * @param error The error condition that occured. + * @return A response to the DNS request. + */ + protected Message processError(Message request, DNSError error) + { + Message errorResponse = null; + try + { + Header respHeader = new Header(request.toWire()); + Message response = new Message(); + response.setHeader(respHeader); + + for (int i = 0; i < 4; i++) + response.removeAllRecords(i); + + response.addRecord(request.getQuestion(), Section.QUESTION); + + response.getHeader().setFlag(Flags.QR); + if (request.getHeader().getFlag(Flags.RD)) + response.getHeader().setFlag(Flags.RD); + respHeader.setRcode(Integer.parseInt(error.getError().toString())); + + return response; + } + catch (IOException e) {} + + return errorResponse; + } + + /** + * Converts a raw DNS wire protocol format message to a Message structure. + * @param buffer The raw DNS wire protocol format. + * @return A Message object converted from the buffer. + * @throws DNSException + */ + protected Message toMessage(byte[] buffer) throws DNSException + { + if (buffer.length <= 0 || buffer.length > settings.getMaxRequestSize()) + throw new DNSException(DNSError.newError(Rcode.REFUSED), "Invalid request size " + buffer.length); + + try + { + return new Message(buffer); + } + catch (IOException e) + { + throw new DNSException(DNSError.newError(Rcode.FORMERR), "Failed to deserialize raw byte message."); + } + } + + /** + * Converts a Message object to a raw DNS wire format byte array. + * @param msg The message to convert. + * @return A byte array representing the raw DNS wire format of the message. + */ + protected byte[] toBytes(Message msg) + { + return msg.toWire(); + } +} diff --git a/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/DNSResponderTCP.java b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/DNSResponderTCP.java new file mode 100644 index 000000000..2c0d043d2 --- /dev/null +++ b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/DNSResponderTCP.java @@ -0,0 +1,58 @@ +/* + Copyright (c) 2010, Direct Project + All rights reserved. + + Authors: + Umesh Madan umeshma@microsoft.com + Chris Lomonico chris.lomonico@surescripts.com + Greg Meyer gm2552@cerner.com + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +Neither the name of The Direct Project (directproject.org) nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +package org.nhindirect.dns; + +/** + * TCP/IP implementation of the {@link DNSReponser) interface. The responder binds to the addresses and port + * provided in the {@link DNSServerSettings} object. + * @author Greg Meyer + * @since 1.0 + */ +public class DNSResponderTCP extends DNSResponder +{ + private final DNSSocketServer socketServer; + + /** + * {@inheritDoc} + */ + public DNSResponderTCP(DNSServerSettings settings, DNSStore store) throws DNSException + { + super(settings, store); + socketServer = new TCPServer(settings, this); + } + + + /** + * {@inheritDoc}} + */ + @Override + public void start() throws DNSException + { + socketServer.start(); + } + + + /** + * {@inheritDoc}} + */ + @Override + public void stop() throws DNSException + { + socketServer.stop(); + } +} diff --git a/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/DNSResponderUDP.java b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/DNSResponderUDP.java new file mode 100644 index 000000000..f6bebdda0 --- /dev/null +++ b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/DNSResponderUDP.java @@ -0,0 +1,58 @@ +/* + Copyright (c) 2010, Direct Project + All rights reserved. + + Authors: + Umesh Madan umeshma@microsoft.com + Chris Lomonico chris.lomonico@surescripts.com + Greg Meyer gm2552@cerner.com + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +Neither the name of The Direct Project (directproject.org) nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +package org.nhindirect.dns; + +/** + * UDP/IP implementation of the {@link DNSReponser) interface. The responder binds to the addresses and port + * provided in the {@link DNSServerSettings} object. + * @author Greg Meyer + * @since 1.0 + */ +public class DNSResponderUDP extends DNSResponder +{ + private final DNSSocketServer socketServer; + + /** + * {@inheritDoc}} + */ + public DNSResponderUDP(DNSServerSettings settings, DNSStore store) throws DNSException + { + super(settings, store); + socketServer = new UDPServer(settings, this); + } + + + /** + * {@inheritDoc}} + */ + @Override + public void start() throws DNSException + { + socketServer.start(); + } + + + /** + * {@inheritDoc}} + */ + @Override + public void stop() throws DNSException + { + socketServer.stop(); + } +} diff --git a/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/DNSServer.java b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/DNSServer.java new file mode 100644 index 000000000..113e97d5a --- /dev/null +++ b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/DNSServer.java @@ -0,0 +1,213 @@ +/* +Copyright (c) 2010, NHIN Direct Project +All rights reserved. + +Authors: + Umesh Madan umeshma@microsoft.com + Greg Meyer gm2552@cerner.com + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the distribution. Neither the name of the The NHIN Direct Project (nhindirect.org). +nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS +BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +package org.nhindirect.dns; + +import java.lang.management.ManagementFactory; +import java.util.UUID; + +import javax.management.JMException; +import javax.management.MBeanServer; +import javax.management.ObjectName; +import javax.management.StandardMBean; +import javax.management.openmbean.CompositeData; +import javax.management.openmbean.CompositeDataSupport; +import javax.management.openmbean.CompositeType; +import javax.management.openmbean.OpenDataException; +import javax.management.openmbean.OpenType; +import javax.management.openmbean.SimpleType; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import com.google.inject.Inject; + +/** + * The DNS server creates the UDP and TCP responders and manages their life cycles. DNS queries are delegated + * the responders which use the {@link DNSStore} to lookup entries. + *

+ * To run a server, an instance of a server is created followed by calling the {@link #start()} method. + * @author Greg Meyer + * @since 1.0 + */ +public class DNSServer implements DNSServerMBean +{ + private static final Log LOGGER = LogFactory.getFactory().getInstance(DNSServer.class); + + private DNSResponder tcpResponder; + private DNSResponder updResponder; + private CompositeData settingsData; + private final String dnsStoreImplName; + + /** + * Create a new DNSServer + * @param store The storage medium of the DNS records. + * @param settings DNS server specific settings such as UDP/TCP ports, IP bindings, and thread tuning parameters. + */ + @Inject + public DNSServer(DNSStore store, DNSServerSettings settings) + { + try + { + tcpResponder = new DNSResponderTCP(settings, store); + } + catch (DNSException e) + { + LOGGER.error("Failed to create TCP responder: " + e.getLocalizedMessage(), e); + } + + try + { + updResponder = new DNSResponderUDP(settings, store); + } + catch (DNSException e) + { + LOGGER.error("Failed to create UDP responder: " + e.getLocalizedMessage(), e); + } + + dnsStoreImplName = store.getClass().getName(); + + registerMBean(settings); + } + + /** + * Register the MBean + */ + private void registerMBean(DNSServerSettings settings) + { + String[] itemNames = {"Port", "Bind Address", "Max Request Size", "Max Outstanding Accepts", "Max Active Accepts", "Max Connection Backlog", + "Read Buffer Size", "Send Timeout", "Receive Timeout", "Socket Close Timeout"}; + + String[] itemDesc = {"Port", "Bind Address", "Max Request Size", "Max Outstanding Accepts", "Max Active Accepts", "Max Connection Backlog", + "Read Buffer Size", "Send Timeout", "Receive Timeout", "Socket Close Timeout"}; + + OpenType[] types = {SimpleType.INTEGER, SimpleType.STRING, SimpleType.INTEGER, SimpleType.INTEGER, SimpleType.INTEGER, SimpleType.INTEGER, SimpleType.INTEGER, + SimpleType.INTEGER, SimpleType.INTEGER, SimpleType.INTEGER}; + + Object[] settingsValues = {settings.getPort(), settings.getBindAddress(), settings.getMaxRequestSize(), settings.getMaxOutstandingAccepts(), + settings.getMaxActiveRequests(), settings.getMaxConnectionBacklog(), settings.getReadBufferSize(), settings.getSendTimeout(), + settings.getReceiveTimeout(), settings.getSocketCloseTimeout()}; + + try + { + CompositeType settingsType = new CompositeType(DNSServerSettings.class.getSimpleName(), "DNS server settings.", itemNames, itemDesc, types); + settingsData = new CompositeDataSupport(settingsType, itemNames, settingsValues); + } + catch (OpenDataException e) + { + LOGGER.error("Failed to create settings composite type: " + e.getLocalizedMessage(), e); + return; + } + + + Class clazz = this.getClass(); + final StringBuilder objectNameBuilder = new StringBuilder(clazz.getPackage().getName()); + objectNameBuilder.append(":type=").append(clazz.getSimpleName()); + objectNameBuilder.append(",name=").append(UUID.randomUUID()); + + try + { + final StandardMBean mbean = new StandardMBean(this, DNSServerMBean.class); + + final MBeanServer mbeanServer = ManagementFactory.getPlatformMBeanServer(); + mbeanServer.registerMBean(mbean, new ObjectName(objectNameBuilder.toString())); + } + catch (JMException e) + { + LOGGER.error("Unable to register the DNSServer MBean", e); + } + } + + /** + * Starts the DNS server by initializing and launching the TCP and UDP listeners. + * @throws DNSException Thrown if the internal listeners could not be started. + */ + public void start() throws DNSException + { + tcpResponder.start(); + updResponder.start(); + } + + /** + * Stops the server and shuts down the TCP and UPD listeners. + * @throws DNSException Thrown if the internal listeners could not be stopped. + */ + public void stop() throws DNSException + { + tcpResponder.stop(); + updResponder.stop(); + } + + /** + * {@inheritDoc} + */ + @Override + public CompositeData getServerSettings() + { + + return settingsData; + } + + /** + * {@inheritDoc} + */ + @Override + public void startServer() + { + LOGGER.info("Received request to start server."); + try + { + start(); + } + catch (DNSException e) + { + LOGGER.error("Failed to start server: " + e.getMessage(), e); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void stopServer() + { + LOGGER.info("Received request to stop server."); + try + { + stop(); + } + catch (DNSException e) + { + LOGGER.error("Failed to stop server: " + e.getMessage(), e); + } + } + + /** + * {@inheritDoc} + */ + @Override + public String getDNSStoreImplName() + { + return dnsStoreImplName; + } +} diff --git a/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/DNSServerFactory.java b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/DNSServerFactory.java new file mode 100644 index 000000000..9feaec4aa --- /dev/null +++ b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/DNSServerFactory.java @@ -0,0 +1,98 @@ +/* +Copyright (c) 2010, NHIN Direct Project +All rights reserved. + +Authors: + Greg Meyer gm2552@cerner.com + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the distribution. Neither the name of the The NHIN Direct Project (nhindirect.org). +nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS +BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +THE POSSIBILITY OF SUCH DAMAGE. +*/ + +package org.nhindirect.dns; + +import java.net.URL; + +import org.nhindirect.dns.config.DNSServerConfig; +import org.nhindirect.dns.module.DNSServerConfigModule; + + +import com.google.inject.Guice; +import com.google.inject.Injector; +import com.google.inject.Provider; + + +/** + * The DNSServerFactory is a bootstrapper for creating instances of the {@link DNSServer) based on configuration information. Configurations + * are loaded from a URL that may take the form of any addressable resource such as a file, HTTP resource, LDAP store, or database. Based on the + * URL protocol, an appropriate configuration loader and parser is instantiated which creates an injector used to provide instance of the DNSServer. + * Optionally specific configuration and {@link DNSStore} providers can be passed for specific object creation. This is generally useful + * for creating mock implementations for testing. + * @author Greg Meyer + * + * @since 1.0 + */ +public class DNSServerFactory +{ + /** + * Creates an instance of a {@link DNSServer} using the configuration information stored at the configuration location. + * @param configLocation The URL of the configuration information. The URL may refer to any addressable resource. + * @return An initialized instance of a DNSServer. + * @throws DNSException Thrown if an error occurs while creating the DNSServer. + */ + public synchronized static DNSServer createDNSServer(URL configLocation) throws DNSException + { + return createDNSServer(configLocation, null, null); + } + + /** + * Creates an instance of a {@link DNSServer} using the configuration information stored at the configuration location. An + * optional {@link DNSStore> provider can be passed for initializing the server with a specific record store. + * @param configLocation The URL of the configuration information. The URL may refer to any addressable resource. + * @param dnsStore Optional provider that will create an instance of a specific {@link DNSStore} type. If this is null, the + * system will create a default store. + * @param settings Optional DNS server settings. Overridden by settings from the configuration service. + * @return An initialized instance of a DNSServer. + * @throws DNSException Thrown if an error occurs while creating the DNSServer. + */ + public synchronized static DNSServer createDNSServer(URL configLocation, Provider dnsStore, Provider settings) throws DNSException + { + DNSServer retVal = null; + + try + { + Injector agentInjector = buildServerInjector(configLocation, dnsStore, settings); + retVal = agentInjector.getInstance(DNSServer.class); + + } + catch (Exception t) + { + // catch all + throw new DNSException(DNSError.newError(-1), "DNSServer creation failed: " + t.getMessage(), t); + } + + return retVal; + } + + /* + * Creates an injector for getting SmtpAgent instances + */ + private static Injector buildServerInjector(URL configLocation, Provider storeProvider, Provider settings) + { + Injector configInjector = Guice.createInjector(DNSServerConfigModule.create(configLocation, storeProvider, settings)); + + DNSServerConfig config = configInjector.getInstance(DNSServerConfig.class); + + return config.getServerInjector(); + } +} diff --git a/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/DNSServerMBean.java b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/DNSServerMBean.java new file mode 100644 index 000000000..2928e6823 --- /dev/null +++ b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/DNSServerMBean.java @@ -0,0 +1,50 @@ +/* + Copyright (c) 2010, Direct Project + All rights reserved. + + Authors: + Greg Meyer gm2552@cerner.com + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +Neither the name of The Direct Project (directproject.org) nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +package org.nhindirect.dns; + +import javax.management.openmbean.CompositeData; + +/** + * MBean interface definition for monitoring and managing the DNS server. + * @author Greg Meyer + * + * @since 1.1.0 + */ +public interface DNSServerMBean +{ + + /** + * Gets the DNS server settings. + * @return The DNS server settings. + */ + public CompositeData getServerSettings(); + + /** + * Initializes the DNS server socket listeners are begins accepting requests. + */ + public void startServer(); + + /** + * Shutdown the DNS server socket listeners and stops accepting requests. + */ + public void stopServer(); + + /** + * Gets the fully qualified class name of the DNS store. + * @return The fully qualified class name of the DNS store. + */ + public String getDNSStoreImplName(); +} diff --git a/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/DNSServerSettings.java b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/DNSServerSettings.java new file mode 100644 index 000000000..6258df0f8 --- /dev/null +++ b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/DNSServerSettings.java @@ -0,0 +1,106 @@ +/* + Copyright (c) 2010, Direct Project + All rights reserved. + + Authors: + Umesh Madan umeshma@microsoft.com + Chris Lomonico chris.lomonico@surescripts.com + Greg Meyer gm2552@cerner.com + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +Neither the name of The Direct Project (directproject.org) nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +package org.nhindirect.dns; + + +/** + * Tuning parameters for the DNS server. + * + * @author Greg Meyer + * @author Umesh Madan + * @author Chris Lomonico + * + * @since 1.0 + */ +public class DNSServerSettings extends SocketServerSettings +{ + private static final int DEFAULT_PORT = 53; + private static final String DEFAULT_BIND_ADDRESS = "0.0.0.0"; // bind to all adapters + public static final int DAFAULT_MAX_REQUEST_SIZE = 1024 * 16; + + private int port; + private String bindAddress; + private int maxRequestSize; + + /** + * Create default DNS server settings + */ + public DNSServerSettings() + { + super(); + port = DEFAULT_PORT; + bindAddress = DEFAULT_BIND_ADDRESS; + maxRequestSize = DAFAULT_MAX_REQUEST_SIZE; + } + + /** + * Gets the IP port that the server will be listening on. The default is 53. + * @return The IP port that the server will be listening on. + */ + public int getPort() + { + return port; + } + + /** + * Sets the IP port that the server will be listening on. + * @param port The IP port that the server will be listening on. + * + */ + public void setPort(int port) + { + this.port = port; + } + + /** + * Gets the IP4 addresses that the server will be bound to. The string is comma delimited list of IP addresses. The default is 0.0.0.0 + * which means that the server will bind to add IP addresses available on the local machine. + * @return The IP4 addresses that the server will be bound to. + */ + public String getBindAddress() + { + return bindAddress; + } + + /** + * Sets the IP4 addresses that the server will be bound to. + * @param bindAddress The IP4 addresses that the server will be bound to. + */ + public void setBindAddress(String bindAddress) + { + this.bindAddress = bindAddress; + } + + /** + * Gets the maximum size in bytes of a request. The default size is 16K. + * @return The maximum size in bytes of a request. + */ + public int getMaxRequestSize() + { + return maxRequestSize; + } + + /** + * Sets the maximum size in bytes of a request. + * @param maxRequestSize The maximum size in bytes of a request. + */ + public void setMaxRequestSize(int maxRequestSize) + { + this.maxRequestSize = maxRequestSize; + } +} diff --git a/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/DNSSocketServer.java b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/DNSSocketServer.java new file mode 100644 index 000000000..e66c19bc6 --- /dev/null +++ b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/DNSSocketServer.java @@ -0,0 +1,321 @@ +/* + Copyright (c) 2010, Direct Project + All rights reserved. + + Authors: + Umesh Madan umeshma@microsoft.com + Chris Lomonico chris.lomonico@surescripts.com + Greg Meyer gm2552@cerner.com + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +Neither the name of The Direct Project (directproject.org) nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +package org.nhindirect.dns; + +import java.io.IOException; +import java.lang.management.ManagementFactory; +import java.net.Socket; +import java.util.UUID; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.SynchronousQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +import javax.management.JMException; +import javax.management.MBeanServer; +import javax.management.ObjectName; +import javax.management.StandardMBean; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + + +/** + * The socket server is an IP protocol agnostic server that manages threading/concurrency and message dispatching to the + * concrete socket implementation. It utilizes a "smart" thread pool for efficiently managing processing threads. + * @author Greg Meyer + * @since 1.0 + */ +public abstract class DNSSocketServer implements DNSSocketServerMBean +{ + private static final Log LOGGER = LogFactory.getFactory().getInstance(DNSSocketServer.class); + + protected final DNSServerSettings settings; + protected final DNSResponder responder; + + protected ExecutorService socketAcceptService; + protected ThreadPoolExecutor dnsRequestService; + + protected final AtomicBoolean running; + + private long serverStartTime = Long.MAX_VALUE; + private volatile long rejectedCount = 0; + private volatile long requestCount = 0; + private TemporalCountBucket countBuckets[] = {new TemporalCountBucket(), new TemporalCountBucket(), + new TemporalCountBucket(), new TemporalCountBucket(), new TemporalCountBucket()}; + + + /** + * Creates a socket server. The server will not start accepting messages until the {@link #start()} method is called. + * @param settings The server settings. The settings contain specific IP and socket configuration parameters. + * @param responsder The DNS responder that will handle lookups. + * @throws DNSException + */ + public DNSSocketServer(DNSServerSettings settings, DNSResponder responsder) throws DNSException + { + running = new AtomicBoolean(false); + + this.settings = settings; + this.responder = responsder; + + // create the server socket + createServerSocket(); + } + + protected void registerMBean(Class clazz) + { + final StringBuilder objectNameBuilder = new StringBuilder(clazz.getPackage().getName()); + objectNameBuilder.append(":type=").append(clazz.getSimpleName()); + objectNameBuilder.append(",name=").append(UUID.randomUUID()); + + try + { + final StandardMBean mbean = new StandardMBean(this, DNSSocketServerMBean.class); + + final MBeanServer mbeanServer = ManagementFactory.getPlatformMBeanServer(); + mbeanServer.registerMBean(mbean, new ObjectName(objectNameBuilder.toString())); + } + catch (JMException e) + { + LOGGER.error("Unable to register the DNSSocketServer MBean", e); + } + } + + /** + * Starts the socket server and initializes the dispatch threads. After this method has been called, the server will start accepting + * DNS requests. + * @throws DNSException + */ + public void start() throws DNSException + { + if (running.get() != true) + { + // create the accept thread + running.set(true); + + dnsRequestService = new ThreadPoolExecutor(0, settings.getMaxActiveRequests(), + 120L, TimeUnit.SECONDS, new SynchronousQueue()); + + + socketAcceptService = Executors.newSingleThreadExecutor(); + socketAcceptService.execute(getSocketAcceptTask()); + + serverStartTime = System.currentTimeMillis(); + } + else + LOGGER.info("Start requested, but socket server is already running."); + + } + + /** + * Shuts down the socket server and terminates the server from accepting additional requests. The server attempts to gracefully + * shutdown the processing threads and gives currently running processing threads a chance to finish. + * @throws DNSException + */ + public void stop() throws DNSException + { + running.set(false); + + socketAcceptService.shutdown(); + + dnsRequestService.shutdown(); + + } + + protected void waitForGracefulStop() + { + try + { + socketAcceptService.awaitTermination(10, TimeUnit.SECONDS); + } + catch (InterruptedException e) {/* no op */} + + try + { + dnsRequestService.awaitTermination(10, TimeUnit.SECONDS); + } + catch (InterruptedException e) {/* no op */} + } + + /** + * Creates and initializes the socket implementation that will accept incoming requests. + * @throws DNSException + */ + public abstract void createServerSocket() throws DNSException; + + /** + * Gets the Runnable task that will be responsible for accepting connections. This task + * is placed in a single thread, so it should loop until the running flag is set to false. + * @return The Runnable task that will be responsible for accepting connections + */ + public abstract Runnable getSocketAcceptTask(); + + /** + * Gets the Runnable task that will process a DNS request. Each accepted request will create a new instance + * of the Runnable task and run it in its own thread. + * @param s An arbitrary parameter passed to the Runnable task. This parameter generally contain the DNS request information. + * This may be the TCP socket from the accept() server socket call or a UDP datagram packet. + * @return The Runnable task that will process a DNS request + */ + public abstract Runnable getDNSRequestTask(Object s); + + /** + * Submits the DNS request to a runnable task. + * @param s An arbitrary parameter passed to the Runnable task. This parameter generally contain the DNS request information. + * This may be the TCP socket from the accept() server socket call or a UDP datagram packet. + */ + protected void submitDNSRequest(Object s) + { + + updateCountMetrics(); + boolean executed = false; + if (dnsRequestService.getActiveCount() < settings.getMaxActiveRequests()) + { + try + { + dnsRequestService.execute(getDNSRequestTask(s)); + executed = true; + } + catch (RejectedExecutionException e) + { + LOGGER.warn("Rejecting DNS request: " + e.getMessage()); + /* no-op, but use logic below to indicate that it was rejected */ + } + } + + if (!executed) + { + ++rejectedCount; + // just close the socket... we're too busy to handle anything + try + { + if (s instanceof Socket) + ((Socket)s).close(); + } + catch (IOException e) {} + } + } + + private void updateCountMetrics() + { + ++requestCount; + long curTime = System.currentTimeMillis(); + int bucketIndex = (int)((curTime / 1000) % 5); + + synchronized (countBuckets) + { + TemporalCountBucket bucket = countBuckets[bucketIndex]; + bucket.increment(curTime); + } + } + + /** + * {@inheritDoc} + */ + @Override + public Long getUptime() + { + if (running.get() == false || serverStartTime == Long.MAX_VALUE) + return -1L; + else + return (System.currentTimeMillis() - serverStartTime); + } + + /** + * {@inheritDoc} + */ + @Override + public Long getRejectedRequestCount() + { + return rejectedCount; + } + + /** + * {@inheritDoc} + */ + @Override + public Long getResourceRequestCount() + { + return requestCount; + } + + /** + * {@inheritDoc} + */ + @Override + public String getResourceRequestLoad() + { + // this is an approximation over the last 5 seconds converted to requests per second + long curTime = System.currentTimeMillis(); + int numTransactions = 0; + synchronized (countBuckets) + { + for (TemporalCountBucket bucket : countBuckets) + { + numTransactions += bucket.getCount(curTime); + } + } + + // can get a little better accuracy if we take into consideration that the last full second of the 5 second time range has not yet passed + double div = 4.0 + ((curTime % 1000) / 1000); + + int aveTransLoad = (int)((double)(numTransactions) / (div)); + + return aveTransLoad + "/sec"; + } + + /* + * class used to hold request count within a 1 second time range + */ + private static class TemporalCountBucket + { + private volatile long count = 0; + private volatile long firstAddedTime = 0; + + /* + * increment the access count... if the access time is outside + * of the 5 second time range, then reset the counter + */ + public synchronized void increment(long accessTime) + { + if ((accessTime - firstAddedTime) > 5000) + { + // round down to nearest 1000 + firstAddedTime = accessTime - (accessTime % 1000); + count = 1; + } + else + ++count; + } + + /* + * get the access count... if the access time is outside of the + * 5 second range, then return 0 + */ + public synchronized long getCount(long accessTime) + { + if (firstAddedTime == 0) + return 0; + + return ((accessTime - firstAddedTime) > 5000) ? 0 : count; + } + } +} diff --git a/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/DNSSocketServerMBean.java b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/DNSSocketServerMBean.java new file mode 100644 index 000000000..0638c655d --- /dev/null +++ b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/DNSSocketServerMBean.java @@ -0,0 +1,68 @@ +/* + Copyright (c) 2010, Direct Project + All rights reserved. + + Authors: + Greg Meyer gm2552@cerner.com + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +Neither the name of The Direct Project (directproject.org) nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +package org.nhindirect.dns; + +/** + * MBean interface definition for monitoring and managing a DNS socket server. + * @author Greg Meyer + * + * @since 1.1.0 + */ +public interface DNSSocketServerMBean +{ + /** + * Gets the number of DNS requests received by the server. + * @return The number of DNS requests received by the server. + */ + public Long getResourceRequestCount(); + + /** + * Gets the request load of the server. Load is returned in number of requests per second averaged over the last 5 seconds. + * @return The request load of the server. + */ + public String getResourceRequestLoad(); + + /** + * Gets the time in milliseconds that the server has been running since its last start. + * @return The time in milliseconds that the server has been running since its last start. + */ + public Long getUptime(); + + /** + * Gets the number of requests that returned without error. NXDOMAIN statuses qualify as successful requests. + * @return The number of requests that returned without error. + */ + public Long getSuccessfulRequestCount(); + + /** + * Gets the number of requests that resulted in an error. + * @return The number of requests that resulted in an error. + */ + public Long getErrorRequestCount(); + + /** + * Gets the number of requests that returned no records without error. + * @return The number of request that returned no records without error. + */ + public Long getMissedRequestCount(); + + /** + * Gets the number of requests that were rejected by the server due to being to busy. A high number of rejected requests indicates that the server + * should be reconfigured to accept a higher load. + * @return The number of requests that were rejected by the server due to being to busy. + */ + public Long getRejectedRequestCount(); +} diff --git a/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/DNSStore.java b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/DNSStore.java new file mode 100644 index 000000000..360a2f411 --- /dev/null +++ b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/DNSStore.java @@ -0,0 +1,43 @@ +/* + Copyright (c) 2010, Direct Project + All rights reserved. + + Authors: + Umesh Madan umeshma@microsoft.com + Chris Lomonico chris.lomonico@surescripts.com + Greg Meyer gm2552@cerner.com + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +Neither the name of The Direct Project (directproject.org) nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +package org.nhindirect.dns; + +import org.xbill.DNS.Message; + +import com.google.inject.ImplementedBy; + +/** + * A DNSStore encapsulates the physical medium that stores DNS records such as a files, databases, or web services. A store processes DNS requests, + * executes the lookup or operational request (such as zone transfers) logic, and returns an appropriate response. + * @author Greg Meyer + * @author Umesh Madan + * + * @since 1.0 + */ +@ImplementedBy(ConfigServiceDNSStore.class) +public interface DNSStore +{ + /** + * Processes a lookup request for DNS records. + * @param dnsMsg The DSN request message. + * @return The DNS response message. Returns null for lookup requests if a matching record cannot be found. + * @throws DNSException Thrown is the request fails due to sever failure such as illegal request parameters or + * a failure accessing the physical record medium. + */ + public Message get(Message dnsMsg) throws DNSException; +} diff --git a/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/ProxyDNSStore.java b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/ProxyDNSStore.java new file mode 100644 index 000000000..480689ac5 --- /dev/null +++ b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/ProxyDNSStore.java @@ -0,0 +1,154 @@ +/* + Copyright (c) 2010, Direct Project + All rights reserved. + + Authors: + Umesh Madan umeshma@microsoft.com + Chris Lomonico chris.lomonico@surescripts.com + Greg Meyer gm2552@cerner.com + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +Neither the name of The Direct Project (directproject.org) nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +package org.nhindirect.dns; + +import java.io.IOException; +import java.net.UnknownHostException; +import java.util.Collection; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.xbill.DNS.ExtendedResolver; +import org.xbill.DNS.Message; +import org.xbill.DNS.ResolverConfig; + +/** + * Proxy DNS store that delegates all requests to another set of DNS servers. + * The store defaults to using port 53 and the machine's configured DNS servers. + * @author Greg Meyer + * + * @since 1.0 + */ +public class ProxyDNSStore implements DNSStore +{ + private static final int DEFAULT_RESOLVER_PORT = 53; + + private final String[] servers; + private final int port; + + protected static final Log LOGGER = LogFactory.getFactory().getInstance(ProxyDNSStore.class); + + /** + * Creates a default proxy store. + */ + public ProxyDNSStore() + { + this(DEFAULT_RESOLVER_PORT); + } + + /** + * Creates a proxy store delegating requests to the provided port. + * @param port The IP port to use when calling the proxy DNS server. + */ + public ProxyDNSStore(int port) + { + this(null, DEFAULT_RESOLVER_PORT); + } + + /** + * Creates a proxy using the the provided servers for delegating requests. + * @param servers A collections of IP4 addresses (as strings) that the proxy will delegate request to. + */ + public ProxyDNSStore(Collection servers) + { + this(servers, DEFAULT_RESOLVER_PORT); + } + + /** + * Creates a proxy using the provided servers and port for delegating requests. + * @param servers A collections of IP4 addresses (as strings) that the proxy will delegate request to. + * @param port The IP port to use when calling the proxy DNS server. + */ + public ProxyDNSStore(Collection servers, int port) + { + if (servers == null || servers.size() == 0) + { + + String[] configedServers = ResolverConfig.getCurrentConfig().servers(); + + if (configedServers != null) + { + this.servers = configedServers; + } + else + this.servers = null; + } + else + { + this.servers = new String[servers.size()]; + servers.toArray(this.servers); + } + + this.port = port; + } + + /** + * {@inheritDoc} + */ + @Override + public Message get(Message dnsMsg) throws DNSException + { + ExtendedResolver resolver = createExResolver(servers, port, 2, 2000); + // try UPD first + + Message response = null; + try + { + response = resolver.send(dnsMsg); + } + catch (IOException e) + { + /* no-op */ + } + + if (response == null) + { + // try TCP + resolver.setTCP(true); + try + { + response = resolver.send(dnsMsg); + } + catch (IOException e) + { + /* no-op */ + } + } + + return response; + } + + /* + * Create the resolver that will do the DNS requests. + */ + private ExtendedResolver createExResolver(String[] servers, int port, int retries, int timeout) + { + ExtendedResolver retVal = null; + try + { + retVal = new ExtendedResolver(servers); + retVal.setRetries(retries); + retVal.setTimeout(timeout); + } + catch (UnknownHostException e) + { + LOGGER.warn("Proxy store resolver could not be created: " + e.getMessage(), e); + } + return retVal; + } +} diff --git a/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/RESTServiceDNSStore.java b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/RESTServiceDNSStore.java new file mode 100644 index 000000000..8badb99dd --- /dev/null +++ b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/RESTServiceDNSStore.java @@ -0,0 +1,384 @@ +package org.nhindirect.dns; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.net.URL; +import java.security.cert.X509Certificate; +import java.security.interfaces.RSAKey; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; + +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang.StringUtils; +import org.apache.http.client.HttpClient; +import org.nhind.config.rest.CertificateService; +import org.nhind.config.rest.DNSService; +import org.nhind.config.rest.impl.DefaultCertPolicyService; +import org.nhind.config.rest.impl.DefaultCertificateService; +import org.nhind.config.rest.impl.DefaultDNSService; +import org.nhind.config.rest.CertPolicyService; +import org.nhindirect.common.rest.ServiceSecurityManager; +import org.nhindirect.config.model.DNSRecord; +import org.nhindirect.config.model.Certificate; +import org.nhindirect.config.model.exceptions.CertificateConversionException; +import org.nhindirect.config.model.utils.CertUtils; +import org.nhindirect.dns.annotation.ConfigServiceURL; +import org.nhindirect.policy.PolicyFilterFactory; +import org.nhindirect.policy.PolicyLexiconParser; +import org.nhindirect.policy.PolicyLexiconParserFactory; +import org.nhindirect.policy.x509.SignatureAlgorithmIdentifier; +import org.xbill.DNS.CERTRecord; +import org.xbill.DNS.DClass; +import org.xbill.DNS.Name; +import org.xbill.DNS.RRset; +import org.xbill.DNS.Rcode; +import org.xbill.DNS.Record; +import org.xbill.DNS.Type; + +import com.google.inject.Inject; + +public class RESTServiceDNSStore extends AbstractDNSStore +{ + protected final CertificateService certService; + protected final CertPolicyService certPolicyService; + protected final DNSService dnsService; + + @Inject + public RESTServiceDNSStore(@ConfigServiceURL String serviceURL, HttpClient httpClient, ServiceSecurityManager securityManager) + { + certService = new DefaultCertificateService(serviceURL, httpClient, securityManager); + certPolicyService = new DefaultCertPolicyService(serviceURL, httpClient, securityManager); + dnsService = new DefaultDNSService(serviceURL, httpClient, securityManager); + + try + { + configCertPolicy(); + } + catch (DNSException e) + { + throw new IllegalStateException(e); + } + + } + + /** + * Checks to see if a certificate policy has been configured. + */ + protected void configCertPolicy() throws DNSException + { + // check to see if there is a certificate policy set + final String polName = System.getProperty(DNS_CERT_POLICY_NAME_VAR); + if (!StringUtils.isEmpty(polName)) + { + InputStream inStream = null; + LOGGER.info("Certificate policy name " + polName + " has been configured."); + try + { + // get the policy by name + final org.nhindirect.config.model.CertPolicy policy = certPolicyService.getPolicyByName(polName); + if (policy == null) + { + LOGGER.warn("Certificate policy " + polName + " could not be found in the system. Falling back to no policy."); + return; + } + + // now compile the policy into an expression + final PolicyLexiconParser parser = PolicyLexiconParserFactory.getInstance(policy.getLexicon()); + inStream = new ByteArrayInputStream(policy.getPolicyData()); + this.polExpression = parser.parse(inStream); + + // now create the filter + this.polFilter = PolicyFilterFactory.getInstance(); + + } + catch (Exception e) + { + // it's OK if can't find the certificate policy that was configured, we'll just log a warning + // it's also OK if we can't download or parse the policy, but we need to log the error + LOGGER.warn("Error loading and compling certificate policy " + polName + ". Will fallback to no policy filter.", e); + } + finally + { + IOUtils.closeQuietly(inStream); + } + } + else + LOGGER.info("No certificate policy has been configured."); + } + + /** + * {@inheritDoc} + */ + @Override + protected RRset processGenericRecordRequest(String name, int type) throws DNSException + { + Collection records; + + try + { + records = dnsService.getDNSRecord(type, name); + } + catch (Exception e) + { + throw new DNSException(DNSError.newError(Rcode.SERVFAIL), "DNS service proxy call for DNS records failed: " + e.getMessage(), e); + } + + if (records == null || records.size() == 0) + return null; + + RRset retVal = new RRset(); + try + { + for (DNSRecord record : records) + { + Record rec = Record.newRecord(Name.fromString(record.getName()), record.getType(), + record.getDclass(), record.getTtl(), record.getData()); + + retVal.addRR(rec); + } + } + catch (Exception e) + { + throw new DNSException(DNSError.newError(Rcode.SERVFAIL), "Failure while parsing generic record data: " + e.getMessage(), e); + } + + return retVal; + } + + @Override + protected Collection processGenericANYRecordRequest(String name) throws DNSException + { + Collection records; + + try + { + records = dnsService.getDNSRecord(Type.ANY, name); + } + catch (Exception e) + { + throw new DNSException(DNSError.newError(Rcode.SERVFAIL), "DNS service proxy call for DNS records failed: " + e.getMessage(), e); + } + + if (records == null || records.size() == 0) + return null; + + Collection retVal = new ArrayList(); + try + { + for (DNSRecord record : records) + { + Record rec = Record.newRecord(Name.fromString(record.getName()), record.getType(), + record.getDclass(), record.getTtl(), record.getData()); + + retVal.add(rec); + } + } + catch (Exception e) + { + throw new DNSException(DNSError.newError(Rcode.SERVFAIL), "Failure while parsing generic record data: " + e.getMessage(), e); + } + + return retVal; + } + + /** + * {@inheritDoc} + */ + @SuppressWarnings("unused") + @Override + protected RRset processCERTRecordRequest(String name) throws DNSException + { + if (name.endsWith(".")) + name = name.substring(0, name.length() - 1); + + Collection certs; + + // use the certificate configuration service + try + { + certs = certService.getCertificatesByOwner(name); + } + catch (Exception e) + { + throw new DNSException(DNSError.newError(Rcode.SERVFAIL), "DNS service proxy call for certificates failed: " + e.getMessage(), e); + } + + if (certs == null || certs.size() == 0) + { + // unless the call above was for an org level cert, it will probably always fail because the + // "name" parameter has had all instances of "@" replaced with ".". The certificate service + // stores owners using "@". + // This is horrible, but try hitting the cert service replacing each "." with "@" one by one. + // Start at the beginning of the address because this is more than likely where the "@" character + // will be. + int previousIndex = 0; + int replaceIndex = 0; + while ((replaceIndex = name.indexOf(".", previousIndex)) > -1) + { + char[] chars = name.toCharArray(); + chars[replaceIndex] = '@'; + try + { + certs = certService.getCertificatesByOwner(String.copyValueOf(chars)); + } + catch (Exception e) + { + throw new DNSException(DNSError.newError(Rcode.SERVFAIL), "DNS service proxy call for certificates failed: " + e.getMessage(), e); + } + if (certs != null && certs.size() > 0) + break; + + if (replaceIndex >= (name.length() - 1)) + break; + + previousIndex = replaceIndex + 1; + } + } + + if (certs == null || certs.size() == 0) + return null; + + if (!name.endsWith(".")) + name += "."; + + RRset retVal = new RRset(); + try + { + for (Certificate cert : certs) + { + int certRecordType = CERTRecord.PKIX; + byte[] retData = null; + + X509Certificate xCert = null; + try + { + // need to convert to cert container because this might be + // a certificate with wrapped private key data + final CertUtils.CertContainer cont = CertUtils.toCertContainer(cert.getData()); + xCert = cont.getCert(); + // check if this is a compliant certificate with the configured policy... if not, move on + if (!isCertCompliantWithPolicy(xCert)) + continue; + + retData = xCert.getEncoded(); + } + catch (CertificateConversionException e) + { + // probably not a Certificate... might be a URL + } + + + if (xCert == null) + { + // see if it's a URL + try + { + retData = cert.getData(); + URL url = new URL(new String(retData)); + certRecordType = CERTRecord.URI; + } + catch (Exception e) + { + throw new DNSException(DNSError.newError(Rcode.SERVFAIL), "Failure while parsing CERT record data: " + e.getMessage(), e); + } + } + + int keyTag = 0; + int alg = 0; + if (xCert != null && xCert.getPublicKey() instanceof RSAKey) + { + RSAKey key = (RSAKey)xCert.getPublicKey(); + byte[] modulus = key.getModulus().toByteArray(); + + keyTag = (modulus[modulus.length - 2] << 8) & 0xFF00; + + keyTag |= modulus[modulus.length - 1] & 0xFF; + if (xCert.getSigAlgOID().equalsIgnoreCase(SignatureAlgorithmIdentifier.SHA1RSA.getId())){ + alg = 5; // RFC 4034 Appendix A.1 + } else if (xCert.getSigAlgOID().equalsIgnoreCase(SignatureAlgorithmIdentifier.SHA256RSA.getId())){ + alg = 8; // RFC 5702 3.1 + } else if (xCert.getSigAlgOID().equalsIgnoreCase(SignatureAlgorithmIdentifier.SHA1DSA.getId())){ + alg = 3; // RFC 4034 Appendix A.1 + } else if (xCert.getSigAlgOID().equalsIgnoreCase(SignatureAlgorithmIdentifier.MD5RSA.getId())){ + alg = 1; // RFC 4034 Appendix A.1 + } else{ + alg = 5; + } + } + + CERTRecord rec = new CERTRecord(Name.fromString(name), DClass.IN, 86400L, certRecordType, keyTag, + alg /*public key alg, RFC 4034*/, retData); + + retVal.addRR(rec); + } + } + catch (Exception e) + { + throw new DNSException(DNSError.newError(Rcode.SERVFAIL), "Failure while parsing CERT record data: " + e.getMessage(), e); + } + + // because of policy filtering, it's possible that we could have filtered out every cert + // resulting in an empty RR set + return (retVal.size() == 0) ? null : retVal; + } + + @Override + protected synchronized Record checkForSoaRecord(String questionName) + { + if (!questionName.endsWith(".")) + questionName += "."; + + if (soaRecords == null) + { + Collection getRecs = null; + // load all SOA records... + try + { + getRecs = dnsService.getDNSRecord(Type.SOA, ""); + + if (getRecs == null || getRecs.size() == 0) + soaRecords = Collections.emptyMap(); + else + { + soaRecords = new HashMap(); + + for (DNSRecord rec : getRecs) + { + Record newRec = Record.newRecord(Name.fromString(rec.getName()), Type.SOA, + rec.getDclass(), rec.getTtl(), rec.getData()); + + soaRecords.put(newRec.getName().toString(), newRec); + } + } + } + catch (Exception e) + { + LOGGER.error("Failed to load SOA records from config service."); + } + } + + Record retVal = null; + if (soaRecords.size() > 0) + { + // look for the record by question name + + retVal = soaRecords.get(questionName); + if (retVal == null) + { + // start taking apart the question name . by . + int index = -1; + while ((index = questionName.indexOf(".")) > 0 && index < (questionName.length() - 1)) + { + questionName = questionName.substring(index + 1); + retVal = soaRecords.get(questionName); + if (retVal != null) + break; + } + } + } + + return retVal; + } +} diff --git a/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/SocketServerSettings.java b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/SocketServerSettings.java new file mode 100644 index 000000000..ce1a5831a --- /dev/null +++ b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/SocketServerSettings.java @@ -0,0 +1,206 @@ +/* + Copyright (c) 2010, Direct Project + All rights reserved. + + Authors: + Umesh Madan umeshma@microsoft.com + Chris Lomonico chris.lomonico@surescripts.com + Greg Meyer gm2552@cerner.com + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +Neither the name of The Direct Project (directproject.org) nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +package org.nhindirect.dns; + +/** + * Tuning parameters for the DNS server socket connections. Tuning parameters are important to maximize the performance + * of the DNS server or to prevent the server machine from becoming over loaded. + * + * @author Greg Meyer + * @author Umesh Madan + * @author Chris Lomonico + * + * @since 1.0 + */ +public class SocketServerSettings +{ + + private static final int DEFAULT_MAX_CONNECTION_BACKLOG = 64; + private static final int DEFAULT_MAX_ACTIVE_REQUESTS = 64; + private static final int DEFAULT_MAX_OUTSTANDING_ACCEPTS = 16; + private static final int DEFAULT_READ_BUFFER_SIZE = 1024; + private static final int DEFAULT_SEND_TIMEOUT = 5000; + private static final int DEFAULT_RECEIVE_TIMEOUT = 50000; + private static final int DEFAULT_SOCKET_CLOSE_TIMEOUT = 5000; + + private int maxOutstandingAccepts; + private int maxActiveRequests; + private int maxConnectionBacklog; + private int readBufferSize; + private int sendTimeout; + private int receiveTimeout; + private int socketCloseTimeout; + + /** + * Creates a default set of socket parameters. + */ + public SocketServerSettings() + { + maxOutstandingAccepts = DEFAULT_MAX_OUTSTANDING_ACCEPTS; + maxActiveRequests = DEFAULT_MAX_ACTIVE_REQUESTS; + maxConnectionBacklog = DEFAULT_MAX_CONNECTION_BACKLOG; + readBufferSize = DEFAULT_READ_BUFFER_SIZE; + sendTimeout = DEFAULT_SEND_TIMEOUT; + receiveTimeout = DEFAULT_RECEIVE_TIMEOUT; + socketCloseTimeout = DEFAULT_SOCKET_CLOSE_TIMEOUT; + } + + /** + * Gets the maximum number of requests that can be accepted by the server, but not yet + * committed to a processing thread. Setting this value to high may result in DNS clients + * timing out due to outstanding requests waiting to long for a processing thread. The + * default is 16 requests. + * @return The maximum number of requests that can be excepted by the server, but not yet + * committed to a processing thread. + */ + public int getMaxOutstandingAccepts() + { + return maxOutstandingAccepts; + } + + /** + * Sets the maximum number of requests that can be accepted by the server, but not yet + * committed to a processing thread. + * @param maxOutstandingAccepts The maximum number of requests that can be accepted by the server, but not yet + * committed to a processing thread. + */ + public void setMaxOutstandingAccepts(int maxOutstandingAccepts) + { + this.maxOutstandingAccepts = maxOutstandingAccepts; + } + + /** + * Gets the maximum number of concurrent requests that can be processed by the server at any give time. Setting this + * value to high may result in overloading the system. Setting this value to low can limit throughput. The default + * 64. + * @return The maximum number of concurrent requests that can be processed by the server at any give time. + */ + public int getMaxActiveRequests() + { + return maxActiveRequests; + } + + /** + * Sets the maximum number of concurrent requests that can be processed by the server at any give time. + * @param maxActiveRequests The maximum number of concurrent requests that can be processed by the server at any give time. + */ + public void setMaxActiveRequests(int maxActiveRequests) + { + this.maxActiveRequests = maxActiveRequests; + } + + /** + * Gets the maximum number of connections that are in the IP socket accept backlog. Socket backlog is only relevant + * for TCP session based connections. Setting this value to high can overload the IP stack or result in DNS + * client timeouts. The default value is 64. + * + * @return The maximum number of connections that are in the IP socket accept backlog. + */ + public int getMaxConnectionBacklog() + { + return maxConnectionBacklog; + } + + /** + * Sets the maximum number of connections that are in the IP socket accept backlog. + * @param maxConnectionBacklog The maximum number of connections that are in the IP socket accept backlog. + */ + public void setMaxConnectionBacklog(int maxConnectionBacklog) + { + this.maxConnectionBacklog = maxConnectionBacklog; + } + + /** + * Gets the maximum size request buffer size in bytes. The default value is 1024 bytes. + * @return The maximum size request buffer size in bytes. + */ + public int getReadBufferSize() + { + return readBufferSize; + } + + /** + * Sets the maximum size request buffer size in bytes. + * @param readBufferSize The maximum size request buffer size in bytes. + */ + public void setReadBufferSize(int readBufferSize) + { + this.readBufferSize = readBufferSize; + } + + /** + * Gets the socket timeout in milliseconds for sending responses. Setting this value to high can + * result in performance degradation if multiple clients abandon their sessions. Setting this value + * to low can result in clients not receiving responses in high latency environments. The default value is + * 5000 milliseconds. + * @return The socket timeout in milliseconds for sending responses + */ + public int getSendTimeout() + { + return sendTimeout; + } + + /** + * Sets the socket timeout in milliseconds for sending responses. + * @param sendTimeout Sets the socket timeout in milliseconds for sending responses. + */ + public void setSendTimeout(int sendTimeout) + { + this.sendTimeout = sendTimeout; + } + + /** + * Gets the socket timeout in milliseconds for receiving or reading request. Setting this value to high can + * result in performance degradation if multiple clients abandon their sessions. Setting this value + * to low can result in the server not fully reading request data in high latency environments. The default value is + * 5000 milliseconds. + * @return The socket timeout in milliseconds for receiving or reading requests. + */ + public int getReceiveTimeout() + { + return receiveTimeout; + } + + /** + * Sets the socket timeout in milliseconds for receiving or reading request. + * @param receiveTimeout The socket timeout in milliseconds for receiving or reading request. + */ + public void setReceiveTimeout(int receiveTimeout) + { + this.receiveTimeout = receiveTimeout; + } + + /** + * Gets the timeout in milliseconds for closing a socket connection. The default value is 5000 milliseconds. + * @return The timeout in milliseconds for closing a socket connection. + */ + public int getSocketCloseTimeout() + { + return socketCloseTimeout; + } + + /** + * Sets the timeout in milliseconds for closing a socket connection. The default value is 5000 milliseconds. + * @param socketCloseTimeout The timeout in milliseconds for closing a socket connection. + */ + public void setSocketCloseTimeout(int socketCloseTimeout) + { + this.socketCloseTimeout = socketCloseTimeout; + } + +} diff --git a/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/TCPServer.java b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/TCPServer.java new file mode 100644 index 000000000..bde6b4154 --- /dev/null +++ b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/TCPServer.java @@ -0,0 +1,318 @@ +/* + Copyright (c) 2010, Direct Project + All rights reserved. + + Authors: + Greg Meyer gm2552@cerner.com + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +Neither the name of The Direct Project (directproject.org) nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +package org.nhindirect.dns; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.Inet4Address; +import java.net.ServerSocket; +import java.net.Socket; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.xbill.DNS.Message; +import org.xbill.DNS.Rcode; +import org.xbill.DNS.Section; + +/** + * TCP socket server that handled DNS requests over TCP. + * @author Greg Meyer + * @since 1.0 + */ +public class TCPServer extends DNSSocketServer +{ + + private static final Log LOGGER = LogFactory.getFactory().getInstance(TCPServer.class); + + private ServerSocket serverSocket; + + private volatile long missCount = 0; + private volatile long errorCount = 0; + private volatile long successCount = 0; + + /** + * Creates a TCP socket server. The server will not start accepting messages until the {@link #start()} method is called. + * @param settings The server settings. The settings contain specific IP and socket configuration parameters. + * @param responsder The DNS responder that will handle lookups. + * @throws DNSException + */ + public TCPServer(DNSServerSettings settings, DNSResponder responder) throws DNSException + { + super(settings, responder); + + registerMBean(this.getClass()); + } + + /** + * {@inheritDoc} + */ + public void start() throws DNSException + { + LOGGER.info("DNS TCP Server Starting"); + super.start(); + + if (LOGGER.isInfoEnabled()) + { + StringBuilder builder = new StringBuilder(); + builder.append("DNS TCP Server Startup Complete\r\n\tBind Address: ").append(settings.getBindAddress()); + builder.append("\r\n\tBind Port: ").append(settings.getPort()); + LOGGER.info(builder.toString()); + } + } + + /** + * {@inheritDoc} + */ + public void stop() throws DNSException + { + super.stop(); + + try + { + serverSocket.close(); + } + catch (IOException e) {/* no-op */} + + waitForGracefulStop(); + } + + /** + * {@inheritDoc} + */ + @Override + public void createServerSocket() throws DNSException + { + + try + { + serverSocket = new ServerSocket(settings.getPort(), settings.getMaxConnectionBacklog(), + Inet4Address.getByName(settings.getBindAddress())); + + } + catch (Exception e) + { + throw new DNSException(null, "Failed to create TCP server socket: " + e.getMessage(), e); + } + } + + /** + * {@inheritDoc} + */ + @Override + public Runnable getSocketAcceptTask() + { + return new AcceptTask(); + } + + /** + * {@inheritDoc} + */ + @Override + public Runnable getDNSRequestTask(Object s) + { + return new RequestTask((Socket)s); + } + + /* + * Thread that accepts socket connections + */ + private class AcceptTask implements Runnable + { + public void run() + { + while(running.get()) + { + + try + { + Socket s = serverSocket.accept(); + + s.setReceiveBufferSize(settings.getMaxRequestSize()); + s.setSoTimeout(settings.getReceiveTimeout()); + submitDNSRequest(s); + } + catch (Throwable e) + { + if (running.get()) + { + LOGGER.error("DNS TCP server socket dropped:" + e.getMessage()); + reconnect(); + } + } + } + } + } + + /* + * In the case that the server loses it connections, the accept socket needs to be re-established. + */ + private void reconnect() + { + // socket may already be closed, but clean up to be thorough. + try + { + serverSocket.close(); + } catch (IOException e) {/* no-op */} + + serverSocket = null; + while (serverSocket == null && running.get()) + { + try + { + createServerSocket(); + LOGGER.error("DNS TCP server socket re-established"); + } + catch (DNSException ex) + { + LOGGER.error("DNS TCP server socket failed to rebind. Trying again in 5 seconds."); + + // the socket creation failed.... + // sleep 5 seconds and come back around and try again + try + { + Thread.sleep(5000); + } + catch (InterruptedException iex) {/* no-op */} + } + } + } + + /* + * Task that handles incoming tasks + */ + private class RequestTask implements Runnable + { + private Socket requestSocket; + + public RequestTask(Socket s) + { + requestSocket = s; + } + + @SuppressWarnings("unused") + public void run() + { + Message response = null; + Message query = null; + int inLength; + DataInputStream dataIn; + DataOutputStream dataOut; + byte [] in; + + try + { + InputStream is = null; + try + { + is = requestSocket.getInputStream(); + dataIn = new DataInputStream(is); + inLength = dataIn.readUnsignedShort(); + } + catch (Exception e) + { + // don't fill up my logs due to input stream errors that can happen + // from DOS attaches + try + { + requestSocket.close(); + } + catch (IOException e2) {/* no-op */} + return; + } + in = new byte[inLength]; + dataIn.readFully(in); + + //LOGGER.info("Valid message... moving on."); + + try + { + query = responder.toMessage(in); + + response = responder.processRequest(query); + } + catch (DNSException e) + { + //LOGGER.info("TCP server error response."); + if (query != null) + response = responder.processError(query, e.getError()); + } + + if (response != null) + { + if (response.getRcode() == Rcode.NOERROR || response.getRcode() == Rcode.NXDOMAIN) + { + ++successCount; + if (response.getSectionArray(Section.ANSWER).length == 0) + ++missCount; + } + else + ++errorCount; + + //LOGGER.info("Sending back valid response."); + + dataOut = new DataOutputStream(requestSocket.getOutputStream()); + byte[] writeBytes = response.toWire(); + dataOut.writeShort(writeBytes.length); + dataOut.write(writeBytes); + } + else + ++errorCount; + } + catch (IOException e) + { + LOGGER.error("Wire/connection protocol error handing DNS request: " + e.getMessage(), e); + } + finally + { + try + { + requestSocket.close(); + } + catch (IOException e) {/* no-op */} + } + } + + } + + /** + * {@inheritDoc} + */ + @Override + public Long getMissedRequestCount() + { + return missCount; + } + + /** + * {@inheritDoc} + */ + @Override + public Long getSuccessfulRequestCount() + { + return successCount; + } + + /** + * {@inheritDoc} + */ + @Override + public Long getErrorRequestCount() + { + return errorCount; + } +} diff --git a/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/UDPServer.java b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/UDPServer.java new file mode 100644 index 000000000..4ed8df47a --- /dev/null +++ b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/UDPServer.java @@ -0,0 +1,287 @@ +/* + Copyright (c) 2010, Direct Project + All rights reserved. + + Authors: + Greg Meyer gm2552@cerner.com + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +Neither the name of The Direct Project (directproject.org) nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +package org.nhindirect.dns; + +import java.io.IOException; +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.Inet4Address; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.xbill.DNS.Message; +import org.xbill.DNS.Rcode; +import org.xbill.DNS.Section; + +/** + * UDP socket server that handled DNS requests over UDP. + * @author Greg Meyer + * @since 1.0 + */ +public class UDPServer extends DNSSocketServer +{ + private static final Log LOGGER = LogFactory.getFactory().getInstance(UDPServer.class); + + private static final int MAX_WIRE_SIZE = 512; + + + private DatagramSocket serverSock; + + private volatile long missCount = 0; + private volatile long errorCount = 0; + private volatile long successCount = 0; + + /** + * Creates a UDP server that listens to datagram packets. The server will not start accepting messages until the {@link #start()} method is called. + * @param settings The server settings. The settings contain specific IP and socket configuration parameters. + * @param responsder The DNS responder that will handle lookups. + * @throws DNSException + */ + public UDPServer(DNSServerSettings settings, DNSResponder responder) throws DNSException + { + super(settings, responder); + + registerMBean(this.getClass()); + } + + /** + * {@inheritDoc} + */ + public void start() throws DNSException + { + LOGGER.info("DNS UPD Server Starting"); + super.start(); + + if (LOGGER.isInfoEnabled()) + { + StringBuilder builder = new StringBuilder(); + builder.append("DNS UDP Server Startup Complete\r\n\tBind Address: ").append(settings.getBindAddress()); + builder.append("\r\n\tBind Port: ").append(settings.getPort()); + LOGGER.info(builder.toString()); + } + } + + /** + * {@inheritDoc} + */ + public void stop() throws DNSException + { + super.stop(); + + serverSock.close(); + + waitForGracefulStop(); + } + + /** + * {@inheritDoc} + */ + @Override + public void createServerSocket() throws DNSException + { + + try + { + serverSock = new DatagramSocket(settings.getPort(), Inet4Address.getByName(settings.getBindAddress())); + serverSock.setReceiveBufferSize(settings.getMaxRequestSize()); + serverSock.setSoTimeout(settings.getReceiveTimeout()); + } + catch (Exception e) + { + throw new DNSException(null, "Failed to create UDP server socket: " + e.getMessage(), e); + } + } + + /** + * {@inheritDoc} + */ + @Override + public Runnable getSocketAcceptTask() + { + return new ReceiveTask(); + } + + /** + * {@inheritDoc} + */ + @Override + public Runnable getDNSRequestTask(Object packet) + { + return new RequestTask((DatagramPacket)packet); + } + + /* + * Task that listens for datagram packets + */ + private class ReceiveTask implements Runnable + { + public void run() + { + + while(running.get()) + { + + try + { + byte[] inBuffer = new byte[settings.getMaxRequestSize()]; + DatagramPacket inPacket = new DatagramPacket(inBuffer, inBuffer.length); + + serverSock.receive(inPacket); + + submitDNSRequest(inPacket); + } + catch (Throwable e) + { + // udp has no state, so we can just call receive again + // unless it was closed + if (serverSock.isClosed() && running.get()) + { + LOGGER.error("DNS UDP server socket dropped:" + e.getMessage()); + reconnect(); + } + } + } + } + } + + /* + * In the event that the server drops its connection, we need to open up a new + * datagram socket to listen for datagram packets. + */ + private void reconnect() + { + // socket may already be closed, but clean up to be thorough. + serverSock.close(); + + serverSock = null; + while (serverSock == null && running.get()) + { + try + { + createServerSocket(); + LOGGER.error("DNS UDP server socket re-established"); + } + catch (DNSException ex) + { + LOGGER.error("DNS UDP server socket failed to rebind. Trying again in 5 seconds."); + + // the socket creation failed.... + // sleep 5 seconds and come back around and try again + try + { + Thread.sleep(5000); + } + catch (InterruptedException iex) {/* no-op */} + } + } + } + + /* + * Task that handles DNS requests. + */ + public class RequestTask implements Runnable + { + private DatagramPacket inPacket; + + public RequestTask(DatagramPacket inPacket) + { + this.inPacket = inPacket; + } + + @SuppressWarnings("unused") + public void run() + { + Message query = null; + Message response = null; + DatagramPacket outPacket = null; + + try + { + + try + { + //LOGGER.info("Got UDP DNS query. Translating"); + query = responder.toMessage(inPacket.getData()); + //LOGGER.info("Send UDP DNS query to service."); + response = responder.processRequest(query); + //LOGGER.info("UDP query returned from config service"); + } + catch (DNSException e) + { + //LOGGER.info("Sending UDP error response"); + if (query != null) + response = responder.processError(query, e.getError()); + } + + if (response != null) + { + if (response.getRcode() == Rcode.NOERROR || response.getRcode() == Rcode.NXDOMAIN) + { + ++successCount; + if (response.getSectionArray(Section.ANSWER).length == 0) + ++missCount; + } + else + ++errorCount; + + byte[] writeBytes = response.toWire(MAX_WIRE_SIZE); + outPacket = new DatagramPacket(writeBytes, + writeBytes.length, + inPacket.getAddress(), + inPacket.getPort()); + + //LOGGER.info("Sending UDP query valid response"); + serverSock.send(outPacket); + } + else + ++errorCount; + } + catch (IOException e) + { + LOGGER.error("Wire/connection protocol error handing DNS request: " + e.getMessage(), e); + } + } + + } + + /** + * {@inheritDoc} + */ + @Override + public Long getMissedRequestCount() + { + return missCount; + } + + /** + * {@inheritDoc} + */ + @Override + public Long getSuccessfulRequestCount() + { + return successCount; + } + + /** + * {@inheritDoc} + */ + @Override + public Long getErrorRequestCount() + { + return errorCount; + } +} diff --git a/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/annotation/ConfigServiceURL.java b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/annotation/ConfigServiceURL.java new file mode 100644 index 000000000..71cbccf68 --- /dev/null +++ b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/annotation/ConfigServiceURL.java @@ -0,0 +1,41 @@ +/* +Copyright (c) 2010, NHIN Direct Project +All rights reserved. + +Authors: + Umesh Madan umeshma@microsoft.com + Greg Meyer gm2552@cerner.com + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the distribution. Neither the name of the The NHIN Direct Project (nhindirect.org). +nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS +BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +THE POSSIBILITY OF SUCH DAMAGE. +*/ + +package org.nhindirect.dns.annotation; + + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import com.google.inject.BindingAnnotation; + +/** + * Guice annotation for the location of the configuration service. + * @author Greg Meyer + * @since 1.0 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target( {ElementType.FIELD, ElementType.PARAMETER}) +@BindingAnnotation +public @interface ConfigServiceURL {} \ No newline at end of file diff --git a/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/annotation/package-info.java b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/annotation/package-info.java new file mode 100644 index 000000000..d28d9a103 --- /dev/null +++ b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/annotation/package-info.java @@ -0,0 +1,4 @@ +/** + * Guice annotations. + */ +package org.nhindirect.dns.annotation; \ No newline at end of file diff --git a/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/config/DNSServerConfig.java b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/config/DNSServerConfig.java new file mode 100644 index 000000000..47cc00394 --- /dev/null +++ b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/config/DNSServerConfig.java @@ -0,0 +1,40 @@ +/* +Copyright (c) 2010, NHIN Direct Project +All rights reserved. + +Authors: + Greg Meyer gm2552@cerner.com + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the distribution. Neither the name of the The NHIN Direct Project (nhindirect.org). +nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS +BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +THE POSSIBILITY OF SUCH DAMAGE. +*/ + +package org.nhindirect.dns.config; + +import com.google.inject.Injector; + +/** + * The DNSServerConfig is responsible for loading configuration information from a URL and creating an injector that will subsequently + * be used to create instances of a {@link DNSServer}. + * @author Greg Meyer + * + * @since 1.0 + */ +public interface DNSServerConfig +{ + /** + * Gets a Guide Injector that can be used to create {@link DNSServer} objects based on configuration information. + * @return A Guide Injector that can be used to create {@link DNSServer} objects. + */ + public Injector getServerInjector(); +} diff --git a/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/config/RESTDNSServerConfig.java b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/config/RESTDNSServerConfig.java new file mode 100644 index 000000000..0f0eb8e54 --- /dev/null +++ b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/config/RESTDNSServerConfig.java @@ -0,0 +1,115 @@ +package org.nhindirect.dns.config; + +import java.net.URL; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.http.client.HttpClient; +import org.nhindirect.config.model.Setting; +import org.nhind.config.rest.SettingService; +import org.nhind.config.rest.impl.DefaultSettingService; +import org.nhindirect.common.rest.ServiceSecurityManager; +import org.nhindirect.dns.DNSServerSettings; +import org.nhindirect.dns.DNSStore; +import org.nhindirect.dns.module.DNSServerModule; +import org.nhindirect.dns.provider.BasicDNSServerSettingsProvider; +import org.nhindirect.dns.provider.ConfigServiceRESTDNSStoreProvider; + +import com.google.inject.Guice; +import com.google.inject.Injector; +import com.google.inject.Provider; + +public class RESTDNSServerConfig implements DNSServerConfig +{ + private static final Log LOGGER = LogFactory.getFactory().getInstance(WSDNSServerConfig.class); + + private static final String DNS_SERVER_BINDING = "DNSServerBindings"; + private static final String DNS_SERVER_PORT = "DNSServerPort"; + + private Provider storeProvider; + private Provider settings; + private HttpClient httpClient; + private ServiceSecurityManager secMgr; + + private final String configServiceLocation; + private final SettingService settingService; + + public RESTDNSServerConfig(String configServiceLocation, HttpClient httpClient, ServiceSecurityManager secMgr, + Provider storeProvider, Provider settings) + { + this.storeProvider = storeProvider; + this.configServiceLocation = configServiceLocation; + this.settings = settings; + this.httpClient = httpClient; + this.secMgr = secMgr; + + settingService = new DefaultSettingService(configServiceLocation, httpClient, secMgr); + } + + /** + * {@inheritDoc} + */ + @Override + public Injector getServerInjector() + { + LOGGER.info("Looking up DNS server configuration info from location " + configServiceLocation); + + Provider settingsProv = getServerSettings(); + + try + { + if (storeProvider == null) + storeProvider = new ConfigServiceRESTDNSStoreProvider(new URL(configServiceLocation), httpClient, secMgr); + } + catch (Exception e) + { + throw new IllegalArgumentException("Conguration location is not a valid URL", e); + } + DNSServerModule module = DNSServerModule.create(storeProvider, settingsProv); + + return Guice.createInjector(module); + } + + /* + * Just use the basic settings provider for now. Will only allow setting the port and IP bindings. + */ + private Provider getServerSettings() + { + String ipBindings = ""; + int port = 0; + + try + { + Setting setting = settingService.getSetting(DNS_SERVER_BINDING); + if (setting != null) + ipBindings = setting.getValue(); + + setting = settingService.getSetting(DNS_SERVER_PORT); + if (setting != null) + { + String sPort = setting.getValue(); + try + { + port = Integer.parseInt(sPort); + } + catch (Exception e) + { + LOGGER.warn("Could not parse port setting " + port + " from configuration service"); + } + } + } + catch (Exception e) + { + LOGGER.warn("Could not get DNS setting from web service."); + } + + if ((ipBindings == null || ipBindings.length() == 0) && port == 0 && settings != null) + { + LOGGER.info("Using DNS server settings from injected provider."); + return settings; + } + + LOGGER.info("Using DNS server settings from configuration service."); + return new BasicDNSServerSettingsProvider(ipBindings, port); + } +} diff --git a/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/config/WSDNSServerConfig.java b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/config/WSDNSServerConfig.java new file mode 100644 index 000000000..ed7b9b044 --- /dev/null +++ b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/config/WSDNSServerConfig.java @@ -0,0 +1,137 @@ +/* + Copyright (c) 2010, Direct Project + All rights reserved. + + Authors: + Greg Meyer gm2552@cerner.com + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +Neither the name of The Direct Project (directproject.org) nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +package org.nhindirect.dns.config; + +import java.net.URL; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.nhind.config.ConfigurationServiceProxy; +import org.nhind.config.Setting; +import org.nhindirect.dns.DNSServerSettings; +import org.nhindirect.dns.DNSStore; +import org.nhindirect.dns.module.DNSServerModule; +import org.nhindirect.dns.provider.BasicDNSServerSettingsProvider; +import org.nhindirect.dns.provider.ConfigServiceDNSStoreProvider; + +import com.google.inject.Guice; +import com.google.inject.Injector; +import com.google.inject.Provider; + +/** + * Loads DNS server configuration settings from the configuration service to create the injector. + * @author Greg Meyer + * + * @since 1.0 + */ +public class WSDNSServerConfig implements DNSServerConfig +{ + private static final Log LOGGER = LogFactory.getFactory().getInstance(WSDNSServerConfig.class); + + private static final String DNS_SERVER_BINDING = "DNSServerBindings"; + private static final String DNS_SERVER_PORT = "DNSServerPort"; + + private Provider storeProvider; + private Provider settings; + + + private final ConfigurationServiceProxy cfService; + private final URL configServiceLocation; + + /** + * Construct and configuration component with the location of the configuration file and an optional provider for creating + * instances of the DNSServer. + * @param configServiceLocation The full path of the XML configuration file. + * @param storeProvider An option provider used for creating instances of the {@link DNSStore}. If the provider is + * null, a default provider is used. + */ + public WSDNSServerConfig(URL configServiceLocation, Provider storeProvider, Provider settings) + { + this.storeProvider = storeProvider; + this.configServiceLocation = configServiceLocation; + this.settings = settings; + + cfService = new ConfigurationServiceProxy(configServiceLocation.toExternalForm()); + } + + /** + * {@inheritDoc} + */ + @Override + public Injector getServerInjector() + { + LOGGER.info("Looking up DNS server configuration info from location " + configServiceLocation.toExternalForm()); + + Provider settingsProv = getServerSettings(); + + if (storeProvider == null) + storeProvider = new ConfigServiceDNSStoreProvider(configServiceLocation); + + DNSServerModule module = DNSServerModule.create(storeProvider, settingsProv); + + return Guice.createInjector(module); + } + + /* + * Just use the basic settings provider for now. Will only allow setting the port and IP bindings. + */ + private Provider getServerSettings() + { + String ipBindings = ""; + int port = 0; + + try + { + Setting[] settings = cfService.getSettingsByNames(new String[] {DNS_SERVER_BINDING, DNS_SERVER_PORT}); + + if (settings != null && settings.length > 0) + { + for (Setting setting : settings) + { + if (setting.getName().equalsIgnoreCase(DNS_SERVER_BINDING)) + { + ipBindings = setting.getValue(); + } + else if (setting.getName().equalsIgnoreCase(DNS_SERVER_PORT)) + { + String sPort = setting.getValue(); + try + { + port = Integer.parseInt(sPort); + } + catch (Exception e) + { + LOGGER.warn("Could not parse port setting " + port + " from configuration service"); + } + } + } + } + } + catch (Exception e) + { + LOGGER.warn("Could not get DNS setting from web service."); + } + + if ((ipBindings == null || ipBindings.length() == 0) && port == 0 && settings != null) + { + LOGGER.info("Using DNS server settings from injected provider."); + return settings; + } + + LOGGER.info("Using DNS server settings from configuration service."); + return new BasicDNSServerSettingsProvider(ipBindings, port); + } +} diff --git a/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/config/package-info.java b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/config/package-info.java new file mode 100644 index 000000000..816344d50 --- /dev/null +++ b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/config/package-info.java @@ -0,0 +1,4 @@ +/** + * Utility class used by Guice modules for getting DNS server configuration information. + */ +package org.nhindirect.dns.config; \ No newline at end of file diff --git a/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/module/DNSServerConfigModule.java b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/module/DNSServerConfigModule.java new file mode 100644 index 000000000..898855031 --- /dev/null +++ b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/module/DNSServerConfigModule.java @@ -0,0 +1,92 @@ +/* +Copyright (c) 2010, NHIN Direct Project +All rights reserved. + +Authors: + Greg Meyer gm2552@cerner.com + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the distribution. Neither the name of the The NHIN Direct Project (nhindirect.org). +nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS +BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +THE POSSIBILITY OF SUCH DAMAGE. +*/ + +package org.nhindirect.dns.module; + +import java.net.URL; + +import org.nhindirect.dns.DNSServerSettings; +import org.nhindirect.dns.DNSStore; +import org.nhindirect.dns.config.DNSServerConfig; +import org.nhindirect.dns.provider.WSDNSServerConfigProvider; + + +import com.google.inject.AbstractModule; +import com.google.inject.Provider; + +/** + * Guice module for generating a configuration provider based on the URL protocol. + * @author Greg Meyer + * + * @since 1.0 + */ +public class DNSServerConfigModule extends AbstractModule +{ + private final URL configLocation; + private final Provider storeProvider; + private final Provider settings; + + /** + * Creates a DNSServerConfigModule that is used by a Guice to create a {@link DNSServerConfig} object. The DNSServerConfig + * object is subsequently used to create {@link DNSServer} objects. + * @param configLocation The URL that the {@link DNSServerConfig} will use to lookup configuration information. + * @param storeProvider An optional {@link DNSStore} provider that will create instances of DNSStore objects. + * @param settings Optional DNS server settings. Overridden by settings from the configuration service. + * @return A configured Guice module used for create {@link DNSServerConfig} objects. + */ + public static DNSServerConfigModule create(URL configLocation, Provider storeProvider, Provider settings) + { + return new DNSServerConfigModule(configLocation, storeProvider, settings); + } + + /* + * Private constructor. + */ + private DNSServerConfigModule(URL configLocation, Provider storeProvider, Provider settings) + { + this.configLocation = configLocation; + this.storeProvider = storeProvider; + this.settings = settings; + } + + /** + * {@inheritDoc} + */ + @Override + protected void configure() + { + Provider provider = null; + + if (provider == null) + { + if (configLocation.getProtocol().equalsIgnoreCase("HTTP") || configLocation.getProtocol().equalsIgnoreCase("HTTPS")) + { + // web services based + provider = new WSDNSServerConfigProvider(configLocation, storeProvider, settings); + } + else + { + throw new IllegalArgumentException("Configuration URL uses an unsupported protocol: " + configLocation.getProtocol()); + } + } + bind(DNSServerConfig.class).toProvider(provider); + } +} diff --git a/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/module/DNSServerModule.java b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/module/DNSServerModule.java new file mode 100644 index 000000000..235d7836b --- /dev/null +++ b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/module/DNSServerModule.java @@ -0,0 +1,112 @@ +/* + Copyright (c) 2010, Direct Project + All rights reserved. + + Authors: + Umesh Madan umeshma@microsoft.com + Chris Lomonico chris.lomonico@surescripts.com + Greg Meyer gm2552@cerner.com + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +Neither the name of The Direct Project (directproject.org) nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +package org.nhindirect.dns.module; + +import java.net.URL; + +import org.nhindirect.dns.DNSServerSettings; +import org.nhindirect.dns.DNSStore; +import org.nhindirect.dns.annotation.ConfigServiceURL; +import org.nhindirect.dns.provider.BasicDNSServerSettingsProvider; + + +import com.google.inject.AbstractModule; +import com.google.inject.Provider; + +/** + * Guice module for configuring and creating {@link DNSServer} instances. Allows configuration either using just a URL to + * a configuration service, or more advanced options by using {@link Provider providers}. + * @author Greg Meyer + * @since 1.0 + */ +public class DNSServerModule extends AbstractModule +{ + private final URL configServiceURL; + private final Provider dnsStore; + private final Provider settings; + + /** + * Creates a module using just a configuration URL. The server is created using the ConfigServiceDNSStore class with + * default server settings. + * @param configServiceURL A URL that provides the location to the configuration service. + * @return A DNSServerModule used to create instances of the DNS server. + */ + public static DNSServerModule create(URL configServiceURL) + { + if (configServiceURL == null) + throw new IllegalArgumentException("URL cannot be null."); + + return new DNSServerModule(configServiceURL, null, null); + } + + /** + * Creates a module using a specific {@link DNSStore} provider and default server settings. + * @param dnsStore A provider used to create instances of the {@link DNSStore}. + * @return A DNSServerModule used to create instances of the DNS server. + */ + public static DNSServerModule create(Provider dnsStore) + { + if (dnsStore == null) + throw new IllegalArgumentException("dnsStore cannot be null."); + + return new DNSServerModule(null, dnsStore, new BasicDNSServerSettingsProvider()); + } + + /** + * Creates a module using specific {@link DNSStore} and {@link DNSServerSettings} providers + * @param dnsStore A provider used to create instances of the {@link DNSStore}. + * @param settings A provider used to create instances of the {@link DNSServerSettings}. + * @return A DNSServerModule used to create instances of the DNS server. + */ + public static DNSServerModule create(Provider dnsStore, Provider settings) + { + if (dnsStore == null) + throw new IllegalArgumentException("dnsStore cannot be null."); + + if (settings == null) + settings = new BasicDNSServerSettingsProvider(); + + return new DNSServerModule(null, dnsStore, settings); + } + + /* + * Private constructor. + */ + private DNSServerModule(URL configServiceURL, Provider dnsStore, Provider settings) + { + this.configServiceURL = configServiceURL; + this.dnsStore = dnsStore; + this.settings = settings; + } + + /** + * {@inheritDoc} + */ + @Override + protected void configure() + { + if (configServiceURL != null) + this.bind(URL.class).annotatedWith(ConfigServiceURL.class).toInstance(configServiceURL); + else + { + this.bind(DNSStore.class).toProvider(dnsStore); + this.bind(DNSServerSettings.class).toProvider(settings); + } + } +} + diff --git a/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/module/package-info.java b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/module/package-info.java new file mode 100644 index 000000000..35edd3e72 --- /dev/null +++ b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/module/package-info.java @@ -0,0 +1,4 @@ +/** + * Guice modules for configuration and creating DNS server instances. + */ +package org.nhindirect.dns.module; \ No newline at end of file diff --git a/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/package-info.java b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/package-info.java new file mode 100644 index 000000000..199e422e8 --- /dev/null +++ b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/package-info.java @@ -0,0 +1,4 @@ +/** + * Direct project DNS services and responders. + */ +package org.nhindirect.dns; \ No newline at end of file diff --git a/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/provider/AbstractConfigDNSStoreProvider.java b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/provider/AbstractConfigDNSStoreProvider.java new file mode 100644 index 000000000..072146322 --- /dev/null +++ b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/provider/AbstractConfigDNSStoreProvider.java @@ -0,0 +1,29 @@ +package org.nhindirect.dns.provider; + +import java.net.URL; + +import org.nhindirect.dns.DNSStore; + +import com.google.inject.Provider; + +/** + * Abstract Guice provider for DNSStoreProviders that use the configuration service to retrieve runtime configuration information. + * @author Greg Meyer + * + * @since 1.0 + */ + +public abstract class AbstractConfigDNSStoreProvider implements Provider +{ + protected final URL configServiceURL; + + /** + * Provider constructor. + * @param configServiceURL A URL to the location of the DNS configuration service. + */ + public AbstractConfigDNSStoreProvider(URL configServiceURL) + { + this.configServiceURL = configServiceURL; + } + +} diff --git a/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/provider/BasicDNSServerSettingsProvider.java b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/provider/BasicDNSServerSettingsProvider.java new file mode 100644 index 000000000..b6d656213 --- /dev/null +++ b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/provider/BasicDNSServerSettingsProvider.java @@ -0,0 +1,68 @@ +/* + Copyright (c) 2010, Direct Project + All rights reserved. + + Authors: + Greg Meyer gm2552@cerner.com + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +Neither the name of The Direct Project (directproject.org) nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +package org.nhindirect.dns.provider; + + +import org.nhindirect.dns.DNSServerSettings; + +import com.google.inject.Provider; + +/** Guice provider for configuring a minimal set of DNSServer settings. + * @author Greg Meyer + * + * @since 1.0 + */ +public class BasicDNSServerSettingsProvider implements Provider +{ + + private final String bindings; + private final int port; + + /** + * Creates a provider using the default DNS server settings. + */ + public BasicDNSServerSettingsProvider() + { + this(null, 0); + } + + /** + * Creates a provider allowing the IP binding addresses and port to be overridden. + * @param bindings A comma delimited list of IP binding addresses. + * @param port The IP port that the server will use to listen for DNS requests. + */ + public BasicDNSServerSettingsProvider(String bindings, int port) + { + this.bindings = bindings; + this.port = port; + } + + /** + * {@inheritDoc} + */ + @Override + public DNSServerSettings get() + { + DNSServerSettings settings = new DNSServerSettings(); + if (port > 0) + settings.setPort(port); + + if (bindings != null && !bindings.isEmpty()) + settings.setBindAddress(bindings); + + return settings; + } +} diff --git a/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/provider/ConfigServiceDNSStoreProvider.java b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/provider/ConfigServiceDNSStoreProvider.java new file mode 100644 index 000000000..4c7a32df2 --- /dev/null +++ b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/provider/ConfigServiceDNSStoreProvider.java @@ -0,0 +1,50 @@ +/* + Copyright (c) 2010, Direct Project + All rights reserved. + + Authors: + Greg Meyer gm2552@cerner.com + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +Neither the name of The Direct Project (directproject.org) nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +package org.nhindirect.dns.provider; + +import java.net.URL; + +import org.nhindirect.dns.ConfigServiceDNSStore; +import org.nhindirect.dns.DNSStore; + + +/** + * Guice provider for creating {@link ConfigServiceDNSStore} instances. + * @author Greg Meyer + * + * @since 1.0 + */ +public class ConfigServiceDNSStoreProvider extends AbstractConfigDNSStoreProvider +{ + + /** + * Provider constructor. + * @param configServiceURL A URL to the location of the DNS configuration service. + */ + public ConfigServiceDNSStoreProvider(URL configServiceURL) + { + super(configServiceURL); + } + + /** + * {@inheritDoc} + */ + @Override + public DNSStore get() + { + return new ConfigServiceDNSStore(configServiceURL); + } +} diff --git a/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/provider/ConfigServiceRESTDNSStoreProvider.java b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/provider/ConfigServiceRESTDNSStoreProvider.java new file mode 100644 index 000000000..7ad000bd0 --- /dev/null +++ b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/provider/ConfigServiceRESTDNSStoreProvider.java @@ -0,0 +1,36 @@ +package org.nhindirect.dns.provider; + +import java.net.MalformedURLException; +import java.net.URL; + +import org.apache.http.client.HttpClient; +import org.nhindirect.common.rest.ServiceSecurityManager; +import org.nhindirect.dns.DNSStore; +import org.nhindirect.dns.RESTServiceDNSStore; + +public class ConfigServiceRESTDNSStoreProvider extends AbstractConfigDNSStoreProvider +{ + protected final HttpClient httpClient; + protected final ServiceSecurityManager secManager; + + /** + * Provider constructor. + * @param configServiceURL A URL to the location of the DNS configuration service. + * @throws MalformedURLException + */ + public ConfigServiceRESTDNSStoreProvider(URL configServiceURL, HttpClient httpClient, ServiceSecurityManager secManager) + { + super(configServiceURL); + this.httpClient = httpClient; + this.secManager = secManager; + } + + /** + * {@inheritDoc} + */ + @Override + public DNSStore get() + { + return new RESTServiceDNSStore(configServiceURL.toExternalForm(), httpClient, secManager); + } +} diff --git a/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/provider/RESTDNSServerConfigProvider.java b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/provider/RESTDNSServerConfigProvider.java new file mode 100644 index 000000000..99e61afa9 --- /dev/null +++ b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/provider/RESTDNSServerConfigProvider.java @@ -0,0 +1,38 @@ +package org.nhindirect.dns.provider; + +import org.apache.http.client.HttpClient; +import org.nhindirect.common.rest.ServiceSecurityManager; +import org.nhindirect.dns.DNSServerSettings; +import org.nhindirect.dns.DNSStore; +import org.nhindirect.dns.config.DNSServerConfig; +import org.nhindirect.dns.config.RESTDNSServerConfig; + +import com.google.inject.Provider; + +public class RESTDNSServerConfigProvider implements Provider +{ + private final String configURL; + private final Provider storeProvider; + private final Provider settings; + private final HttpClient httpClient; + private final ServiceSecurityManager secMgr; + + public RESTDNSServerConfigProvider(String configURL, HttpClient httpClient, ServiceSecurityManager secMgr, + Provider storeProvider, Provider settings) + { + this.configURL = configURL; + this.storeProvider = storeProvider; + this.settings = settings; + this.httpClient = httpClient; + this.secMgr = secMgr; + } + + /** + * {@inheritDoc} + */ + @Override + public DNSServerConfig get() + { + return new RESTDNSServerConfig(configURL, httpClient, secMgr, storeProvider, settings); + } +} diff --git a/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/provider/WSDNSServerConfigProvider.java b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/provider/WSDNSServerConfigProvider.java new file mode 100644 index 000000000..dc63e20c6 --- /dev/null +++ b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/provider/WSDNSServerConfigProvider.java @@ -0,0 +1,67 @@ +/* +Copyright (c) 2010, NHIN Direct Project +All rights reserved. + +Authors: + Umesh Madan umeshma@microsoft.com + Greg Meyer gm2552@cerner.com + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the distribution. Neither the name of the The NHIN Direct Project (nhindirect.org). +nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS +BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +THE POSSIBILITY OF SUCH DAMAGE. +*/ + +package org.nhindirect.dns.provider; + +import java.net.URL; + +import org.nhindirect.dns.DNSServerSettings; +import org.nhindirect.dns.DNSStore; +import org.nhindirect.dns.config.DNSServerConfig; +import org.nhindirect.dns.config.WSDNSServerConfig; + +import com.google.inject.Provider; + +/** + * Configuration provider for web service based configuration of the DNS Server. + * @author Greg Meyer + * + * @since 1.0 + */ +public class WSDNSServerConfigProvider implements Provider +{ + private final URL configURL; + private final Provider storeProvider; + private final Provider settings; + + /** + * Creates a provider with the location of the configuration service and an optional {@link DNSStore} provider. + * @param configURL A URL that contains the location of the configuration service. + * @param storeProvider An option provider used for creating specific instances of a {@link DNSStore} + * @param settings Optional DNS server settings. Overridden by settings from the configuration service. + */ + public WSDNSServerConfigProvider(URL configURL, Provider storeProvider, Provider settings) + { + this.configURL = configURL; + this.storeProvider = storeProvider; + this.settings = settings; + } + + /** + * {@inheritDoc} + */ + @Override + public DNSServerConfig get() + { + return new WSDNSServerConfig(configURL, storeProvider, settings); + } +} diff --git a/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/provider/package-info.java b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/provider/package-info.java new file mode 100644 index 000000000..2799b19e3 --- /dev/null +++ b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/provider/package-info.java @@ -0,0 +1,4 @@ +/** + * Guice providers for creating DNS server configuration and server classes. + */ +package org.nhindirect.dns.provider; \ No newline at end of file diff --git a/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/service/DNSServerService.java b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/service/DNSServerService.java new file mode 100644 index 000000000..6015ca20e --- /dev/null +++ b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/service/DNSServerService.java @@ -0,0 +1,151 @@ +/* +Copyright (c) 2010, NHIN Direct Project +All rights reserved. + +Authors: + Greg Meyer gm2552@cerner.com + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the distribution. Neither the name of the The NHIN Direct Project (nhindirect.org). +nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS +BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +package org.nhindirect.dns.service; + +import java.net.URL; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.nhindirect.dns.DNSException; +import org.nhindirect.dns.DNSServer; +import org.nhindirect.dns.DNSServerFactory; +import org.nhindirect.dns.DNSServerSettings; +import org.nhindirect.dns.DNSStore; +import org.nhindirect.dns.provider.AbstractConfigDNSStoreProvider; +import org.nhindirect.dns.provider.BasicDNSServerSettingsProvider; + +import com.google.inject.Provider; + +/** + * Service wrapper that instantiates and configures the DNS server. + * @author Greg Meyer + * + * @since 1.0 + */ +public class DNSServerService +{ + protected static final String DNS_STORE_PROVIDER_VAR = "org.nhindirect.dns.DNSStoreProviderClass"; + + private static final Log LOGGER = LogFactory.getFactory().getInstance(DNSServerService.class); + + protected final DNSServer server; + + /** + * Creates the service wrapper with the location of the configuration service and server settings. + * @param configLocation URL with the location of the configuration service. + * @param settings Default settings for the server. Settings in the configuration service can over ride these settings. + * @throws DNSException + * + * @since 1.0 + */ + public DNSServerService(URL configLocation, DNSServerSettings settings) throws DNSException + { + LOGGER.info("Creating the DNSServer using configuration location " + configLocation.toExternalForm()); + + + BasicDNSServerSettingsProvider settingsProv = new BasicDNSServerSettingsProvider(settings.getBindAddress(), settings.getPort()); + + Provider dnsStoreProvider = getDNSStoreProvider(configLocation); + + server = DNSServerFactory.createDNSServer(configLocation, dnsStoreProvider, settingsProv); + + LOGGER.info("DNS Server created. Starting server."); + server.start(); + + Runtime.getRuntime().addShutdownHook(new Thread() + { + public void run() + { + try + { + LOGGER.info("Shutdown hook detected. Intiate server shutdown."); + stopService(); + } + catch (DNSException e) {/* no-op */} + + } + }); + } + + /** + * Creates a {@link Provider} instance based on the system property + * org.nhindirect.dns.DNSStoreProviderClass. This property is the fully qualified class name + * of the provider. If the provider extends the {@link AbstractConfigDNSStoreProvider} class, then the configuration service + * location will be passed at construction time. + * @param configLocation The URL of the configuration service. + * @return A constructed instance of a provider. If the system cannot locate the Provider class specified by the + * org.nhindirect.dns.DNSStoreProviderClass system property of if the property does not exist, the method + * will return null. + * + */ + @SuppressWarnings("unchecked") + protected Provider getDNSStoreProvider(URL configLocation) + { + Provider retVal = null; + + // get the system property + String className = System.getProperty(DNS_STORE_PROVIDER_VAR); + if (className != null && !className.isEmpty()) + { + try + { + Class loadedClazz = DNSServerService.class.getClassLoader().loadClass(className); + + if (AbstractConfigDNSStoreProvider.class.isAssignableFrom(loadedClazz)) + { + // this provider takes a URL for the constructor + retVal = (Provider)loadedClazz.getConstructor(URL.class).newInstance(configLocation); + } + else + retVal = (Provider)loadedClazz.newInstance(); + + LOGGER.info("Loaded Provider class " + className + " for creating the DNSStore"); + } + catch(Throwable e) + { + LOGGER.error("Could not load or construct instance of Provider class " + className + " A default " + + "provider will be used." , e); + } + } + else + LOGGER.info("A DNSStore provider class was not set. A default provider will be used."); + + return retVal; + } + + /** + * Stops and shutdown the service. + * @throws DNSException + * + * @since 1.0 + */ + public synchronized void stopService() throws DNSException + { + if (server != null) + { + LOGGER.info("Shutting down DNS server."); + server.stop(); + } + + } +} diff --git a/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/service/SimpleServiceRunner.java b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/service/SimpleServiceRunner.java new file mode 100644 index 000000000..73152155b --- /dev/null +++ b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/service/SimpleServiceRunner.java @@ -0,0 +1,249 @@ +/* + Copyright (c) 2010, Direct Project + All rights reserved. + + Authors: + Greg Meyer gm2552@cerner.com + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +Neither the name of The Direct Project (directproject.org) nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +package org.nhindirect.dns.service; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.net.Inet4Address; +import java.net.URL; +import java.security.Security; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.nhindirect.dns.DNSException; +import org.nhindirect.dns.DNSServerSettings; + + +/** + * Simple command line based application that launches the DNS server. Use the -help runtime parameter + * to see usage. + * @author Greg Meyer + * @since 1.0 + */ +public class SimpleServiceRunner +{ + private static final Log LOGGER = LogFactory.getFactory().getInstance(SimpleServiceRunner.class); + + private static final String MODE_STANDALONE = "STANDALONE"; + private static final String MODE_SERVER = "SERVER"; + + private static int port; + private static String bind; + private static URL servURL; + private static String mode; + + static + { + Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider()); + + port = 53; + bind = "0.0.0.0"; + mode = MODE_STANDALONE; + + try + { + servURL = new URL("http://localhost:8080/config-service/ConfigurationService"); + } + catch (Exception e) {/* no-op */} + } + + /** + * Main entry point into the application. + * @param argv Command line arguments. + */ + public static void main(String[] argv) + { + + // Check parameters + for (int i = 0; i < argv.length; i++) + { + String arg = argv[i]; + + // Options + if (!arg.startsWith("-")) + { + System.err.println("Error: Unexpected argument [" + arg + "]\n"); + printUsage(); + System.exit(-1); + } + else if (arg.equalsIgnoreCase("-p")) + { + if (i == argv.length - 1 || argv[i + 1].startsWith("-")) + { + System.err.println("Error: Missing port."); + System.exit(-1); + } + + port = Integer.parseInt(argv[++i]); + + } + else if (arg.equals("-b")) + { + if (i == argv.length - 1 || argv[i + 1].startsWith("-")) + { + System.err.println("Error: Missing bind IP address."); + System.exit(-1); + } + bind = argv[++i]; + + // validate its a valid IP addresses + String checkIP = ""; + try + { + String[] ips = bind.split(","); + for (String ip : ips) + { + checkIP = ip; + Inet4Address.getByName(checkIP); + } + } + catch(Exception e) + { + System.err.println("Error in bind IP address " + checkIP + " : " + e.getMessage()); + System.exit(-1); + } + } + else if (arg.equals("-u")) + { + if (i == argv.length - 1 || argv[i + 1].startsWith("-")) + { + System.err.println("Error: Missing service URL"); + System.exit(-1); + } + String url = argv[++i]; + try + { + servURL = new URL(url); + } + catch (Exception e) + { + System.err.println("Error in service URL parameter: " + e.getMessage()); + System.exit(-1); + } + } + else if (arg.equals("-m")) + { + if (i == argv.length - 1 || argv[i + 1].startsWith("-")) + { + System.err.println("Error: Missing mode"); + System.exit(-1); + } + mode = argv[++i]; + if (!mode.equalsIgnoreCase(MODE_STANDALONE) && !mode.equalsIgnoreCase(MODE_SERVER)) + { + System.err.println("Unknown mode: " + mode); + System.exit(-1); + + } + } + else if (arg.equals("-help")) + { + printUsage(); + System.exit(-1); + } + else + { + System.err.println("Error: Unknown argument " + arg + "\n"); + printUsage(); + System.exit(-1); + } + } + + startAndRun(); + + if (mode.equalsIgnoreCase(MODE_STANDALONE)) + System.exit(0); + + } + + /* + * Creates, intializes, and runs the server. + */ + private static void startAndRun() + { + StringBuffer buffer = new StringBuffer("Starting DNS server. Settings:"); + buffer.append("\r\n\tBind Addresses: ").append(bind); + buffer.append("\r\n\tListen Port: ").append(port); + buffer.append("\r\n\tService URL: ").append(servURL.toString()); + LOGGER.info(buffer.toString() + "\n"); + + + DNSServerService server = null; + try + { + DNSServerSettings settings = new DNSServerSettings(); + settings.setPort(port); + settings.setBindAddress(bind); + + server = new DNSServerService(servURL, settings); + } + catch (DNSException e) + { + LOGGER.error("Server failed to start: " + e.getMessage(), e); + return; + } + + if (mode.equalsIgnoreCase(MODE_STANDALONE)) + { + LOGGER.info("\r\nServer running.... Press Enter or Return to stop."); + + InputStreamReader input = new InputStreamReader(System.in); + BufferedReader reader = new BufferedReader(input); + + try + { + reader.readLine(); + + LOGGER.info("Shutting down server. Wait 5 seconds for cleanup."); + + server.stopService(); + + Thread.sleep(5000); + + LOGGER.info("Server stopped"); + } + catch (Exception e) + { + + } + } + else + LOGGER.info("\r\nServer running."); + } + + /* + * Prints the command line usage. + */ + private static void printUsage() + { + StringBuffer use = new StringBuffer(); + use.append("Usage:\n"); + use.append("java SimpleDNSServiceRunner (options)...\n\n"); + use.append("options:\n"); + use.append("-p port IP listener port.\n"); + use.append(" Default: 53\n\n"); + use.append("-b bind Comma limited list of IP addresses to bind to.\n"); + use.append(" Default: 0.0.0.0 (All IP addresses on local machine)\n\n"); + use.append("-u URL URL of DNS configuration service.\n"); + use.append(" Default: http://localhost:8080/config-service/ConfigurationService\n\n"); + use.append("-m mode Run mode: STANDALONE or SERVER.\n"); + use.append(" Default: STANDALONE\n\n"); + + + System.err.println(use); + } + +} diff --git a/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/service/package-info.java b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/service/package-info.java new file mode 100644 index 000000000..ca50b190c --- /dev/null +++ b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/service/package-info.java @@ -0,0 +1,4 @@ +/** + * Direct project DNS application/service wrappers and entry points. + */ +package org.nhindirect.dns.service; \ No newline at end of file diff --git a/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/tools/CertCommands.java b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/tools/CertCommands.java new file mode 100644 index 000000000..db009b24c --- /dev/null +++ b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/tools/CertCommands.java @@ -0,0 +1,221 @@ +package org.nhindirect.dns.tools; + + +import java.io.File; +import java.io.IOException; +import java.security.cert.X509Certificate; +import java.util.Arrays; + +import org.apache.commons.io.FileUtils; +import org.nhind.config.ConfigurationServiceProxy; +import org.nhind.config.EntityStatus; +import org.nhindirect.dns.tools.printers.CertRecordPrinter; +import org.nhindirect.dns.tools.printers.RecordPrinter; +import org.nhindirect.dns.tools.utils.Command; +import org.nhindirect.dns.tools.utils.StringArrayUtil; +import org.nhindirect.dns.utils.CertUtils; +import org.nhindirect.stagent.CryptoExtensions; +import org.nhindirect.stagent.cert.X509CertificateEx; + +public class CertCommands +{ + private static final String LIST_CERTIFICATES_USAGE = "Lists certificates in the system"; + + private static final String LIST_EMAIL_CERTIFICATES_USAGE = "Lists certificates by a given email address or domain" + + "\r\n address" + + "\r\n\t address: The email address or domain to search for. Certificates are mathed on the subject alternative name field of legacy email address of the certificate"; + + private static final String IMPORT_PUBLIC_CERT_USAGE = "Imports a certificate that does not contain private key information" + + "\r\n certfile" + + "\r\n\t certfile: Fully qualified path and file name of the X509 certificate file. Place the file name in quotes (\"\") if there are spaces in the path or name."; + + private static final String IMPORT_PRIVATE_CERT_USAGE = "Imports a certificate with a private key an optional passphrase. \r\n" + + "Files should be in pkcs12 format." + + "\r\n certfile [passphrase]" + + "\r\n\t certfile: Fully qualified path and file name of the pkcs12 certificate file. Place the file name in quotes (\"\") if there are spaces in the path or name." + + "\r\n\t [passphrase]: Optional passphrase to decrypt the pkcs12 file."; + + private static final String ADD_IPKIX_CERT_USAGE = "Add an IPKIX record with a subject and URL. \r\n" + + "\r\n subject URL" + + "\r\n subject: email address or domain name" + + "\r\n\t URL: Fully qualified URL to certificate"; + + private static final String REMOVED_CERTIFICATE_USAGE = "Removes a certifacte from the system by owner." + + "\r\n owner" + + "\r\n\t owner: owner or URL of the certificate to be removed"; + + + protected ConfigurationServiceProxy proxy; + + protected RecordPrinter certPrinter; + + public CertCommands(ConfigurationServiceProxy proxy) + { + this.proxy = proxy; + + this.certPrinter = new CertRecordPrinter(); + } + + @Command(name = "ListCerts", usage = LIST_CERTIFICATES_USAGE) + public void listCerts(String[] args) + { + try + { + final org.nhind.config.Certificate[] certs = proxy.listCertificates(1, 1000, null); + if (certs == null || certs.length == 0) + System.out.println("No certificates found"); + else + { + certPrinter.printRecords(Arrays.asList(certs)); + } + } + catch (Exception e) + { + System.out.println("Failed to lookup certificates: " + e.getMessage()); + } + + } + + @Command(name = "ListCertsByAddress", usage = LIST_EMAIL_CERTIFICATES_USAGE) + public void listCertsByAddress(String[] args) + { + String owner = StringArrayUtil.getRequiredValue(args, 0); + + try + { + final org.nhind.config.Certificate[] certs = proxy.getCertificatesForOwner(owner, null); + + if (certs == null || certs.length == 0) + System.out.println("No certificates found"); + else + { + certPrinter.printRecords(Arrays.asList(certs)); + } + } + catch (Exception e) + { + System.out.println("Failed to lookup certificates: " + e.getMessage()); + } + } + + @Command(name = "AddPublicCert", usage = IMPORT_PUBLIC_CERT_USAGE) + public void importPublicCert(String[] args) + { + final String fileLoc = StringArrayUtil.getRequiredValue(args, 0); + try + { + final X509Certificate cert = CertUtils.certFromFile(fileLoc); + + + final org.nhind.config.Certificate addCert = new org.nhind.config.Certificate(); + addCert.setData(cert.getEncoded()); + addCert.setOwner(CryptoExtensions.getSubjectAddress(cert)); + addCert.setPrivateKey(false); + addCert.setStatus(EntityStatus.ENABLED); + + proxy.addCertificates(new org.nhind.config.Certificate[] {addCert}); + System.out.println("Successfully imported public certificate."); + + } + catch (IOException e) + { + System.out.println("Error reading file " + fileLoc + " : " + e.getMessage()); + return; + } + ///CLOVER:OFF + catch (Exception e) + { + System.out.println("Error importing certificate " + fileLoc + " : " + e.getMessage()); + } + ///CLOVER:ON + + } + + @Command(name = "AddPrivateCert", usage = IMPORT_PRIVATE_CERT_USAGE) + public void importPrivateCert(String[] args) + { + final String fileLoc = StringArrayUtil.getRequiredValue(args, 0); + final String passPhrase = StringArrayUtil.getOptionalValue(args, 1, ""); + try + { + + final byte[] certBytes = FileUtils.readFileToByteArray(new File(fileLoc)); + + final byte[] insertBytes = (passPhrase == null || passPhrase.isEmpty()) ? + certBytes : CertUtils.pkcs12ToStrippedPkcs12(certBytes, passPhrase); + + final X509Certificate cert = CertUtils.toX509Certificate(insertBytes); + + org.nhind.config.Certificate addCert = new org.nhind.config.Certificate(); + addCert.setData(certBytes); + addCert.setOwner(CryptoExtensions.getSubjectAddress(cert)); + addCert.setPrivateKey(cert instanceof X509CertificateEx); + addCert.setStatus(EntityStatus.ENABLED); + + proxy.addCertificates(new org.nhind.config.Certificate[] {addCert}); + System.out.println("Successfully imported private certificate."); + + } + catch (IOException e) + { + System.out.println("Error reading file " + fileLoc + " : " + e.getMessage()); + return; + } + catch (Exception e) + { + System.out.println("Error importing certificate " + fileLoc + " : " + e.getMessage()); + } + } + + @Command(name = "AddIPKIXCert", usage = ADD_IPKIX_CERT_USAGE) + public void addIPKIXCert(String[] args) + { + final String owner = StringArrayUtil.getRequiredValue(args, 0); + final String URL = StringArrayUtil.getRequiredValue(args, 1); + + try + { + + org.nhind.config.Certificate addCert = new org.nhind.config.Certificate(); + addCert.setData(URL.getBytes()); + addCert.setOwner(owner); + addCert.setPrivateKey(false); + addCert.setStatus(EntityStatus.ENABLED); + + proxy.addCertificates(new org.nhind.config.Certificate[] {addCert}); + System.out.println("Successfully added IPKIX certificate URL."); + + + } + catch (Exception e) + { + System.out.println("Error add IPKIX URL: " + e.getMessage()); + } + } + + @Command(name = "RemoveCert", usage = REMOVED_CERTIFICATE_USAGE) + public void removeCert(String[] args) + { + final String owner = StringArrayUtil.getRequiredValue(args, 0); + + try + { + proxy.removeCertificatesForOwner(owner); + System.out.println("Successfully removed certificate for owner." + owner); + } + catch (Exception e) + { + System.out.println("Error removing certificate for owner " + owner + " : " + e.getMessage()); + } + } + + public void setRecordPrinter(RecordPrinter printer) + { + this.certPrinter = printer; + } + + public void setConfigurationProxy(ConfigurationServiceProxy proxy) + { + this.proxy = proxy; + } +} diff --git a/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/tools/DNSManager.java b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/tools/DNSManager.java new file mode 100644 index 000000000..ebb899894 --- /dev/null +++ b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/tools/DNSManager.java @@ -0,0 +1,153 @@ +/* +Copyright (c) 2010, NHIN Direct Project +All rights reserved. + +Authors: + Greg Meyer gm2552@cerner.com + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the distribution. Neither the name of the The NHIN Direct Project (nhindirect.org). +nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS +BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +package org.nhindirect.dns.tools; + +import java.net.URL; +import java.util.Arrays; + +import org.nhind.config.ConfigurationServiceProxy; +import org.nhindirect.dns.tools.utils.Commands; + +/** + * Command line tool for managing DNS entries in the Direct Project configuration service. + *

+ * The tool can either be run directly from the command prompt by passing arguments directly from + * the command line or can be run interactively by passing 0 parameters. The only exception is setting + * the URL to the configuration service. By default the manager uses "http://localhost:8081/config-service/ConfigurationService" + * as the config URL, but can be changed using the configURL command line parameters (it must be the first parameter on the + * command line). + *

+ * DNSManager configURL http://someserver:8081/config-service/ConfigurationService + * @author Greg Meyer + * + * @since 1.0 + */ +public class DNSManager +{ + private static final String DEFAULT_CONFIG_URL = "http://localhost:8081/config-service/ConfigurationService"; + + private final Commands commands; + + private static boolean exitOnEndCommands = true; + + /** + * Application entry point. + * @param args Command line arguments. + * + * @since 1.0 + */ + public static void main(String[] args) + { + String[] passArgs = null; + String configURL = null; + + // get the config URL if it exist + if (args.length > 1) + { + // check if the first argument is the config url + if (args[0].equalsIgnoreCase("configurl")) + { + //the next argument should be the config URL + configURL = args[1]; + if (args.length > 2) + passArgs = (String[])Arrays.copyOfRange(args, 2, args.length); + else + passArgs = new String[0]; + + } + } + + if (configURL == null) + { + configURL = DEFAULT_CONFIG_URL; + passArgs = args; + } + + DNSManager manager = null; + try + { + manager = new DNSManager(new URL(configURL)); + } + catch (Exception e) + { + System.err.println("Invalid config URL"); + } + + boolean runCommand = false; + + if (manager != null) + { + runCommand = manager.run(passArgs); + } + + if (exitOnEndCommands) + System.exit(runCommand ? 0 : -1); + } + + /** + * Constructor with the location of the configuration service. + * @param configURL URL containing the locations of the configuration service. + * + * @since 1.0 + */ + public DNSManager(URL configURL) + { + ConfigurationServiceProxy proxy = new ConfigurationServiceProxy(configURL.toExternalForm()); + + commands = new Commands("DNS Management Console"); + commands.register(new DNSRecordCommands(proxy)); + commands.register(new CertCommands(proxy)); + + System.out.println("Configuration service URL: " + configURL.toExternalForm()); + + } + + /** + * Either executes commands from the command line or runs the manager interactively. + * @param args Command arguments. If the arguments are empty, then the manager runs interactively. + * @return True if the command was run successfully. False otherwise. + * + * @since 1.0 + */ + public boolean run(String[] args) + { + if (args != null && args.length > 0) + { + return commands.run(args); + } + + commands.runInteractive(); + System.out.println("Shutting Down DNS Manager Console"); + return true; + } + + /** + * Determines if the application should exit when command processing is complete. It may be desirable to set this + * to false if calling from another application context. The default is true. + * @param exit True if the application should terminate on completing processing commands. False otherwise. + */ + public static void setExitOnEndCommands(boolean exit) + { + exitOnEndCommands = exit; + } +} diff --git a/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/tools/DNSRecordCommands.java b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/tools/DNSRecordCommands.java new file mode 100644 index 000000000..a63cfe93c --- /dev/null +++ b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/tools/DNSRecordCommands.java @@ -0,0 +1,690 @@ +/* +Copyright (c) 2010, NHIN Direct Project +All rights reserved. + +Authors: + Greg Meyer gm2552@cerner.com + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the distribution. Neither the name of the The NHIN Direct Project (nhindirect.org). +nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS +BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +THE POSSIBILITY OF SUCH DAMAGE. +*/ + +package org.nhindirect.dns.tools; + +import java.io.File; +import java.rmi.RemoteException; +import java.util.ArrayList; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.commons.io.FileUtils; +import org.bouncycastle.util.Arrays; +import org.nhind.config.ConfigurationServiceProxy; +import org.nhind.config.DnsRecord; +import org.nhindirect.dns.tools.utils.Command; +import org.nhindirect.dns.tools.utils.StringArrayUtil; +import org.xbill.DNS.Record; +import org.xbill.DNS.Section; +import org.xbill.DNS.Type; + +/** + * Command definition and logic for managing DNS records. Commands are case-insensitive. + * @author Greg Meyer + * + * @since 1.0 + */ +public class DNSRecordCommands +{ + private static final String IMPORT_MX_USAGE = "Import a new MX dns record from a binary file." + + "\r\n\tfilepath " + + "\r\n\t filePath: path to the MX record binary file. Can have any (or no extension)"; + + private static final String IMPORT_SOA_USAGE = "Import a new SOA dns record from a binary file." + + "\r\n\tfilepath " + + "\r\n\t filePath: path to the SOA record binary file. Can have any (or no extension)"; + + private static final String IMPORT_ADDRESS_USAGE = "Import a new A dns record from a binary file." + + "\r\n\tfilepath " + + "\r\n\t filePath: path to the A record binary file. Can have any (or no extension)"; + + private static final String ADD_MX_USAGE = "Add a new MX dns record." + + "\r\n" + DNSRecordParser.PARSE_MX_USAGE; + + private static final String ENSURE_MX_USAGE = "Adds a new MX dns record if an identical one does't already exist. " + + "\r\n" + DNSRecordParser.PARSE_MX_USAGE; + + private static final String ADD_SOA_USAGE = "Add a new SOA dns record." + + "\r\n" + DNSRecordParser.PARSE_SOA_USAGE; + + private static final String ENSURE_SOA_USAGE = "Add a new SOA dns record if an identical one does not exist." + + "\r\n" + DNSRecordParser.PARSE_SOA_USAGE; + + private static final String ADD_ANAME_USAGE = "Add a new ANAME dns record." + + "\r\n" + DNSRecordParser.PARSE_ANAME_USAGE; + + private static final String ENSURE_ANAME_USAGE = "Add a new ANAME dns record if an identical one does not exist." + + "\r\n" + DNSRecordParser.PARSE_ANAME_USAGE; + + private static final String REMOVE_MX_USAGE = "Remove an existing MX record by ID." + + "\r\n\trecordid" + + "\r\n\t recordid: record id to be removed from the database"; + + + private static final String REMOVE_SOA_USAGE = "Remove an existing SOA record by ID." + + "\r\n\trecordid" + + "\r\nt\t recordid: record id to be removed from the database"; + + + private static final String REMOVE_ANAME_USAGE = "Remove an existing ANAME record by ID." + + "\r\n\trecordid" + + "\r\n\t recordid: record id to be removed from the database"; + + + private static final String GET_MX_USAGE = "Gets an existing MX record by ID." + + "\r\n\trecordid" + + "\r\n\t recordid: record id to be retrieved from the database"; + + + private static final String GET_SOA_USAGE = "Gets an existing SOA record by ID." + + "\r\n\trecordid" + + "\r\n\t recordid: record id to be retrieved from the database"; + + + private static final String GET_ANAME_USAGE = "Gets an existing ANAME record by ID." + + "\r\n\trecordid"; + + private static final String GET_ALL_USAGE = "Gets all records in the DNS store."; + private DNSRecordPrinter printer; + private DNSRecordParser parser; + private ConfigurationServiceProxy proxy; + + /** + * Constructor that takes a reference to the configuration service proxy. + * @param proxy Configuration service proxy for accessing the configuration service. + * + * @since 1.0 + */ + public DNSRecordCommands(ConfigurationServiceProxy proxy) + { + parser = new DNSRecordParser(); + printer = new DefaultDNSRecordPrinter(); + this.proxy = proxy; + } + + /* + * Convert a dnsjava record to a DnsRecord for use with the proxy. + */ + private DnsRecord fromRecord(Record rec) + { + DnsRecord retVal = new DnsRecord(); + retVal.setData(rec.rdataToWireCanonical()); + retVal.setDclass(rec.getDClass()); + retVal.setName(rec.getName().toString()); + retVal.setTtl(rec.getTTL()); + retVal.setType(rec.getType()); + + return retVal; + } + + /* + * Loads a record from a file. Records are stored in raw wire format. + */ + private DnsRecord loadAndVerifyDnsRecordFromBin(String path) + { + File recFile = new File(path); + if (!recFile.exists()) + throw new IllegalArgumentException("Record file " + recFile.getAbsolutePath() + " not found"); + + Record rec = null; + try + { + byte[] wire = FileUtils.readFileToByteArray(recFile); + + rec = Record.fromWire(wire, Section.ANSWER); + } + catch (Exception e) + { + throw new RuntimeException("Error reading file " + recFile.getAbsolutePath() + " : " + e.getMessage(), e); + } + + return (rec != null) ? fromRecord(rec) : null; + } + + /* + * Adds a DNS record to the configuration service. + */ + private void addDNS(DnsRecord dnsRecord) + { + try + { + proxy.addDNS(new DnsRecord[] {dnsRecord}); + System.out.println("Record added successfully."); + } + catch (RemoteException e) + { + throw new RuntimeException("Error adding DNS record: " + e.getMessage(), e); + } + + } + + /* + * Removed a DNS record from the service + */ + private void removeDNS(long recordId) + { + try + { + proxy.removeDNSByRecordId(recordId); + System.out.println("Record removed successfully."); + } + catch (Exception e) + { + throw new RuntimeException("Error accessing configuration service: " + e.getMessage(), e); + } + } + + /* + * Imports a specific DNS record type from a file. + */ + private void importRecord(String path, int type) + { + DnsRecord dnsRecord = loadAndVerifyDnsRecordFromBin(path); + + if (dnsRecord.getType() != type) + { + throw new IllegalArgumentException("File " + path + " does not contain the requested record type"); + } + + addDNS(dnsRecord); + } + + /** + * Imports an MX record from a file. The file contains the record in raw DNS wire format. + * @param args The first entry in the array contains the file path (required). + * + * @since 1.0 + */ + @Command(name = "Dns_MX_Import", usage = IMPORT_MX_USAGE) + public void mXImport(String[] args) + { + String path = StringArrayUtil.getRequiredValue(args, 0); + importRecord(path, Type.MX); + } + + /** + * Imports an SOA record from a file. The file contains the record in raw DNS wire format. + * @param args The first entry in the array contains the file path (required). + * + * @since 1.0 + */ + @Command(name = "Dns_SOA_Import", usage = IMPORT_SOA_USAGE) + public void sOAImport(String[] args) + { + String path = StringArrayUtil.getRequiredValue(args, 0); + importRecord(path, Type.SOA); + } + + /** + * Imports an A record from a file. The file contains the record in raw DNS wire format. + * @param args The first entry in the array contains the file path (required). + * + * @since 1.0 + */ + @Command(name = "Dns_ANAME_Import", usage = IMPORT_ADDRESS_USAGE) + public void importAddress(String[] args) + { + String path = StringArrayUtil.getRequiredValue(args, 0); + importRecord(path, Type.A); + } + + /** + * Adds an MX records to the configuration service. + * @param args Contains the MX record attributes. + * + * @since 1.0 + */ + @Command(name = "Dns_MX_Add", usage = ADD_MX_USAGE) + public void addMX(String[] args) + { + DnsRecord record = fromRecord(parser.parseMX(args)); + + addDNS(record); + } + + /** + * Adds an MX records to the configuration service only if the record does not exist. + * @param args Contains the MX record attributes. + * + * @since 1.0 + */ + @Command(name = "Dns_MX_Ensure", usage = ENSURE_MX_USAGE) + public void ensureMX(String[] args) + { + DnsRecord record = fromRecord(parser.parseMX(args)); + if (!verifyIsUnique(record, false)) + { + return; + } + + + addDNS(record); + } + + /** + * Adds an SOA records to the configuration service. + * @param args Contains the SOA record attributes. + * + * @since 1.0 + */ + @Command(name = "Dns_SOA_Add", usage = ADD_SOA_USAGE) + public void addSOA(String[] args) + { + DnsRecord record = fromRecord(parser.parseSOA(args)); + + addDNS(record); + } + + /** + * Adds an SOA records to the configuration service only if the record does not exist. + * @param args Contains the SOA record attributes. + * + * @since 1.0 + */ + @Command(name = "Dns_SOA_Ensure", usage = ENSURE_SOA_USAGE) + public void ensureSOA(String[] args) + { + DnsRecord record = fromRecord(parser.parseSOA(args)); + if (!verifyIsUnique(record, false)) + { + return; + } + + addDNS(record); + } + + /** + * Adds an A records to the configuration service. + * @param args Contains the A record attributes. + * + * @since 1.0 + */ + @Command(name = "Dns_ANAME_Add", usage = ADD_ANAME_USAGE) + public void addANAME(String[] args) + { + DnsRecord record = fromRecord(parser.parseANAME(args)); + addDNS(record); + } + + + /** + * Adds an A records to the configuration service only if the record does not exist. + * @param args Contains the A record attributes. + * + * @since 1.0 + */ + @Command(name = "Dns_ANAME_Ensure", usage = ENSURE_ANAME_USAGE) + public void ensureANAME(String[] args) + { + DnsRecord record = fromRecord(parser.parseANAME(args)); + if (!verifyIsUnique(record, false)) + { + return; + } + + addDNS(record); + } + + /** + * Removes an MX record from the configuration service by record id. + * @param args The first entry in the array contains the record id (required). + * + * @since 1.0 + */ + @Command(name = "Dns_MX_Remove", usage = REMOVE_MX_USAGE) + public void removeMX(String[] args) + { + long recordID = Long.parseLong(StringArrayUtil.getRequiredValue(args, 0)); + removeDNS(recordID); + } + + /** + * Removes an SOA record from the configuration service by record id. + * @param args The first entry in the array contains the record id (required). + * + * @since 1.0 + */ + @Command(name = "Dns_SOA_Remove", usage = REMOVE_SOA_USAGE) + public void removeSOA(String[] args) + { + long recordID = Long.parseLong(StringArrayUtil.getRequiredValue(args, 0)); + removeDNS(recordID); + } + + /** + * Removes an A record from the configuration service by record id. + * @param args The first entry in the array contains the record id (required). + * + * @since 1.0 + */ + @Command(name = "Dns_ANAME_Remove", usage = REMOVE_ANAME_USAGE) + public void removeANAME(String[] args) + { + long recordID = Long.parseLong(StringArrayUtil.getRequiredValue(args, 0)); + removeDNS(recordID); + } + + /** + * Looks up an MX record by record id. + * @param args The first entry in the array contains the record id (required). + * + * @since 1.0 + */ + @Command(name = "Dns_MX_Get", usage = GET_MX_USAGE) + public void getMX(String[] args) + { + get(Long.parseLong(StringArrayUtil.getRequiredValue(args, 0))); + } + + /** + * Looks up an SOA record by record id. + * @param args The first entry in the array contains the record id (required). + * + * @since 1.0 + */ + @Command(name = "Dns_SOA_Get", usage = GET_SOA_USAGE) + public void getSOA(String[] args) + { + get(Long.parseLong(StringArrayUtil.getRequiredValue(args, 0))); + } + + /** + * Looks up an A record by record id. + * @param args The first entry in the array contains the record id (required). + * + * @since 1.0 + */ + @Command(name = "Dns_ANAME_Get", usage = GET_ANAME_USAGE) + public void getANAME(String[] args) + { + get(Long.parseLong(StringArrayUtil.getRequiredValue(args, 0))); + } + + /** + * Retrieves and prints all records in the configuration store. + * @param args Empty + * + * @since 1.0 + */ + @Command(name= "Dns_Get_All", usage = GET_ALL_USAGE) + public void getAll(String[] args) + { + DnsRecord[] records = null; + try + { + records = proxy.getDNSByType(Type.ANY); + } + catch (Exception e) + { + throw new RuntimeException("Error accessing configuration service: " + e.getMessage(), e); + } + + if (records == null || records.length == 0) + { + System.out.println("No records found"); + } + else + print(records); + } + + /* + * Gets and prints a record by record is + */ + private void get(long recordID) + { + DnsRecord record = getRecord(recordID); + if (record != null) + printer.print(record); + } + + /** + * Looks up all records for a given domain and any sub domains. + * @param args The first entry in the array contains the domain name (required). + * + * @since 1.0 + */ + @Command(name = "Dns_Match", usage = "Resolve all records for the given domain") + public void match(String[] args) + { + String domain = StringArrayUtil.getRequiredValue(args, 0); + DnsRecord[] records = null; + Pattern pattern = Pattern.compile(domain); + ArrayList matchedRecords = new ArrayList(); + try + { + records = proxy.getDNSByType(Type.ANY); + } + catch (Exception e) + { + throw new RuntimeException("Error accessing configuration service: " + e.getMessage(), e); + } + + if (records == null || records.length == 0) + { + System.out.println("No records found"); + return; + } + else + { + for (DnsRecord record : records) + { + Matcher matcher = pattern.matcher(record.getName()); + if (matcher.find()) + { + matchedRecords.add(record); + } + } + } + + if (matchedRecords.size() == 0) + { + System.out.println("No records found"); + return; + } + + print(matchedRecords.toArray(new DnsRecord[matchedRecords.size()])); + } + + /** + * Looks up SOA records for a given domain. + * @param args The first entry in the array contains the domain name (required). + * + * @since 1.0 + */ + @Command(name = "Dns_SOA_Match", usage = "Resolve SOA records for the given domain") + public void matchSOA(String[] args) + { + match(StringArrayUtil.getRequiredValue(args, 0), Type.SOA); + } + + /** + * Looks up A records for a given host name. + * @param args The first entry in the array contains the domain name (required). + * + * @since 1.0 + */ + @Command(name = "Dns_ANAME_Match", usage = "Resolve Address records for the given domain") + public void matchAName(String[] args) + { + match(StringArrayUtil.getRequiredValue(args, 0), Type.A); + } + + /** + * Looks up MX records for a given domain. + * @param args The first entry in the array contains the domain name (required). + * + * @since 1.0 + */ + @Command(name = "Dns_MX_Match", usage = "Resolve MX records for the given domain") + public void matchMX(String[] args) + { + match(StringArrayUtil.getRequiredValue(args, 0), Type.MX); + } + + /* + * gets records for a domain name and sub domains for a specific type of record + */ + private void match(String domain, int type) + { + DnsRecord[] records = getRecords(domain, type); + if (records != null && records.length > 0) + print(records); + } + + /* + * gets a record by record id + */ + private DnsRecord getRecord(long recordID) + { + DnsRecord dr = null; + try + { + dr = proxy.getDNSByRecordId(recordID); + } + catch (Exception e) + { + throw new RuntimeException("Error accessing configuration service: " + e.getMessage(), e); + } + + if (dr == null) + { + System.out.println("No record found matching id."); + } + + return dr; + } + + /* + * gets records by name and type + */ + private DnsRecord[] getRecords(String domain, int type) + { + if (!domain.endsWith(".")) + domain += "."; + + DnsRecord[] records = null; + try + { + records = proxy.getDNSByNameAndType(domain, type); + } + catch (Exception e) + { + throw new RuntimeException("Error accessing configuration service: " + e.getMessage(), e); + } + + if (records == null || records.length == 0) + { + System.out.println("No records found"); + } + return records; + } + + /* + * ensures that a record is unique in the configuration service + */ + private boolean verifyIsUnique(DnsRecord record, boolean details) + { + DnsRecord existing = find(record); + if (existing != null) + { + System.out.println("Record already exists"); + + print(existing); + + return false; + } + + return true; + } + + /* + * finds a specific record by name and type + */ + private DnsRecord find(DnsRecord record) + { + DnsRecord[] existingRecords = null; + try + { + existingRecords = proxy.getDNSByNameAndType(record.getName(), record.getType()); + } + catch (Exception e) + { + throw new RuntimeException("Error accessing configuration service: " + e.getMessage(), e); + } + + if (existingRecords == null || existingRecords.length == 0) + { + return null; + } + + for (DnsRecord existingRecord : existingRecords) + if (Arrays.areEqual(record.getData(), existingRecord.getData())) + return existingRecord; + + return null; + } + + /* + * prints the contents of an array of records + */ + private void print(DnsRecord[] records) + { + if (records != null) + { + for(DnsRecord record : records) + { + print(record); + System.out.println("\r\n-------------------------------------------"); + } + } + } + + /* + * prints the contents of a specific record + */ + private void print(DnsRecord dnsRecord) + { + System.out.println("RecordID: " + dnsRecord.getId()); + + + printer.print(dnsRecord); + + } + + /** + * Sets the printer that will be used to print record query responses. + * @param printer The printer that will be used to print record query responses. + */ + public void setRecordPrinter(DNSRecordPrinter printer) + { + this.printer = printer; + } + + /** + * Sets the printer that will be used to print record query responses. + * @param printer The printer that will be used to print record query responses. + */ + public void setConfigurationProxy(ConfigurationServiceProxy proxy) + { + this.proxy = proxy; + } +} diff --git a/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/tools/DNSRecordParser.java b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/tools/DNSRecordParser.java new file mode 100644 index 000000000..3ebe9d048 --- /dev/null +++ b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/tools/DNSRecordParser.java @@ -0,0 +1,163 @@ +/* +Copyright (c) 2010, NHIN Direct Project +All rights reserved. + +Authors: + Greg Meyer gm2552@cerner.com + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the distribution. Neither the name of the The NHIN Direct Project (nhindirect.org). +nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS +BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +THE POSSIBILITY OF SUCH DAMAGE. +*/ + +package org.nhindirect.dns.tools; + +import java.net.InetAddress; + +import org.nhindirect.dns.tools.utils.StringArrayUtil; +import org.xbill.DNS.ARecord; +import org.xbill.DNS.DClass; +import org.xbill.DNS.MXRecord; +import org.xbill.DNS.Name; +import org.xbill.DNS.SOARecord; + +/** + * Parses an array of strings into DNS records. + * @author Greg Meyer + * + * @since 1.0 + */ +public class DNSRecordParser +{ + public static final String PARSE_ANAME_USAGE = " hostname ipaddress ttl [notes]" + + "\r\n\t hostname: host name for the record" + + "\r\n\t ipaddress: IP address in dot notation" + + "\r\n\t ttl: time to live in seconds, 32bit int"; + + public static final String PARSE_SOA_USAGE = " domainname primarysourcedomain responsibleemail serialnumber ttl [refresh] [retry] [expire] [minimum] [notes]" + + "\r\n\t domainname: The domain name of the name server that was the primary source for this zone" + + "\r\n\t responsibleemail: Email mailbox of the hostmaster" + + "\r\n\t serialnumber: Version number of the original copy of the zone." + + "\r\n\t ttl: time to live in seconds, 32bit int" + + "\r\n\t [refresh]: Number of seconds before the zone should be refreshed." + + "\r\n\t [retry]: Number of seconds before failed refresh should be retried." + + "\r\n\t [expire]: Number of seconds before records should be expired if not refreshed" + + "\r\n\t [minimum]: Minimum TTL for this zone."; + + public static final String PARSE_MX_USAGE = " domainname exchange ttl [preference] [notes]" + + "\r\n\t domainname: email domain name for the record" + + "\r\n\t exchange: smtp server host name for the domain" + + "\r\n\t ttl: time to live in seconds" + + "\r\n\t [preference]: short value indicating preference of the record"; + + /** + * Default empty constructor + * + * @since 1.0 + */ + public DNSRecordParser() + { + } + + /* + * converts a string to a dnsjava Name + */ + private Name nameFromString(String str) + { + if (!str.endsWith(".")) + str += "."; + + try + { + return Name.fromString(str); + } + catch (Exception e) + { + throw new IllegalArgumentException("Invalid DNS name"); + } + } + + /* + * converts a string to a InetAddress object + */ + private InetAddress inetFromString(String str) + { + try + { + return InetAddress.getByName(str); + } + catch (Exception e) + { + throw new IllegalArgumentException("Invalid ip address"); + } + } + + /** + * Converts A record configuration information to an ARecord + * @param args The A record configuration parameters. + * @return A DNS ARecord. + * + * @since 1.0 + */ + public ARecord parseANAME(String[] args) + { + + String domainName = StringArrayUtil.getRequiredValue(args, 0); + String ipAddress = StringArrayUtil.getRequiredValue(args, 1); + int ttl = Integer.parseInt(StringArrayUtil.getRequiredValue(args, 2)); + + return new ARecord(nameFromString(domainName), DClass.IN, ttl, inetFromString(ipAddress)); + + } + + /** + * Converts SAO record configuration information to an SOARecord + * @param args The SOA record configuration parameters. + * @return A DNS SAORecord. + * + * @since 1.0 + */ + public SOARecord parseSOA(String[] args) + { + String domainName = StringArrayUtil.getRequiredValue(args, 0); + String primarySourceDomain = StringArrayUtil.getRequiredValue(args, 1); + String responsibleEmail = StringArrayUtil.getRequiredValue(args, 2); + int serialNumber = Integer.parseInt(StringArrayUtil.getRequiredValue(args, 3)); + int ttl = Integer.parseInt(StringArrayUtil.getRequiredValue(args, 4)); + + int refresh = Integer.parseInt(StringArrayUtil.getOptionalValue(args, 5, "0")); + int retry = Integer.parseInt(StringArrayUtil.getOptionalValue(args, 6, "0")); + int expire = Integer.parseInt(StringArrayUtil.getOptionalValue(args, 7, "0")); + int minimum = Integer.parseInt(StringArrayUtil.getOptionalValue(args, 8, "0")); + + return new SOARecord(nameFromString(domainName), DClass.IN, ttl, nameFromString(primarySourceDomain), + nameFromString(responsibleEmail), serialNumber, refresh, retry, expire, minimum); + + } + + /** + * Converts MX record configuration information to an MXRecord + * @param args The MX record configuration parameters. + * @return A DNS MXRecord. + * + * @since 1.0 + */ + public MXRecord parseMX(String[] args) + { + String domainName = StringArrayUtil.getRequiredValue(args, 0); + String exchange = StringArrayUtil.getRequiredValue(args, 1); + int ttl = Integer.parseInt(StringArrayUtil.getRequiredValue(args, 2)); + short pref = Short.parseShort(StringArrayUtil.getOptionalValue(args, 3, "0")); + + return new MXRecord(nameFromString(domainName), DClass.IN, ttl, pref, nameFromString(exchange)); + } +} diff --git a/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/tools/DNSRecordPrinter.java b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/tools/DNSRecordPrinter.java new file mode 100644 index 000000000..f04c52941 --- /dev/null +++ b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/tools/DNSRecordPrinter.java @@ -0,0 +1,38 @@ +package org.nhindirect.dns.tools; + +import java.util.Collection; + +import org.nhind.config.DnsRecord; + +/** + * Interface for printing DNS records to an output Stream. + * @author Greg Meyer + * + * @since 1.0 + */ +public interface DNSRecordPrinter +{ + /** + * Prints the contents of a collection of DNS records. + * @param records A collection of DNS records to print. + * + * @since 1.0 + */ + public void print(Collection records); + + /** + * Prints the contents of an array of DNS records. + * @param records An array of DNS records to print. + * + * @since 1.0 + */ + public void print(DnsRecord[] records); + + /** + * Prints the contents of a single DNS records. + * @param record DNS records to print. + * + * @since 1.0 + */ + public void print(DnsRecord record); +} diff --git a/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/tools/DefaultDNSRecordPrinter.java b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/tools/DefaultDNSRecordPrinter.java new file mode 100644 index 000000000..154535aae --- /dev/null +++ b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/tools/DefaultDNSRecordPrinter.java @@ -0,0 +1,267 @@ +/* +Copyright (c) 2010, NHIN Direct Project +All rights reserved. + +Authors: + Greg Meyer gm2552@cerner.com + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the distribution. Neither the name of the The NHIN Direct Project (nhindirect.org). +nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS +BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +THE POSSIBILITY OF SUCH DAMAGE. +*/ + +package org.nhindirect.dns.tools; + +import java.io.PrintWriter; +import java.security.cert.Certificate; +import java.security.cert.X509Certificate; +import java.util.Arrays; +import java.util.Collection; + +import org.nhind.config.DnsRecord; +import org.xbill.DNS.ARecord; +import org.xbill.DNS.CERTRecord; +import org.xbill.DNS.MXRecord; +import org.xbill.DNS.Name; +import org.xbill.DNS.Record; +import org.xbill.DNS.SOARecord; +import org.xbill.DNS.Type; +import org.xbill.DNS.security.CERTConverter; + +/** + * Utility class for formatting and outputting the content of DNS records. + * @author Greg Meyer + * + * @since 1.0 + */ +public class DefaultDNSRecordPrinter implements DNSRecordPrinter +{ + private final PrintWriter writer; + + /** + * Default constructor. Create a writer that outputs to system console. + * + * @since 1.0 + */ + public DefaultDNSRecordPrinter() + { + writer = new PrintWriter(System.out); + } + + /** + * {@inheritDoc} + */ + public void print(Collection records) + { + if (records == null || records.size() == 0) + { + writer.println("Empty record list"); + return; + } + + for (DnsRecord record : records) + { + print(record); + } + } + + /** + * {@inheritDoc} + */ + public void print(DnsRecord[] records) + { + if (records == null || records.length == 0) + { + writer.println("Empty record array"); + return; + } + + print(Arrays.asList(records)); + } + + /* + * Converts a DNS record type to a string representation + */ + private String typeToString(int type) + { + switch (type) + { + case Type.A: + return "A"; + + case Type.MX: + return "MX"; + + case Type.SOA: + return "SOA"; + + case Type.CERT: + return "CERT"; + + default: + return "Unknown"; + } + } + + /** + * {@inheritDoc} + */ + public void print(DnsRecord record) + { + if (record == null) + { + writer.println("Null Resource Record"); + return; + } + + writer.println("-----------"); + print("Record Name", record.getName()); + print("Type", typeToString(record.getType())); + print("TTL", String.valueOf(record.getTtl())); + switch(record.getType()) + { + default: + break; + + case Type.A: + print((ARecord)toRecord(record)); + break; + + case Type.SOA: + print((SOARecord)toRecord(record)); + break; + + case Type.MX: + print((MXRecord)toRecord(record)); + break; + + case Type.CERT: + print((CERTRecord)toRecord(record)); + break; + } + + writer.flush(); + } + + /* + * converts a String to a DNS name + */ + private Name nameFromString(String str) + { + if (!str.endsWith(".")) + str += "."; + + try + { + return Name.fromString(str); + } + catch (Exception e) + { + throw new IllegalArgumentException("Invalid DNS name"); + } + } + + /* + * converts a configuration service DnsRecord to a dnsjava Record + */ + private Record toRecord(DnsRecord rec) + { + return Record.newRecord(nameFromString(rec.getName()), rec.getType(), rec.getDclass(), rec.getTtl(), rec.getData()); + } + + /* + * prints the A record specific fields + */ + private void print(ARecord body) + { + if (body == null) + { + print("Null A Record Body"); + return; + } + + this.print("IPAddress", body.getAddress().getHostAddress()); + } + + /* + * prints the MX record specific fields + */ + private void print(MXRecord body) + { + if (body == null) + { + print("Null MX Record Body"); + return; + } + + print("Access Exchage Server", body.getTarget().toString()); + print("Priority", String.valueOf(body.getPriority())); + } + + /* + * prints the SOA record specific fields + */ + private void print(SOARecord soa) + { + if (soa == null) + { + print("Null SOA Record Body"); + return; + } + + + print("DomainName", soa.getName().toString()); + print("Primary Name Server", soa.getHost().toString()); + print("Refresh", String.valueOf(soa.getRefresh())); + print("Retry", String.valueOf(soa.getRetry())); + print("Expire", String.valueOf(soa.getExpire())); + print("Minimum", String.valueOf(soa.getMinimum())); + } + + /* + * prints the CERT record specific fields + */ + private void print(CERTRecord certbody) + { + if (certbody == null) + { + print("Null CERT Record Body"); + return; + } + + + Certificate cert = CERTConverter.parseRecord(certbody); + if (cert instanceof X509Certificate) // may not be an X509Cert + { + X509Certificate xcert = (X509Certificate)cert; + print("Certificate Subject", xcert.getSubjectDN().getName()); + } + } + + + /* + * prints a name value pair + */ + private void print(String name, String value) + { + writer.println(name + ": " + value); + } + + /* + * prints a specific string message + */ + private void print(String message) + { + writer.println(message); + } + +} diff --git a/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/tools/package-info.java b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/tools/package-info.java new file mode 100644 index 000000000..71c2fb79b --- /dev/null +++ b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/tools/package-info.java @@ -0,0 +1,4 @@ +/** + * DNS management tools and command sets. + */ +package org.nhindirect.dns.tools; \ No newline at end of file diff --git a/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/tools/printers/AbstractRecordPrinter.java b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/tools/printers/AbstractRecordPrinter.java new file mode 100644 index 000000000..631140eba --- /dev/null +++ b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/tools/printers/AbstractRecordPrinter.java @@ -0,0 +1,139 @@ +package org.nhindirect.dns.tools.printers; + +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.Collection; + + +public abstract class AbstractRecordPrinter implements RecordPrinter +{ + protected final Collection reportColumns; + protected final int tableWidth; + + protected static class ReportColumn + { + protected final String header; + protected final int width; + protected final String fieldName; + + public ReportColumn(String header, int width, String fieldName) + { + this.header = header; + this.width = width; + this.fieldName = fieldName; + } + } + + public AbstractRecordPrinter(int tableWidth, Collection reportColumns) + { + this.tableWidth = tableWidth; + this.reportColumns = reportColumns; + } + + @Override + @SuppressWarnings("unchecked") + public void printRecord(T record) + { + printRecords(Arrays.asList(record)); + } + + @Override + public void printRecords(Collection records) + { + printHeader(); + + for (T record : records) + printRecordInternal(record); + } + + protected void printRecordInternal(T record) + { + StringBuilder builder = new StringBuilder(); + + int cnt = 0; + for (ReportColumn column : reportColumns) + { + + + builder.append(" "); + String colValue = getColumnValue(column, record); + builder.append(colValue); + // pad the rest with spaces + int padSize = (column.width - 2 ) - colValue.length(); + for (int i = 0; i < padSize; ++i) + builder.append(' '); + + if (++cnt < reportColumns.size()) + builder.append("|"); + } + + builder.append("\r\n"); + for (int i = 0; i < tableWidth; ++i) + builder.append('-'); + + System.out.println(builder.toString()); + } + + protected String getColumnValue(ReportColumn column, T record) + { + // default is to get the field value by introspection using + // the field name + try + { + Method method = record.getClass().getDeclaredMethod("get" + column.fieldName); + Object obj = method.invoke(record); + return obj.toString(); + } + catch (Exception e) + { + return "ERROR: " + e.getMessage(); + } + } + + protected void printHeader() + { + StringBuilder builder = new StringBuilder(); + + // top of header + for (int i = 0; i < tableWidth; ++i) + builder.append('-'); + + builder.append("\r\n|"); + + int cnt = 0; + int widthUsed = 0; + for (ReportColumn column : reportColumns) + { + int currentWidth = 0; + if (++cnt >= reportColumns.size()) + currentWidth = tableWidth - widthUsed; + else + currentWidth = column.width; + + // center the header + int padding = (currentWidth - column.header.length()) / 2; + + // add pre padding + for (int i = 0; i < padding; ++i) + builder.append(' '); + + // print header + builder.append(column.header); + + // add post padding + for (int i = 0; i < (padding -1); ++i) + builder.append(' '); + + builder.append("|"); + + widthUsed += currentWidth; + } + + // end of header + builder.append("\r\n"); + for (int i = 0; i < tableWidth; ++i) + builder.append('-'); + + System.out.println(builder.toString()); + } +} \ No newline at end of file diff --git a/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/tools/printers/CertRecordPrinter.java b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/tools/printers/CertRecordPrinter.java new file mode 100644 index 000000000..719b36ddb --- /dev/null +++ b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/tools/printers/CertRecordPrinter.java @@ -0,0 +1,106 @@ +package org.nhindirect.dns.tools.printers; + + +import java.net.URL; + +import java.security.cert.X509Certificate; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Locale; + +import org.nhindirect.dns.DNSException; +import org.nhindirect.dns.utils.CertUtils; +import org.nhindirect.stagent.cert.Thumbprint; + +public class CertRecordPrinter extends AbstractRecordPrinter +{ + + protected static final SimpleDateFormat dateFormatter; + + protected static final String CERT_NAME_COL = "Subject Name/URL"; + protected static final String RECORD_TYPE_COL = "Record Type"; + protected static final String PRIVATE_IND_COL = "Private Key"; + protected static final String TP_NAME_COL = "Thumbprint"; + protected static final String EXPIRES_COL = "Expires"; + + protected static final Collection REPORT_COLS; + + static + { + REPORT_COLS = new ArrayList(); + + REPORT_COLS.add(new ReportColumn(CERT_NAME_COL, 55, "getCertificate")); + REPORT_COLS.add(new ReportColumn(RECORD_TYPE_COL, 11, "getCertificate")); + REPORT_COLS.add(new ReportColumn(PRIVATE_IND_COL, 12, "getCertificate")); + REPORT_COLS.add(new ReportColumn(TP_NAME_COL, 55, "getCertificate")); + REPORT_COLS.add(new ReportColumn(EXPIRES_COL, 15, "getCertificate")); + + + dateFormatter = new SimpleDateFormat("MMM d yyyy" , Locale.getDefault()); + } + + + public CertRecordPrinter() + { + super(150, REPORT_COLS); + } + + @SuppressWarnings("unused") + @Override + protected String getColumnValue(ReportColumn column, org.nhind.config.Certificate retCert) + { + String tpOrURL = null; + boolean isURL = false; + + X509Certificate cert = null; + + try + { + cert = CertUtils.toX509Certificate(retCert.getData()); + tpOrURL = Thumbprint.toThumbprint(cert).toString(); + } + catch (DNSException e) + { + // probably not an X509 CERT... might be a URL + } + + if (tpOrURL == null) + { + try + { + tpOrURL = new String(retCert.getData()); + URL url = new URL(tpOrURL); + isURL = true; + } + catch (Exception e) + { + // invalid URL + return ""; + } + } + + + + try + { + if (column.header.equals(CERT_NAME_COL)) + return retCert.getOwner(); + else if (column.header.equals(RECORD_TYPE_COL)) + return (isURL) ? "IPKIX" : "PKIX"; + else if (column.header.equals(TP_NAME_COL)) + return isURL ? tpOrURL : Thumbprint.toThumbprint(cert).toString(); + else if (column.header.equals(EXPIRES_COL)) + return isURL ? "" : dateFormatter.format(cert.getNotAfter()); + else if (column.header.equals(PRIVATE_IND_COL)) + return retCert.isPrivateKey() ? "Y" : "N"; + else + return super.getColumnValue(column, retCert); + } + catch (Exception e) + { + return "ERROR: " + e.getMessage(); + } + } + +} diff --git a/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/tools/printers/RecordPrinter.java b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/tools/printers/RecordPrinter.java new file mode 100644 index 000000000..f4bed70c3 --- /dev/null +++ b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/tools/printers/RecordPrinter.java @@ -0,0 +1,10 @@ +package org.nhindirect.dns.tools.printers; + +import java.util.Collection; + +public interface RecordPrinter +{ + public void printRecord(T rec); + + public void printRecords(Collection recs); +} diff --git a/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/tools/utils/Action.java b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/tools/utils/Action.java new file mode 100644 index 000000000..b08d57d55 --- /dev/null +++ b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/tools/utils/Action.java @@ -0,0 +1,40 @@ +/* +Copyright (c) 2010, NHIN Direct Project +All rights reserved. + +Authors: + Greg Meyer gm2552@cerner.com + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the distribution. Neither the name of the The NHIN Direct Project (nhindirect.org). +nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS +BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +package org.nhindirect.dns.tools.utils; + +/** + * Command interface for DNS manager. + * @author Greg Meyer + * + * @param Parameters that are sent to the command actions. + * + * @since 1.0 + */ +public interface Action +{ + /** + * Executes a command action. + * @param param Parameters passed to the action. + */ + public void doAction(T param); +} diff --git a/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/tools/utils/Command.java b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/tools/utils/Command.java new file mode 100644 index 000000000..e54b37ec0 --- /dev/null +++ b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/tools/utils/Command.java @@ -0,0 +1,51 @@ +/* +Copyright (c) 2010, NHIN Direct Project +All rights reserved. + +Authors: + Greg Meyer gm2552@cerner.com + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the distribution. Neither the name of the The NHIN Direct Project (nhindirect.org). +nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS +BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +package org.nhindirect.dns.tools.utils; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation that marks a class method as a executable command in the DNS configuration tool. + * @author Greg Meyer + * + * @since 1.0 + */ +@Target( {ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface Command +{ + /** + * The name of the command. The is the actual command name used in the tool. + * @return The name of the command. + */ + String name(); + + /** + * Optional text that describes how the command is used. + * @return + */ + String usage(); +} diff --git a/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/tools/utils/CommandDef.java b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/tools/utils/CommandDef.java new file mode 100644 index 000000000..575c7acda --- /dev/null +++ b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/tools/utils/CommandDef.java @@ -0,0 +1,89 @@ +/* +Copyright (c) 2010, NHIN Direct Project +All rights reserved. + +Authors: + Greg Meyer gm2552@cerner.com + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the distribution. Neither the name of the The NHIN Direct Project (nhindirect.org). +nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS +BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +package org.nhindirect.dns.tools.utils; + +import java.util.Locale; + +/** + * Defines the attributes of a command. + * @author Greg Meyer + * + */ +class CommandDef +{ + private String name; + private Action eval; + private CommandUsage usage; + + public String getName() + { + return name; + } + + public void setName(String name) + { + this.name = name; + } + + public Action getEval() + { + return eval; + } + + public void setEval(Action eval) + { + this.eval = eval; + } + + public CommandUsage getUsage() + { + return usage; + } + + public void setUsage(CommandUsage usage) + { + this.usage = usage; + } + + + boolean hasUsage() + { + return (usage != null); + } + + void showUsage() + { + System.out.println(getName().toUpperCase(Locale.getDefault())); + if (usage != null) + { + System.out.println(usage.getUsage()); + } + + System.out.println(); + } + + void showCommand() + { + System.out.println(getName().toUpperCase(Locale.getDefault())); + } +} diff --git a/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/tools/utils/CommandUsage.java b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/tools/utils/CommandUsage.java new file mode 100644 index 000000000..475522c7f --- /dev/null +++ b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/tools/utils/CommandUsage.java @@ -0,0 +1,34 @@ +/* +Copyright (c) 2010, NHIN Direct Project +All rights reserved. + +Authors: + Greg Meyer gm2552@cerner.com + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the distribution. Neither the name of the The NHIN Direct Project (nhindirect.org). +nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS +BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +package org.nhindirect.dns.tools.utils; + +/** + * Interface used for getting the text used to decribe a command. + * @author Greg Meyer + * + * @since 1.0 + */ +public interface CommandUsage +{ + public String getUsage(); +} diff --git a/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/tools/utils/Commands.java b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/tools/utils/Commands.java new file mode 100644 index 000000000..089972d7e --- /dev/null +++ b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/tools/utils/Commands.java @@ -0,0 +1,558 @@ +/* +Copyright (c) 2010, NHIN Direct Project +All rights reserved. + +Authors: + Greg Meyer gm2552@cerner.com + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the distribution. Neither the name of the The NHIN Direct Project (nhindirect.org). +nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS +BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +THE POSSIBILITY OF SUCH DAMAGE. +*/ + +package org.nhindirect.dns.tools.utils; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Hashtable; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.commons.io.FileUtils; + +/** + * Main command and control class for the DNS configuration manager tool. Command classes are registered to this class, and + * commands marked with the {@link Command} annotation are mapped as runnable commands into the system. + * @author Greg Meyer + * + * @since 1.0 + */ +public class Commands +{ + static final String[] EmptyArgs = new String[0]; + + private static final String SEARCH_USAGE = "Search for commands matching the given wildcard pattern" + + "\r\n\tpattern" + + "\r\n\tpattern: (optional) pattern, containing '*' wildcards"; + + private static final String HELP_USAGE = "Show help" + + "\r\nhelp ['all' | name]" + + "\r\n\tall: All commands" + + "\r\n\tname: This command name or names with this PREFIX" + + "\r\nsearch [pattern]" + + "\r\n" + + SEARCH_USAGE; + + private static final String COMMANDS_USAGE = "List the commands available" + + "\r\ncommands [nameprefix]"; + + private static final String EXIT_USAGE = "Exit the application"; + + private static final String BATCH_USAGE = "Run a series of commands from a file" + + "\r\nEach command is on its own line. Comments begin with //" + + "\r\nfilepath [echo command (default true)]"; + + + private final String appName; + private final List instances; + private final Map commands; + private final Map, Object> typeLookup; + private String[] commandNames; + private boolean running; + + + public Commands(String appName) + { + if (appName == null || appName.isEmpty()) + { + throw new IllegalArgumentException("appName value null or empty"); + } + + this.appName = appName; + instances = new ArrayList(); + commands = new Hashtable(); + typeLookup = new Hashtable, Object>(); + + register(this); + } + + public CommandDef getCommand(String name) + { + return commands.get(name.toUpperCase(Locale.getDefault())); + } + + + public Object getCommand(Class clazz) + { + Object cmd = typeLookup.get(clazz); + if (cmd == null) + { + throw new IllegalStateException("Command of type " + clazz.getName() + " was not found."); + } + return cmd; + } + + public Collection getCommandNames() + { + ensureCommandNamesArray(); + return Arrays.asList(commandNames); + } + + /* + public event Action Error; + */ + + + public void register(Object instance) + { + if (instance == null) + { + throw new IllegalArgumentException("instance"); + } + + Class type = instance.getClass(); + Method[] methods = type.getMethods(); + + Collection commandMethods; + if (methods != null && methods.length > 0) + commandMethods = Arrays.asList(methods); + else + commandMethods = Collections.emptyList(); + + instances.add(instance); + typeLookup.put(instance.getClass(), instance); + + discoverCommandMethods(commandMethods, instance); + } + + private void discoverCommandMethods(Collection methods, Object instance) + { + for (Method method : methods) + { + discoverCommandMethod(method, instance); + } + } + + private void discoverCommandMethod(Method method, Object instance) + { + + Command cmd = method.getAnnotation(Command.class); + if (cmd != null) + { + if (cmd.name() != null && !cmd.name().isEmpty()) + { + final Object inst = instance; + final Method meth = method; + Action action = new Action() + { + public void doAction(String[] params) + { + try + { + meth.invoke(inst, new Object[]{params}); + } + catch (Throwable e) + { + Throwable error = e.getCause(); + if (error != null) + System.err.println("\r\n" + error.getMessage()); + + } + } + }; + + setEval(cmd.name(), action); + + + final String usageStr = cmd.usage(); + if (usageStr != null && !usageStr.isEmpty()) + { + CommandUsage usage = new CommandUsage() + { + public String getUsage() + { + return usageStr; + } + }; + setUsage(cmd.name(), usage); + } + } + } + } + + + public void runInteractive() + { + running = true; + System.out.println(appName); + + String input; + InputStreamReader in = new InputStreamReader(System.in); + BufferedReader reader = new BufferedReader(in); + System.out.print(">"); + try + { + while (running && (input = reader.readLine()) != null) + { + if (!input.isEmpty()) + { + run(input); + } + if (running) + System.out.print("\r\n>"); + } + } + catch (IOException e) + { + e.printStackTrace(); + } + } + + public boolean run(String commandLine) + { + if (commandLine != null && !commandLine.isEmpty()) + { + String[] args = StringArrayUtil.parseAsCommandLine(commandLine); + if (args != null && args.length > 0) + { + return run(args); + } + } + + return false; + } + + public boolean run(String[] args) + { + try + { + eval(args); + return true; + } + catch (Exception ex) + { + handleError(ex); + } + + return false; + } + + public void eval(String[] input) throws Exception + { + if (input == null || input.length == 0) + { + return; + } + + String commandName = input[0]; + CommandDef cmd = getCommand(commandName); + if (cmd == null) + { + System.out.println(commandName.toUpperCase(Locale.getDefault()) + " not found."); + System.out.println(); + System.out.println(HELP_USAGE); + return; + } + + try + { + String[] commandParams = (input.length > 1) ? (String[])Arrays.copyOfRange(input, 1, input.length) : EmptyArgs; + cmd.getEval().doAction(commandParams); + } + catch(Exception ex) + { + handleError(ex); + System.out.println(); + cmd.showUsage(); + throw ex; + } + } + + public void showUsage(String cmdName) + { + if (cmdName == null || cmdName.isEmpty()) + { + showAllUsage(); + } + else + { + this.bind(cmdName).showUsage(); + } + } + + private void showAllUsage() + { + System.out.println("Registered commands"); + + ensureCommandNamesArray(); + + for(String name : commandNames) + { + showUsage(name); + } + } + + public Collection prefixMatchCommandNames(String prefix) + { + ensureCommandNamesArray(); + Collection retVal = new ArrayList(); + for (String cmd : commandNames) + if (cmd.toUpperCase(Locale.getDefault()).startsWith(prefix.toUpperCase(Locale.getDefault()))) + retVal.add(cmd); + + return retVal; + } + + public Collection matchCommandNames(String pat) + { + ensureCommandNamesArray(); + Pattern pattern = Pattern.compile(pat); + + Collection retVal = new ArrayList(); + for (String cmd : commandNames) + { + Matcher matcher = pattern.matcher(cmd); + if (matcher.find()) + { + retVal.add(cmd); + } + + } + + return retVal; + } + + /* + static void Exit(int code) + { + Environment.Exit(code); + } + */ + + + CommandDef bind(String name) + { + CommandDef cmd = getCommand(name); + if (cmd == null) + { + throw new IllegalArgumentException("Command " + name + " not found. Type help for usage."); + } + return cmd; + } + + public void ensureCommandNamesArray() + { + if (commandNames != null && commandNames.length > 0) + { + return; + } + + Collection names = new ArrayList(); + if (commands != null) + { + for (CommandDef cmd : commands.values()) + names.add(cmd.getName()); + + commandNames = names.toArray(new String[names.size()]); + } + } + + void setEval(String name, Action eval) + { + this.ensure(name).setEval(eval); + } + + void setUsage(String name, CommandUsage usage) + { + this.ensure(name).setUsage(usage); + } + + private CommandDef ensure(String name) + { + CommandDef cmd = getCommand(name); + if (cmd == null) + { + cmd = new CommandDef(); + cmd.setName(name); + commands.put(name.toUpperCase(Locale.getDefault()), cmd); + } + + return cmd; + } + + void handleError(Exception ex) + { + /* + if (this.Error != null) + { + this.Error(ex); + } + else + { + CommandUI.Print(ex); + } + */ + } + + /* + * Built in Standard Commands + */ + @Command(name = "Quit", usage = EXIT_USAGE) + public void quit(String[] args) + { + running = false; + } + + @Command(name = "Exit", usage = EXIT_USAGE) + public void exit(String[] args) + { + running = false; + } + + @Command(name = "Commands", usage = COMMANDS_USAGE) + public void listCommands(String[] args) + { + ensureCommandNamesArray(); + String prefix = (args != null && args.length > 0) ? args[0] : null; + + Collection names; + if (prefix == null || prefix.isEmpty()) + { + names = Arrays.asList(commandNames); + } + else + { + names = prefixMatchCommandNames(prefix); + } + + for (String name : names) + { + bind(name).showCommand(); + } + } + + + @Command(name = "Help", usage = HELP_USAGE) + public void help(String[] args) + { + String cmdName = null; + if (args != null && args.length > 0) + { + cmdName = args[0]; + } + + if (cmdName == null || cmdName.isEmpty()) + { + System.out.println(HELP_USAGE); + return; + } + + if (cmdName.equalsIgnoreCase("all")) + { + showAllUsage(); + return; + } + + CommandDef cmd = getCommand(cmdName); + if (cmd != null) + { + cmd.showUsage(); + return; + } + + + for (String name : prefixMatchCommandNames(cmdName)) + { + bind(name).showUsage(); + } + } + + + @Command(name = "Search", usage = SEARCH_USAGE) + public void search(String[] args) + { + String pattern = (args != null && args.length > 0) ? args[0] : null; + if (pattern == null || pattern.isEmpty()) + { + showAllUsage(); + return; + } + + pattern = pattern.replace("*", ".*"); + for (String name : matchCommandNames(pattern)) + { + bind(name).showUsage(); + } + } + + @SuppressWarnings("unchecked") + @Command(name = "Batch", usage = BATCH_USAGE) + public void batch(String[] args) + { + if (args == null || args.length ==0) + throw new IllegalArgumentException("Missing argument at position 0"); + + File file = new File(args[0]); + + boolean echo = (args != null && args.length > 1) ? Boolean.parseBoolean(args[1]) : true; + if (!file.exists()) + { + throw new IllegalArgumentException("File " + file.getAbsolutePath() + " not found."); + } + + try + { + List lines = FileUtils.readLines(file); + for (String line : lines) + { + line = line.trim(); + if ((line != null && !line.isEmpty()) && !line.startsWith("//")) + { + if (echo && !line.toUpperCase(Locale.getDefault()).startsWith("ECHO")) + { + System.out.println(line); + } + run(line); + } + } + } + catch (IOException e) + { + throw new IllegalStateException("Error reading file " + file.getAbsolutePath()); + } + } + + + @Command(name = "Echo", usage = "Echo the args to the console") + public void echo(String[] args) + { + if (args == null || args.length == 0) + { + return; + } + + for (String arg : args) + { + System.out.println(arg); + } + } +} diff --git a/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/tools/utils/StringArrayUtil.java b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/tools/utils/StringArrayUtil.java new file mode 100644 index 000000000..d1157d224 --- /dev/null +++ b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/tools/utils/StringArrayUtil.java @@ -0,0 +1,159 @@ +/* +Copyright (c) 2010, NHIN Direct Project +All rights reserved. + +Authors: + Greg Meyer gm2552@cerner.com + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the distribution. Neither the name of the The NHIN Direct Project (nhindirect.org). +nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS +BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +package org.nhindirect.dns.tools.utils; + +import java.util.ArrayList; +import java.util.Arrays; + +/** + * Utility class for parsing and working with string array paremeters. + * @author Greg Meyer + * + * @since 1.0 + */ +public class StringArrayUtil +{ + private static final char[] Whitespace = { ' ', '\t', '\r', '\n' }; + private static final char[] Quotes = { '"' }; + + static + { + Arrays.sort(Whitespace); + } + + public static String getValueOrNull(String[] args, int indexAt) + { + if (indexAt < 0 || indexAt >= args.length) + { + return null; + } + + return args[indexAt]; + } + + public static boolean isNullOrEmpty(String[] args) + { + return (args == null || args.length == 0); + } + + public static boolean isNullOrEmpty(String str) + { + return (str == null || str.isEmpty()); + } + + public static String getRequiredValue(String[] args, int index) + { + String value = getValueOrNull(args, index); + if (isNullOrEmpty(value)) + { + throw new IllegalArgumentException("Missing argument at position " + index); + } + + return value; + } + + public static String getOptionalValue(String[] args, int index, String defaultValue) + { + String value = getValueOrNull(args,index); + if (isNullOrEmpty(value)) + { + return defaultValue; + } + + return value; + } + + static int skipOver(String source, int startAt, char[] chars) + { + for (int i = startAt; i < source.length(); ++i) + { + if (Arrays.binarySearch(chars, source.charAt(i)) < 0) + { + return i; + } + } + + return -1; + } + + static String[] parseAsCommandLine(String input) + { + if (isNullOrEmpty(input)) + return new String[0]; + + ArrayList retVal = new ArrayList(); + + int index = 0; + while (index < input.length()) + { + int tokenStartAt = skipOver(input, index, Whitespace); + if (tokenStartAt < 0) + { + break; + } + + char[] delimiter; + if (Arrays.binarySearch(Quotes, input.charAt(tokenStartAt)) >= 0) + { + tokenStartAt++; + delimiter = Quotes; + } + else + { + delimiter = Whitespace; + } + + int tokenEndAt = skipTo(input, tokenStartAt, delimiter); + if (tokenEndAt < 0) + { + tokenEndAt = input.length(); + } + + int length = tokenEndAt - tokenStartAt; + if (length > 0) + { + retVal.add(input.substring(tokenStartAt, tokenEndAt)); + } + + index = tokenEndAt + 1; + } + + return retVal.toArray(new String[retVal.size()]); + } + + static int skipTo(String source, int startAt, char[] chars) + { + int firstIndex = 0x8FFF; + int foundIndex = -1; + + for (char ch : chars) + { + if ((foundIndex = source.indexOf(ch, startAt)) > -1) + if (foundIndex < firstIndex) + firstIndex = foundIndex; + } + + return (firstIndex < 0x8FFF) ? firstIndex : -1; + + } +} diff --git a/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/utils/CertUtils.java b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/utils/CertUtils.java new file mode 100644 index 000000000..f8f57a45d --- /dev/null +++ b/java/tags/dns-2.0.1/src/main/java/org/nhindirect/dns/utils/CertUtils.java @@ -0,0 +1,256 @@ +package org.nhindirect.dns.utils; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.security.Key; +import java.security.KeyStore; +import java.security.PrivateKey; +import java.security.Security; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.util.Enumeration; + +import org.apache.commons.io.FileUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.nhindirect.dns.DNSException; +import org.nhindirect.stagent.CryptoExtensions; +import org.nhindirect.stagent.cert.X509CertificateEx; + + +public class CertUtils +{ + private static final Log LOGGER = LogFactory.getFactory().getInstance(CertUtils.class); + static + { + Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider()); + } + + /** + * Takes a PKCS12 byte stream and returns a PKCS12 byte stream with the pass phrase protection and encryption removed. + * @param bytes The PKCS12 byte stream that will be stripped. + * @param passphrase The pass phrase of the PKCS12 byte stream. This is used to decrypt the PKCS12 stream. + * @return A PKCS12 byte stream representation of the original PKCS12 stream with the pass phrase protection and encryption removed. + */ + public static byte[] pkcs12ToStrippedPkcs12(byte[] bytes, String passphrase) throws DNSException + { + if (bytes == null || bytes.length == 0) + throw new IllegalArgumentException("Pkcs byte stream cannot be null or empty."); + + if (passphrase == null) + throw new IllegalArgumentException("Passphrase cannot be null."); + + + byte[] retVal = null; + final ByteArrayInputStream bais = new ByteArrayInputStream(bytes); + final ByteArrayOutputStream outStr = new ByteArrayOutputStream(); + // lets try this a as a PKCS12 data stream first + try + { + final KeyStore localKeyStore = KeyStore.getInstance("PKCS12", CryptoExtensions.getJCEProviderName()); + + localKeyStore.load(bais, passphrase.toCharArray()); + final Enumeration aliases = localKeyStore.aliases(); + + + + // we are really expecting only one alias + if (aliases.hasMoreElements()) + { + final String alias = aliases.nextElement(); + X509Certificate cert = (X509Certificate)localKeyStore.getCertificate(alias); + + // check if there is private key + final Key key = localKeyStore.getKey(alias, "".toCharArray()); + if (key != null && key instanceof PrivateKey) + { + // now convert to a pcks12 format without the passphrase + final char[] emptyPass = "".toCharArray(); + + localKeyStore.setKeyEntry("privCert", key, emptyPass, new java.security.cert.Certificate[] {cert}); + + localKeyStore.store(outStr, emptyPass); + + retVal = outStr.toByteArray(); + + } + } + } + catch (Exception e) + { + throw new DNSException("Failed to strip encryption for PKCS stream."); + } + finally + { + try {bais.close(); } + catch (Exception e) {/* no-op */} + + try {outStr.close(); } + catch (Exception e) {/* no-op */} + } + + return retVal; + } + + /** + * Converts an X509Certificate to a byte stream representation. If the certificate contains a private key, the returned representation + * is a PKCS12 byte stream with no pass phrase protection or encryption. + * @param cert The certificate to convert. + * @return A byte stream representation of the certificate. + */ + public static byte[] x509CertificateToBytes(X509Certificate cert) throws DNSException + { + if (cert instanceof X509CertificateEx) + { + final ByteArrayOutputStream outStr = new ByteArrayOutputStream(); + try + { + // return as a pkcs12 file with no encryption + final KeyStore convertKeyStore = KeyStore.getInstance("PKCS12", CryptoExtensions.getJCEProviderName()); + convertKeyStore.load(null, null); + final char[] emptyPass = "".toCharArray(); + + convertKeyStore.setKeyEntry("privCert", ((X509CertificateEx) cert).getPrivateKey(), emptyPass, new java.security.cert.Certificate[] {cert}); + convertKeyStore.store(outStr, emptyPass); + + return outStr.toByteArray(); + } + ///CLOVER:OFF + catch (Exception e) + { + throw new DNSException("Failed to convert certificate to a byte stream."); + } + ///CLOVER:ON + finally + { + try {outStr.close(); } + catch (Exception e) {/* no-op */} + } + } + else + { + try + { + return cert.getEncoded(); + } + ///CLOVER:OFF + catch (Exception e) + { + throw new DNSException("Failed to convert certificate to a byte stream."); + } + ///CLOVER:ON + } + } + + /** + * Converts a byte stream to an X509Certificate. The byte stream can either be an encoded X509Certificate or a PKCS12 byte stream. + *

+ * If the stream is a PKCS12 representation, then an empty ("") pass phrase is used to decrypt the stream. In addition the resulting X509Certificate + * implementation will contain the private key. + * @param data The byte stream representation to convert. + * @return An X509Certificate representation of the byte stream. + */ + public static X509Certificate toX509Certificate(byte[] data) throws DNSException + { + return toX509Certificate(data, ""); + } + + /** + * Converts a byte stream to an X509Certificate. The byte stream can either be an encoded X509Certificate or a PKCS12 byte stream. + *

+ * If the stream is a PKCS12 representation, then the pass phrase is used to decrypt the stream. In addition the resulting X509Certificate + * implementation will contain the private key. + * @param data The byte stream representation to convert. + * @param passPhrase If the byte stream is a PKCS12 representation, then the then the pass phrase is used to decrypt the stream. Can be + * null if the stream is an encoded X509Certificate and not a PKCS12 byte stream. + * @return An X509Certificate representation of the byte stream. + */ + public static X509Certificate toX509Certificate(byte[] data, String passPhrase) throws DNSException + { + if (data == null || data.length == 0) + throw new IllegalArgumentException("Byte stream cannot be null or empty."); + + // do not use a null pass phrase + if (passPhrase == null) + passPhrase = ""; + + X509Certificate retVal = null; + ByteArrayInputStream bais = new ByteArrayInputStream(data); + try + { + + // lets try this a as a PKCS12 data stream first + try + { + KeyStore localKeyStore = KeyStore.getInstance("PKCS12", CryptoExtensions.getJCEProviderName()); + + localKeyStore.load(bais, passPhrase.toCharArray()); + Enumeration aliases = localKeyStore.aliases(); + + + // we are really expecting only one alias + if (aliases.hasMoreElements()) + { + String alias = aliases.nextElement(); + X509Certificate cert = (X509Certificate)localKeyStore.getCertificate(alias); + + // check if there is private key + Key key = localKeyStore.getKey(alias, passPhrase.toCharArray()); + if (key != null && key instanceof PrivateKey) + { + retVal = X509CertificateEx.fromX509Certificate(cert, (PrivateKey)key); + } + } + } + catch (Exception e) + { + // must not be a PKCS12 stream, try next step + } + + if (retVal == null) + { + //try X509 certificate factory next + bais.reset(); + bais = new ByteArrayInputStream(data); + + retVal = (X509Certificate) CertificateFactory.getInstance("X.509").generateCertificate(bais); + } + } + catch (Exception e) + { + throw new DNSException("Failed to convert byte stream to a certificate."); + } + finally + { + try {bais.close();} catch (IOException ex) {} + } + + return retVal; + } + + /** + * Creates an X509Certificate object from an existing file. The file should be a DER encoded representation of the certificate. + * @param certFile The file to load into a certificate object. + * @return An X509Certificate loaded from the file. + */ + public static X509Certificate certFromFile(String certFile) + { + final File theCertFile = new File(certFile); + try + { + LOGGER.trace("Full path of cert file to load: " + theCertFile.getAbsolutePath()); + + return toX509Certificate(FileUtils.readFileToByteArray(theCertFile)); + } + catch (Exception e) + { + // this is used as a factory method, so just return null if the certificate could not be loaded + // instead of throwing an exception, but make sure the error is logged + LOGGER.error("Failed to load certificate from file " + theCertFile.getAbsolutePath(), e); + return null; + } + } +} + diff --git a/java/tags/dns-2.0.1/src/site/apt/releaseNotes.apt b/java/tags/dns-2.0.1/src/site/apt/releaseNotes.apt new file mode 100644 index 000000000..70ccd9991 --- /dev/null +++ b/java/tags/dns-2.0.1/src/site/apt/releaseNotes.apt @@ -0,0 +1,211 @@ +~~ $Id$ + + --- + Release Notes + --- + Greg Meyer + --- + +{2.0.1} + + Changes included with release 2.0.1 + + [] + + Enhancements + + * N/A + + [] + + Bug Fixes + + * Switching log statements to out text displayable version of the DNS query type name (instead of the RFC int value). + +{2.0} + + Changes included with release 2.0 + + [] + + Enhancements + + * Updated to support using REST API for config service. + + [] + + Bug Fixes + + * N/A + + +{1.5.2} + + Changes included with release 1.5.2 + + [] + + Enhancements + + * NA + + [] + + Bug Fixes + + * Updated to direct-policy-1.0.1 to resolve a concurrency issue in the policy engine. + +{1.5} + + Changes included with release 1.5 + + [] + + Enhancements + + * Support for new wrapped key format. + + [] + + Bug Fixes + + * NA + +{1.4.1} + + Changes included with release 1.4.1 + + [] + + Enhancements + + * NA + + [] + + Bug Fixes + + * Fixed issue where UPD and TCP responders were crashing when rejecting tasks due to load. + +{1.4} + + Changes included with release 1.4 + + [] + + Enhancements + + * Added policy filter capabilities for returning only certificates that are in compliance with Local DNS publishing policy. + + * Removed delegation as it was confusing and never used. + + [] + + Bug Fixes + + * NA + +{1.3} + + Changes included with release 1.3 + + [] + + Enhancements + + * {{{http://code.google.com/p/nhin-d/issues/detail?id=237}237}}: Added ability to delegate sub zones to a different DNS server. + + [] + + Bug Fixes + + * NA + +{1.2.3} + + Changes included with release 1.2.3 + + [] + + * NA + + [] + + Bug Fixes + + * {{{http://code.google.com/p/nhin-d/issues/detail?id=218}218}}: Fixed build and test issues with JDK 7. + +{1.2.2} + + Changes included with release 1.2.2 + + [] + + Enhancements + + * NA + + [] + + Bug Fixes + + * {{{http://code.google.com/p/nhin-d/issues/detail?id=215}215}}: ANY type query failing when multiple types associated with DNS question. + +{1.2.1} + + Changes included with release 1.2.1 + + [] + + Enhancements + + * NA + + [] + + Bug Fixes + + * {{{http://code.google.com/p/nhin-d/issues/detail?id=182}182}}: Fixed issue with UDP replies not being properly truncated. + +{1.2} + + Changes included with release 1.2 + + [] + + Enhancements + + * DNSStoreProvider is now configurable using "org.nhindirect.dns.DNSStoreProviderClass" JVM parameter in the DNSServerService DNS. + + [] + + Bug Fixes + + * NA + +{1.1} + + Changes included with release 1.1 + + [] + + Enhancements + + * Added simple real time metrics exposed through JMX. + + [] + + Bug Fixes + + * {{{http://code.google.com/p/nhin-d/issues/detail?id=142}142}}: Certificate extension searches now searching for subject alt names. + +{1.0.0} + + Changes included with release 1.0. + + [] + + * Initial Release + + * Requires Java SE 1.6 or greater. + diff --git a/java/tags/dns-2.0.1/src/site/resources/css/site.css b/java/tags/dns-2.0.1/src/site/resources/css/site.css new file mode 100644 index 000000000..e69de29bb diff --git a/java/tags/dns-2.0.1/src/site/resources/images/logo.png b/java/tags/dns-2.0.1/src/site/resources/images/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..bf6958ae0101d0499de564342baafb5b2217564f GIT binary patch literal 13648 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 Pkpd{ls7jYh8V3F!!2)TE literal 0 HcmV?d00001 diff --git a/java/tags/dns-2.0.1/src/site/site.xml b/java/tags/dns-2.0.1/src/site/site.xml new file mode 100644 index 000000000..5dd34b73a --- /dev/null +++ b/java/tags/dns-2.0.1/src/site/site.xml @@ -0,0 +1,26 @@ + + + + The Direct Project: DNS Services + images/logo.png + http://nhindirect.org/ + + + + +

+ + + + + + + + ${reports} + + + org.apache.maven.skins + maven-classic-skin + 1.0 + + \ No newline at end of file diff --git a/java/tags/dns-2.0.1/src/site/xdoc/index.xml b/java/tags/dns-2.0.1/src/site/xdoc/index.xml new file mode 100644 index 000000000..13ccd164f --- /dev/null +++ b/java/tags/dns-2.0.1/src/site/xdoc/index.xml @@ -0,0 +1,29 @@ + + + + The Direct Project: Security And Trust Agent + Greg Meyer + + + The Direct Project: DNS Services + + +
+

+ The DNS services implement a simple authoritative only DNS server for hosting DNS records for a Direct + messaging domain. It is intentionally tuned to handle only certain type of DNS records including CERT + type records as defined by the security and trust + specification. +

+

+ The server can be run as a stand alone application or may be deployed as an operating system service. +

+

+ For details on deploying and configuring the DNS server, see the DNS server + users guide. +

+
+ +
diff --git a/java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/ConfigServiceDNSStore_configCertPolicyTest.java b/java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/ConfigServiceDNSStore_configCertPolicyTest.java new file mode 100644 index 000000000..86fc3d626 --- /dev/null +++ b/java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/ConfigServiceDNSStore_configCertPolicyTest.java @@ -0,0 +1,127 @@ +package org.nhindirect.dns; + +import java.net.URL; + +import org.nhind.config.CertPolicy; +import org.nhind.config.ConfigurationServiceProxy; +import org.nhind.config.PolicyLexicon; +import org.nhindirect.dns.util.ConfigServiceRunner; + +import junit.framework.TestCase; + +public class ConfigServiceDNSStore_configCertPolicyTest extends TestCase +{ + static final String VALID_POLICY = "(X509.TBS.EXTENSION.KeyUsage & 32) > 0"; + static final String INVALID_VALID_POLICY = "(X509.TBS.EXTENSION.KeyUsage4fds & | 32) > 0"; + + protected ConfigurationServiceProxy proxy; + + public void setUp() + { + + try + { + if (!ConfigServiceRunner.isServiceRunning()) + ConfigServiceRunner.startConfigService(); + proxy = new ConfigurationServiceProxy(ConfigServiceRunner.getConfigServiceURL()); + + cleanRecords(); + } + catch (Exception e) + { + throw new RuntimeException(e); + } + } + + private void cleanRecords() throws Exception + { + CertPolicy[] pols = proxy.getPolicies(); + + if (pols != null && pols.length > 0) + { + final Long[] ids = new Long[pols.length]; + for (int i = 0; i < pols.length; ++i) + ids[i] = pols[i].getId(); + + proxy.deletePolicies(ids); + } + pols = proxy.getPolicies(); + + assertNull(pols); + } + + public void testConfigCertPolicy_noJVMParam_assertNoPolicyConfiged() throws Exception + { + final ConfigServiceDNSStore store = new ConfigServiceDNSStore(new URL(ConfigServiceRunner.getConfigServiceURL())); + + assertNull(store.polExpression); + assertNull(store.polFilter); + } + + public void testConfigCertPolicy_policyDoesNotExists_assertNoPolicyConfiged() throws Exception + { + System.setProperty(ConfigServiceDNSStore.DNS_CERT_POLICY_NAME_VAR, "NoPolicy"); + + try + { + final ConfigServiceDNSStore store = new ConfigServiceDNSStore(new URL(ConfigServiceRunner.getConfigServiceURL())); + + assertNull(store.polExpression); + assertNull(store.polFilter); + } + finally + { + System.setProperty(ConfigServiceDNSStore.DNS_CERT_POLICY_NAME_VAR, ""); + } + } + + public void testConfigCertPolicy_invalidPolicy_assertNoPolicyConfiged() throws Exception + { + System.setProperty(ConfigServiceDNSStore.DNS_CERT_POLICY_NAME_VAR, "InvalidPolicy"); + + try + { + final CertPolicy policy = new CertPolicy(); + policy.setLexicon(PolicyLexicon.SIMPLE_TEXT_V1); + policy.setPolicyName("InvalidPolicy"); + policy.setPolicyData(INVALID_VALID_POLICY.getBytes()); + + proxy.addPolicy(policy); + + final ConfigServiceDNSStore store = new ConfigServiceDNSStore(new URL(ConfigServiceRunner.getConfigServiceURL())); + + assertNull(store.polExpression); + assertNull(store.polFilter); + } + finally + { + System.setProperty(ConfigServiceDNSStore.DNS_CERT_POLICY_NAME_VAR, ""); + } + } + + public void testConfigCertPolicy_validPolicy_assertPolicyConfiged() throws Exception + { + System.setProperty(ConfigServiceDNSStore.DNS_CERT_POLICY_NAME_VAR, "ValidPolicy"); + + try + { + final CertPolicy policy = new CertPolicy(); + policy.setLexicon(PolicyLexicon.SIMPLE_TEXT_V1); + policy.setPolicyName("ValidPolicy"); + policy.setPolicyData(VALID_POLICY.getBytes()); + + proxy.addPolicy(policy); + + final ConfigServiceDNSStore store = new ConfigServiceDNSStore(new URL(ConfigServiceRunner.getConfigServiceURL())); + + assertNotNull(store.polExpression); + assertNotNull(store.polFilter); + } + finally + { + System.setProperty(ConfigServiceDNSStore.DNS_CERT_POLICY_NAME_VAR, ""); + } + } + + +} diff --git a/java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/ConfigServiceDNSStore_isCertCompliantWithPolicyTest.java b/java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/ConfigServiceDNSStore_isCertCompliantWithPolicyTest.java new file mode 100644 index 000000000..d62d55d85 --- /dev/null +++ b/java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/ConfigServiceDNSStore_isCertCompliantWithPolicyTest.java @@ -0,0 +1,142 @@ +package org.nhindirect.dns; + +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Matchers.any; + +import java.net.URL; +import java.security.cert.X509Certificate; + +import junit.framework.TestCase; + +import org.nhind.config.CertPolicy; +import org.nhind.config.ConfigurationServiceProxy; +import org.nhind.config.PolicyLexicon; +import org.nhindirect.dns.util.ConfigServiceRunner; +import org.nhindirect.dns.util.DNSRecordUtil; +import org.nhindirect.policy.PolicyExpression; +import org.nhindirect.policy.PolicyFilter; + +public class ConfigServiceDNSStore_isCertCompliantWithPolicyTest extends TestCase +{ + static final String KEY_ENC_POLICY = "(X509.TBS.EXTENSION.KeyUsage & 32) > 0"; + + protected ConfigurationServiceProxy proxy; + + public void setUp() + { + + try + { + if (!ConfigServiceRunner.isServiceRunning()) + ConfigServiceRunner.startConfigService(); + proxy = new ConfigurationServiceProxy(ConfigServiceRunner.getConfigServiceURL()); + + cleanRecords(); + } + catch (Exception e) + { + throw new RuntimeException(e); + } + } + + private void cleanRecords() throws Exception + { + CertPolicy[] pols = proxy.getPolicies(); + + if (pols != null && pols.length > 0) + { + final Long[] ids = new Long[pols.length]; + for (int i = 0; i < pols.length; ++i) + ids[i] = pols[i].getId(); + + proxy.deletePolicies(ids); + } + pols = proxy.getPolicies(); + + assertNull(pols); + } + + public void testisCertCompliantWithPolicy_noPolicyConfigured_assertCompliant() throws Exception + { + final ConfigServiceDNSStore store = new ConfigServiceDNSStore(new URL(ConfigServiceRunner.getConfigServiceURL())); + + assertNull(store.polExpression); + assertNull(store.polFilter); + + X509Certificate cert = DNSRecordUtil.loadCertificate("bob.der"); + + assertTrue(store.isCertCompliantWithPolicy(cert)); + } + + public void testisCertCompliantWithPolicy_policyConfigured_compliantCert_assertCompliant() throws Exception + { + System.setProperty(ConfigServiceDNSStore.DNS_CERT_POLICY_NAME_VAR, "ValidPolicy"); + + try + { + final CertPolicy policy = new CertPolicy(); + policy.setLexicon(PolicyLexicon.SIMPLE_TEXT_V1); + policy.setPolicyName("ValidPolicy"); + policy.setPolicyData(KEY_ENC_POLICY.getBytes()); + + proxy.addPolicy(policy); + + final ConfigServiceDNSStore store = new ConfigServiceDNSStore(new URL(ConfigServiceRunner.getConfigServiceURL())); + + assertNotNull(store.polExpression); + assertNotNull(store.polFilter); + + X509Certificate cert = DNSRecordUtil.loadCertificate("bob.der"); + + assertTrue(store.isCertCompliantWithPolicy(cert)); + } + finally + { + System.setProperty(ConfigServiceDNSStore.DNS_CERT_POLICY_NAME_VAR, ""); + } + } + + public void testisCertCompliantWithPolicy_policyConfigured_nonCompliantCert_assertNonCompliant() throws Exception + { + System.setProperty(ConfigServiceDNSStore.DNS_CERT_POLICY_NAME_VAR, "ValidPolicy"); + + try + { + final CertPolicy policy = new CertPolicy(); + policy.setLexicon(PolicyLexicon.SIMPLE_TEXT_V1); + policy.setPolicyName("ValidPolicy"); + policy.setPolicyData(KEY_ENC_POLICY.getBytes()); + + proxy.addPolicy(policy); + + final ConfigServiceDNSStore store = new ConfigServiceDNSStore(new URL(ConfigServiceRunner.getConfigServiceURL())); + + assertNotNull(store.polExpression); + assertNotNull(store.polFilter); + + X509Certificate cert = DNSRecordUtil.loadCertificate("umesh.der"); + + assertFalse(store.isCertCompliantWithPolicy(cert)); + } + finally + { + System.setProperty(ConfigServiceDNSStore.DNS_CERT_POLICY_NAME_VAR, ""); + } + } + + public void testisCertCompliantWithPolicy_exceptionInFilter_assertCompliant() throws Exception + { + final ConfigServiceDNSStore store = new ConfigServiceDNSStore(new URL(ConfigServiceRunner.getConfigServiceURL())); + final PolicyFilter filt = mock(PolicyFilter.class); + + doThrow(new RuntimeException("Just Passing Through")).when(filt).isCompliant((X509Certificate)any(), (PolicyExpression)any()); + + store.polFilter = filt; + + X509Certificate cert = DNSRecordUtil.loadCertificate("umesh.der"); + + assertTrue(store.isCertCompliantWithPolicy(cert)); + + } +} diff --git a/java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/DNSConnectionTest.java b/java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/DNSConnectionTest.java new file mode 100644 index 000000000..8ba48e290 --- /dev/null +++ b/java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/DNSConnectionTest.java @@ -0,0 +1,248 @@ +package org.nhindirect.dns; + + +import org.apache.mina.util.AvailablePortFinder; +import org.nhindirect.dns.util.IPUtils; +import org.xbill.DNS.Cache; +import org.xbill.DNS.DClass; +import org.xbill.DNS.ExtendedResolver; +import org.xbill.DNS.Lookup; +import org.xbill.DNS.Name; +import org.xbill.DNS.Options; +import org.xbill.DNS.Record; +import org.xbill.DNS.ResolverConfig; +import org.xbill.DNS.Type; + +import junit.framework.TestCase; + +public class DNSConnectionTest extends TestCase +{ + private static String[] servers; + private static int recType = Type.A; + private static String lookupRec; + private static boolean useTCP = false; + + private static int getRecTypeFromArg(String arg) + { + int retVal = Type.A; + + if (arg.equalsIgnoreCase("A")) + retVal = Type.A; + else if (arg.equalsIgnoreCase("CERT")) + retVal = Type.CERT; + else if (arg.equalsIgnoreCase("SOA")) + retVal = Type.SOA; + else if (arg.equalsIgnoreCase("MX")) + retVal = Type.MX; + else + System.err.println("Warning: Unsupported record type " + arg + ". Defaulting to type A."); + + return retVal; + } + + /* + * Application entry point for testing dnsjava functionality on different platforms. + */ + public static void main(String argv[]) + { + if (argv.length == 0) + { + printUsage(); + System.exit(-1); + } + + // Check parameters + for (int i = 0; i < argv.length; i++) + { + String arg = argv[i]; + + // Options + if (!arg.startsWith("-")) + { + lookupRec = arg; + } + else if (arg.equalsIgnoreCase("-serv")) + { + if (i == argv.length - 1 || argv[i + 1].startsWith("-")) + { + System.err.println("Error: Missing server list."); + System.exit(-1); + } + servers = argv[++i].split(","); + } + else if (arg.equals("-type")) + { + if (i == argv.length - 1 || argv[i + 1].startsWith("-")) + { + System.err.println("Error: Missing record type."); + System.exit(-1); + } + + recType = getRecTypeFromArg(argv[++i]); + } + else if (arg.equalsIgnoreCase("-useTCP")) + { + if (i == argv.length - 1 || argv[i + 1].startsWith("-")) + { + System.err.println("Error: Missing TCP indicator"); + System.exit(-1); + } + useTCP = Boolean.parseBoolean(argv[++i]); + } + else if (arg.equals("-help")) + { + printUsage(); + System.exit(-1); + } + else + { + System.err.println("Error: Unknown argument " + arg + "\n"); + printUsage(); + System.exit(-1); + } + } + + if (lookupRec == null) + { + System.err.println("Error: Missing record to lookup\n"); + printUsage(); + System.exit(-1); + } + + try + { + performLookup(); + } + catch (Exception e) + { + e.printStackTrace(); + } + + System.exit(0); + + } + + private static void performLookup() throws Exception + { + // turn on debug settings for the DNS client + Options.set("verbose", "true"); + Cache ch = Lookup.getDefaultCache(DClass.IN); + ch.clearCache(); + + if (servers == null || servers.length == 0) + servers = ResolverConfig.getCurrentConfig().servers(); + + System.out.println("\r\nConfigure DNS resolvers:"); + for (String server : servers) + { + System.out.println("\t" + server); + } + + System.out.println("\r\nLookup up record " + lookupRec); + + Lookup lu = new Lookup(new Name(lookupRec), recType); + ExtendedResolver resolver = new ExtendedResolver(servers); + resolver.setTCP(useTCP); + lu.setResolver(resolver); + + + Record[] retRecords = lu.run(); + + if (retRecords != null && retRecords.length > 0) + System.out.println(retRecords.length + " records found."); + else + System.out.println("No records found."); + + } + + /* + * Prints the command line usage. + */ + private static void printUsage() + { + StringBuffer use = new StringBuffer(); + use.append("Usage:\n"); + use.append("java DNSConnectionTest (options)...\n\n"); + use.append("options:\n"); + use.append("-serv server List of DNS servers used for resolution\n"); + use.append(" Default: Currently configured DNS servers\n\n"); + use.append("-type record type Type of DNS record to lookup\n"); + use.append(" Default: A\n\n"); + use.append("-useTCP use TCP Use TCP to connect to the DNS server\n"); + use.append(" Default: false\n\n"); + + System.err.println(use); + } + + public void testDNSSocketConnectionTCPWithProxyStore() throws Exception + { + DNSServerSettings settings = new DNSServerSettings(); + settings.setPort(AvailablePortFinder.getNextAvailable( 1024 )); + + DNSServer server = new DNSServer(new ProxyDNSStore(), settings); + + + server.start(); + + // give the server a couple seconds to start + Thread.sleep(2000); + + // turn on debug settings for the DNS client + Options.set("verbose", "true"); + + Lookup lu = new Lookup(new Name("google.com"), Type.A); + + + ExtendedResolver resolver = new ExtendedResolver(IPUtils.getDNSLocalIps()); + resolver.setTCP(true); + resolver.setPort(settings.getPort()); + lu.setResolver(resolver); // default retries is 3, limite to 2 + + + Record[] retRecords = lu.run(); + assertNotNull(retRecords); + + + + server.stop(); + + Thread.sleep(2000); + } + + + public void testDNSSocketConnectionUDPWithProxyStore() throws Exception + { + + + DNSServerSettings settings = new DNSServerSettings(); + settings.setPort(AvailablePortFinder.getNextAvailable( 1024 )); + + DNSServer server = new DNSServer(new ProxyDNSStore(), settings); + + + server.start(); + + // give the server a couple seconds to start + Thread.sleep(2000); + + // turn on debug settings for the DNS client + Options.set("verbose", "true"); + + Lookup lu = new Lookup(new Name("google.com"), Type.A); + + ExtendedResolver resolver = new ExtendedResolver(IPUtils.getDNSLocalIps()); + resolver.setTCP(false); + resolver.setPort(settings.getPort()); + lu.setResolver(resolver); // default retries is 3, limite to 2 + + + Record[] retRecords = lu.run(); + assertNotNull(retRecords); + + + server.stop(); + + Thread.sleep(2000); + } + +} diff --git a/java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/DNSServer_Function_Test.java b/java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/DNSServer_Function_Test.java new file mode 100644 index 000000000..e31ce602d --- /dev/null +++ b/java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/DNSServer_Function_Test.java @@ -0,0 +1,1043 @@ +package org.nhindirect.dns; + + +import java.net.URL; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; + +import junit.framework.TestCase; + +import org.apache.mina.util.AvailablePortFinder; +import org.nhind.config.CertPolicy; +import org.nhind.config.Certificate; +import org.nhind.config.ConfigurationServiceProxy; +import org.nhind.config.DnsRecord; +import org.nhind.config.EntityStatus; +import org.nhind.config.PolicyLexicon; +import org.nhindirect.dns.util.BaseTestPlan; +import org.nhindirect.dns.util.ConfigServiceRunner; +import org.nhindirect.dns.util.DNSRecordUtil; +import org.nhindirect.dns.util.IPUtils; +import org.xbill.DNS.CERTRecord; +import org.xbill.DNS.Cache; +import org.xbill.DNS.DClass; +import org.xbill.DNS.ExtendedResolver; +import org.xbill.DNS.Lookup; +import org.xbill.DNS.Name; +import org.xbill.DNS.Record; +import org.xbill.DNS.Type; +import org.xbill.DNS.security.CERTConverter; + +public class DNSServer_Function_Test extends TestCase +{ + static final String KEY_ENC_POLICY = "(X509.TBS.EXTENSION.KeyUsage & 32) > 0"; + + private static class Query + { + public String name; + public int type; + + public Query(String name, int type) + { + this.name = name; + this.type = type; + } + } + + private Certificate xCertToCert(X509Certificate cert) throws Exception + { + Certificate retVal = new Certificate(); + retVal.setOwner(DNSRecordUtil.getCertOwner(cert)); + retVal.setData(cert.getEncoded()); + + return retVal; + } + + abstract class TestPlan extends BaseTestPlan + { + protected int port; + protected DNSServer server = null; + protected ConfigurationServiceProxy proxy; + + + @Override + protected void setupMocks() throws Exception + { + if (!ConfigServiceRunner.isServiceRunning()) + ConfigServiceRunner.startConfigService(); + + proxy = new ConfigurationServiceProxy(ConfigServiceRunner.getConfigServiceURL()); + + cleanRecords(); + + addRecords(); + + port = AvailablePortFinder.getNextAvailable(1024); + DNSServerSettings settings = new DNSServerSettings(); + settings.setPort(port); + + + + server = new DNSServer(new ConfigServiceDNSStore(new URL(ConfigServiceRunner.getConfigServiceURL())), settings); + + server.start(); + } + + @Override + protected void tearDownMocks() throws Exception + { + if (server != null) + server.stop(); + } + + @Override + protected void performInner() throws Exception + { + ExtendedResolver resolver = new ExtendedResolver(IPUtils.getDNSLocalIps()); + resolver.setTimeout(300); + + resolver.setTCP(true); + resolver.setPort(port); + + Collection retrievedRecord = new ArrayList(); + + Collection queries = getTestQueries(); + for (Query query : queries) + { + Lookup lu = new Lookup(new Name(query.name), query.type); + Cache ch = Lookup.getDefaultCache(DClass.IN); + ch.clearCache(); + lu.setResolver(resolver); + + Record[] retRecords = lu.run(); + if (retRecords != null && retRecords.length > 0) + retrievedRecord.addAll(Arrays.asList(retRecords)); + } + + doAssertions(retrievedRecord); + } + + protected void cleanRecords() throws Exception + { + CertPolicy[] pols = proxy.getPolicies(); + + if (pols != null && pols.length > 0) + { + final Long[] ids = new Long[pols.length]; + for (int i = 0; i < pols.length; ++i) + ids[i] = pols[i].getId(); + + proxy.deletePolicies(ids); + } + pols = proxy.getPolicies(); + + assertNull(pols); + + DnsRecord[] rec = proxy.getDNSByType(Type.ANY); + + if (rec != null && rec.length > 0) + proxy.removeDNS(rec); + + rec = proxy.getDNSByType(Type.ANY); + + assertNull(rec); + + + Certificate[] certs = proxy.getCertificatesForOwner(null, null); + if (certs != null && certs.length > 0) + { + long[] ids = new long[certs.length]; + int cnt = 0; + for (Certificate cert : certs) + ids[cnt++] = cert.getId(); + + proxy.removeCertificates(ids); + } + + certs = proxy.getCertificatesForOwner("", null); + + assertNull(certs); + } + + protected abstract void addRecords() throws Exception; + + protected abstract Collection getTestQueries() throws Exception; + + protected abstract void doAssertions(Collection records) throws Exception; + + } + + /* + public void testLoadTest() throws Exception + { + new TestPlan() + { + protected void addRecords() throws Exception + { + ArrayList recs = new ArrayList(); + DnsRecord rec = DNSRecordUtil.createARecord("example.domain.com", "127.0.0.1"); + recs.add(rec); + + rec = DNSRecordUtil.createARecord("example2.domain.com", "127.0.0.1"); + recs.add(rec); + + proxy.addDNS(recs.toArray(new DnsRecord[recs.size()])); + + } + + protected Collection getTestQueries() throws Exception + { + Collection queries = new ArrayList(); + queries.add(new Query("example2.domain.com", Type.A)); + + return queries; + } + + @Override + protected void performInner() throws Exception + { + + final Runnable resolveRunner = new Runnable() + { + public void run() + { + try + { + ExtendedResolver resolver = new ExtendedResolver(new String[]{"127.0.0.1"}); + resolver.setTimeout(300); + + resolver.setTCP(true); + resolver.setPort(1053); + + Collection retrievedRecord = new ArrayList(); + + Collection queries = getTestQueries(); + for (Query query : queries) + { + Lookup lu = new Lookup(new Name(query.name), query.type); + Cache ch = Lookup.getDefaultCache(DClass.IN); + ch.clearCache(); + lu.setResolver(resolver); + + Record[] retRecords = lu.run(); + if (retRecords != null && retRecords.length > 0) + retrievedRecord.addAll(Arrays.asList(retRecords)); + } + } + catch (Exception e) + { + + } + } + }; + + for (int i = 0; i < 300; ++i) + { + final Thread thr = new Thread(resolveRunner); + thr.start(); + } + + Thread.sleep(100000000); + //doAssertions(retrievedRecord); + } + protected void doAssertions(Collection records) throws Exception + { + assertNotNull(records); + assertEquals(1, records.size()); + assertEquals("example2.domain.com.", records.iterator().next().getName().toString()); + } + }.perform(); + } + */ + public void testQueryARecord_AssertRecordsRetrieved_NoSOA() throws Exception + { + new TestPlan() + { + protected void addRecords() throws Exception + { + ArrayList recs = new ArrayList(); + DnsRecord rec = DNSRecordUtil.createARecord("example.domain.com", "127.0.0.1"); + recs.add(rec); + + rec = DNSRecordUtil.createARecord("example2.domain.com", "127.0.0.1"); + recs.add(rec); + + proxy.addDNS(recs.toArray(new DnsRecord[recs.size()])); + + } + + protected Collection getTestQueries() throws Exception + { + Collection queries = new ArrayList(); + queries.add(new Query("example2.domain.com", Type.A)); + + return queries; + } + + protected void doAssertions(Collection records) throws Exception + { + assertNotNull(records); + assertEquals(1, records.size()); + assertEquals("example2.domain.com.", records.iterator().next().getName().toString()); + } + }.perform(); + } + + public void testQueryARecord_AssertRecordsRetrieved_SOARecord() throws Exception + { + new TestPlan() + { + protected void addRecords() throws Exception + { + ArrayList recs = new ArrayList(); + DnsRecord rec = DNSRecordUtil.createARecord("example.domain.com", "127.0.0.1"); + recs.add(rec); + + rec = DNSRecordUtil.createARecord("example2.domain.com", "127.0.0.1"); + recs.add(rec); + + rec = DNSRecordUtil.createARecord("sub2.example2.domain.com", "127.0.0.1"); + recs.add(rec); + + rec = DNSRecordUtil.createSOARecord("domain.com", "nsserver.domain.com","master.domain.com"); + recs.add(rec); + + proxy.addDNS(recs.toArray(new DnsRecord[recs.size()])); + + } + + protected Collection getTestQueries() throws Exception + { + Collection queries = new ArrayList(); + queries.add(new Query("sub2.example2.domain.com", Type.A)); + + return queries; + } + + protected void doAssertions(Collection records) throws Exception + { + assertNotNull(records); + assertEquals(1, records.size()); + assertEquals("sub2.example2.domain.com.", records.iterator().next().getName().toString()); + } + }.perform(); + } + +/* + public void testQueryARecord_noRecordInDNSServer_assertDelegation() throws Exception + { + new TestPlan() + { + protected void addRecords() throws Exception + { + ArrayList recs = new ArrayList(); + DnsRecord rec = DNSRecordUtil.createARecord("example.domain.com", "127.0.0.1"); + recs.add(rec); + + rec = DNSRecordUtil.createNSRecord("sub.example.domain.com", "127.0.0.3"); + recs.add(rec); + + proxy.addDNS(recs.toArray(new DnsRecord[recs.size()])); + + } + + protected Collection getTestQueries() throws Exception + { + Collection queries = new ArrayList(); + queries.add(new Query("sub.example.domain.com", Type.A)); + + return queries; + } + + protected void doAssertions(Collection records) throws Exception + { + assertNotNull(records); + assertEquals(1, records.size()); + Record rec = records.iterator().next(); + assertEquals("sub.example.domain.com.", rec.getName().toString()); + assertEquals(Type.NS, rec.getType()); + } + }.perform(); + } +*/ + + public void testQueryARecordByAny_AssertRecordsRetrieved() throws Exception + { + new TestPlan() + { + protected void addRecords() throws Exception + { + ArrayList recs = new ArrayList(); + DnsRecord rec = DNSRecordUtil.createARecord("example.domain.com", "127.0.0.1"); + recs.add(rec); + + rec = DNSRecordUtil.createARecord("example2.domain.com", "127.0.0.1"); + recs.add(rec); + + proxy.addDNS(recs.toArray(new DnsRecord[recs.size()])); + + } + + protected Collection getTestQueries() throws Exception + { + Collection queries = new ArrayList(); + queries.add(new Query("example2.domain.com", Type.ANY)); + + return queries; + } + + protected void doAssertions(Collection records) throws Exception + { + assertNotNull(records); + assertEquals(1, records.size()); + assertEquals("example2.domain.com.", records.iterator().next().getName().toString()); + } + }.perform(); + } + + public void testQueryMutliARecords_AssertRecordsRetrieved() throws Exception + { + new TestPlan() + { + protected void addRecords() throws Exception + { + ArrayList recs = new ArrayList(); + DnsRecord rec = DNSRecordUtil.createARecord("example.domain.com", "127.0.0.1"); + recs.add(rec); + + rec = DNSRecordUtil.createARecord("example.domain.com", "127.0.0.2"); + recs.add(rec); + + rec = DNSRecordUtil.createSOARecord("domain.com", "nsserver.domain.com","master.domain.com"); + recs.add(rec); + + proxy.addDNS(recs.toArray(new DnsRecord[recs.size()])); + + } + + protected Collection getTestQueries() throws Exception + { + Collection queries = new ArrayList(); + queries.add(new Query("example.domain.com", Type.A)); + + return queries; + } + + protected void doAssertions(Collection records) throws Exception + { + assertNotNull(records); + assertEquals(2, records.size()); + assertEquals("example.domain.com.", records.iterator().next().getName().toString()); + } + }.perform(); + } + + public void testQueryARecords_AssertNoRecordsRetrieved() throws Exception + { + new TestPlan() + { + protected void addRecords() throws Exception + { + + } + + protected Collection getTestQueries() throws Exception + { + + Collection queries = new ArrayList(); + queries.add(new Query("example.domain.com", Type.A)); + + return queries; + } + + protected void doAssertions(Collection records) throws Exception + { + assertNotNull(records); + assertEquals(0, records.size()); + } + }.perform(); + } + + public void testAnyQueryType_multipleTypesInRecord_AssertRecordsRetrieved() throws Exception + { + new TestPlan() + { + protected void addRecords() throws Exception + { + ArrayList recs = new ArrayList(); + DnsRecord rec = DNSRecordUtil.createARecord("example.domain.com", "127.0.0.1"); + recs.add(rec); + + rec = DNSRecordUtil.createARecord("example.domain.com", "127.0.0.2"); + recs.add(rec); + + rec = DNSRecordUtil.createMXRecord("example.domain.com", "domain.com", 1); + recs.add(rec); + + proxy.addDNS(recs.toArray(new DnsRecord[recs.size()])); + + } + + protected Collection getTestQueries() throws Exception + { + Collection queries = new ArrayList(); + queries.add(new Query("example.domain.com", Type.ANY)); + + return queries; + } + + protected void doAssertions(Collection records) throws Exception + { + assertNotNull(records); + assertEquals(3, records.size()); + assertEquals("example.domain.com.", records.iterator().next().getName().toString()); + } + }.perform(); + } + + public void testQueryCERTRecords_AssertRecordsRetrieved() throws Exception + { + new TestPlan() + { + protected void addRecords() throws Exception + { + // add some CERT records + ArrayList recs = new ArrayList(); + + X509Certificate cert = DNSRecordUtil.loadCertificate("bob.der"); + Certificate addCert = xCertToCert(cert); + recs.add(addCert); + + cert = DNSRecordUtil.loadCertificate("gm2552.der"); + addCert = xCertToCert(cert); + recs.add(addCert); + + cert = DNSRecordUtil.loadCertificate("ryan.der"); + addCert = xCertToCert(cert); + recs.add(addCert); + + proxy.addCertificates(recs.toArray(new Certificate[recs.size()])); + + + ArrayList soaRecs = new ArrayList(); + DnsRecord rec = DNSRecordUtil.createSOARecord("securehealthemail.com", "nsserver.securehealthemail.com","master.securehealthemail.com"); + soaRecs.add(rec); + + proxy.addDNS(soaRecs.toArray(new DnsRecord[soaRecs.size()])); + + } + + protected Collection getTestQueries() throws Exception + { + Collection queries = new ArrayList(); + queries.add(new Query("gm2552.securehealthemail.com", Type.CERT)); + queries.add(new Query("ryan.securehealthemail.com", Type.ANY)); + queries.add(new Query("bob.somewhere.com", Type.A)); + + return queries; + + } + + protected void doAssertions(Collection records) throws Exception + { + assertNotNull(records); + assertEquals(2, records.size()); + + boolean foundGreg = false; + boolean foundRyan = false; + for (Record record : records) + { + assertTrue(record instanceof CERTRecord); + + X509Certificate cert = (X509Certificate)CERTConverter.parseRecord((CERTRecord)record); + assertNotNull(cert); + + if (DNSRecordUtil.getCertOwner(cert).equals("gm2552@securehealthemail.com")) + foundGreg = true; + else if (DNSRecordUtil.getCertOwner(cert).equals("ryan@securehealthemail.com")) + foundRyan = true; + } + + assertTrue(foundGreg); + assertTrue(foundRyan); + } + }.perform(); + } + + + public void testQueryCERTRecords_policyExists_AssertRecordsRetrieved() throws Exception + { + new TestPlan() + { + + @Override + public void tearDownMocks() throws Exception + { + + System.setProperty(ConfigServiceDNSStore.DNS_CERT_POLICY_NAME_VAR, ""); + super.tearDownMocks(); + } + + @Override + protected void setupMocks() throws Exception + { + System.setProperty(ConfigServiceDNSStore.DNS_CERT_POLICY_NAME_VAR, "ValidPolicy"); + + if (!ConfigServiceRunner.isServiceRunning()) + ConfigServiceRunner.startConfigService(); + + proxy = new ConfigurationServiceProxy(ConfigServiceRunner.getConfigServiceURL()); + + cleanRecords(); + + addRecords(); + + port = AvailablePortFinder.getNextAvailable(1024); + DNSServerSettings settings = new DNSServerSettings(); + settings.setPort(port); + + // create the key encypherment policy + final CertPolicy policy = new CertPolicy(); + policy.setLexicon(PolicyLexicon.SIMPLE_TEXT_V1); + policy.setPolicyName("ValidPolicy"); + policy.setPolicyData(KEY_ENC_POLICY.getBytes()); + + proxy.addPolicy(policy); + + + server = new DNSServer(new ConfigServiceDNSStore(new URL(ConfigServiceRunner.getConfigServiceURL())), settings); + + server.start(); + } + + protected void addRecords() throws Exception + { + // add some CERT records + ArrayList recs = new ArrayList(); + + X509Certificate cert = DNSRecordUtil.loadCertificate("bob.der"); + Certificate addCert = xCertToCert(cert); + recs.add(addCert); + + cert = DNSRecordUtil.loadCertificate("umesh.der"); + addCert = xCertToCert(cert); + recs.add(addCert); + + proxy.addCertificates(recs.toArray(new Certificate[recs.size()])); + + + ArrayList soaRecs = new ArrayList(); + DnsRecord rec = DNSRecordUtil.createSOARecord("securehealthemail.com", "nsserver.securehealthemail.com","master.securehealthemail.com"); + soaRecs.add(rec); + + rec = DNSRecordUtil.createSOARecord("nhind.hsgincubator.com", "nsserver.nhind.hsgincubator.com","master.nhind.hsgincubator.com"); + soaRecs.add(rec); + + + proxy.addDNS(soaRecs.toArray(new DnsRecord[soaRecs.size()])); + + } + + protected Collection getTestQueries() throws Exception + { + Collection queries = new ArrayList(); + queries.add(new Query("bob.nhind.hsgincubator.com", Type.CERT)); + queries.add(new Query("umesh.securehealthemail.com", Type.CERT)); + + return queries; + + } + + protected void doAssertions(Collection records) throws Exception + { + assertNotNull(records); + assertEquals(1, records.size()); + + final Record record = records.iterator().next(); + assertTrue(record instanceof CERTRecord); + + X509Certificate cert = (X509Certificate)CERTConverter.parseRecord((CERTRecord)record); + assertNotNull(cert); + + DNSRecordUtil.getCertOwner(cert).equals("bob@nhind.hsgincubator.com"); + } + }.perform(); + } + + public void testQueryCERTRecords_AssertNoRecordsRetrieved() throws Exception + { + new TestPlan() + { + protected void addRecords() throws Exception + { + + } + + protected Collection getTestQueries() throws Exception + { + Collection queries = new ArrayList(); + queries.add(new Query("gm2552.securehealthemail.com", Type.CERT)); + queries.add(new Query("ryan.securehealthemail.com", Type.ANY)); + queries.add(new Query("bob.somewhere.com", Type.A)); + + return queries; + + } + + protected void doAssertions(Collection records) throws Exception + { + assertNotNull(records); + assertEquals(0, records.size()); + + } + }.perform(); + } + + public void testQueryIPKIXCERTRecords_AssertRecordsRetrieved() throws Exception + { + new TestPlan() + { + protected void addRecords() throws Exception + { + // add some CERT records + ArrayList recs = new ArrayList(); + + X509Certificate cert = DNSRecordUtil.loadCertificate("ryan.der"); + Certificate addCert = xCertToCert(cert); + recs.add(addCert); + + cert = DNSRecordUtil.loadCertificate("gm2552.der"); + addCert = xCertToCert(cert); + recs.add(addCert); + + Certificate ipkixCert = new Certificate(); + ipkixCert.setOwner("somewhere.com"); + ipkixCert.setData("http://localhost/somewhere.der".getBytes()); + ipkixCert.setPrivateKey(false); + ipkixCert.setStatus(EntityStatus.ENABLED); + recs.add(ipkixCert); + + proxy.addCertificates(recs.toArray(new Certificate[recs.size()])); + + + ArrayList soaRecs = new ArrayList(); + DnsRecord rec = DNSRecordUtil.createSOARecord("securehealthemail.com", "nsserver.securehealthemail.com","master.securehealthemail.com"); + soaRecs.add(rec); + + rec = DNSRecordUtil.createSOARecord("somewhere.com", "nsserver.somewhere.com","master.somewhere.com"); + soaRecs.add(rec); + + proxy.addDNS(soaRecs.toArray(new DnsRecord[soaRecs.size()])); + + } + + protected Collection getTestQueries() throws Exception + { + Collection queries = new ArrayList(); + queries.add(new Query("somewhere.com", Type.CERT)); + queries.add(new Query("ryan.securehealthemail.com", Type.ANY)); + queries.add(new Query("bob.somewhere.com", Type.A)); + + return queries; + + } + + protected void doAssertions(Collection records) throws Exception + { + assertNotNull(records); + assertEquals(2, records.size()); + + boolean foundSomewhere = false; + boolean foundRyan = false; + for (Record record : records) + { + assertTrue(record instanceof CERTRecord); + + CERTRecord certRect = (CERTRecord)record; + + if (certRect.getCertType() == CERTRecord.URI) + { + + assertEquals("http://localhost/somewhere.der", new String(certRect.getCert())); + foundSomewhere = true; + } + else + { + X509Certificate cert = (X509Certificate)CERTConverter.parseRecord((CERTRecord)record); + assertNotNull(cert); + assertTrue(DNSRecordUtil.getCertOwner(cert).equals("ryan@securehealthemail.com")); + foundRyan = true; + } + } + + assertTrue(foundSomewhere); + assertTrue(foundRyan); + } + }.perform(); + } + + public void testQueryMXRecord_AssertRecordsRetrieved() throws Exception + { + new TestPlan() + { + protected void addRecords() throws Exception + { + ArrayList recs = new ArrayList(); + DnsRecord rec = DNSRecordUtil.createMXRecord("domain.com", "example.domain.com", 1); + recs.add(rec); + + rec = DNSRecordUtil.createMXRecord("domain.com", "example2.domain.com", 2); + recs.add(rec); + + rec = DNSRecordUtil.createMXRecord("domain2.com", "example.domain2.com", 1); + recs.add(rec); + + proxy.addDNS(recs.toArray(new DnsRecord[recs.size()])); + + } + + protected Collection getTestQueries() throws Exception + { + Collection queries = new ArrayList(); + queries.add(new Query("domain.com", Type.MX)); + queries.add(new Query("domain.com", Type.A)); + + return queries; + } + + protected void doAssertions(Collection records) throws Exception + { + assertNotNull(records); + assertEquals(2, records.size()); + assertEquals("domain.com.", records.iterator().next().getName().toString()); + } + }.perform(); + } + + public void testQueryMXRecordByA_AssertNoRecordsRetrieved() throws Exception + { + new TestPlan() + { + protected void addRecords() throws Exception + { + ArrayList recs = new ArrayList(); + DnsRecord rec = DNSRecordUtil.createMXRecord("domain.com", "example.domain.com", 1); + recs.add(rec); + + rec = DNSRecordUtil.createMXRecord("domain.com", "example2.domain.com", 2); + recs.add(rec); + + rec = DNSRecordUtil.createMXRecord("domain2.com", "example.domain2.com", 1); + recs.add(rec); + + proxy.addDNS(recs.toArray(new DnsRecord[recs.size()])); + + } + + protected Collection getTestQueries() throws Exception + { + Collection queries = new ArrayList(); + queries.add(new Query("domain.com", Type.A)); + + return queries; + } + + protected void doAssertions(Collection records) throws Exception + { + assertNotNull(records); + assertEquals(0, records.size()); + } + }.perform(); + } + + + public void testQueryNSRecord_AssertRecordsRetrieved() throws Exception + { + new TestPlan() + { + protected void addRecords() throws Exception + { + ArrayList recs = new ArrayList(); + DnsRecord rec = DNSRecordUtil.createNSRecord("domain.com", "ns.domain.com"); + recs.add(rec); + + rec = DNSRecordUtil.createNSRecord("domain.com", "ns2.domain.com"); + recs.add(rec); + + rec = DNSRecordUtil.createNSRecord("domain2.com", "ns.domain2.com"); + recs.add(rec); + + proxy.addDNS(recs.toArray(new DnsRecord[recs.size()])); + + } + + protected Collection getTestQueries() throws Exception + { + Collection queries = new ArrayList(); + queries.add(new Query("domain.com", Type.NS)); + queries.add(new Query("domain.com", Type.A)); + + return queries; + } + + protected void doAssertions(Collection records) throws Exception + { + assertNotNull(records); + assertEquals(2, records.size()); + assertEquals("domain.com.", records.iterator().next().getName().toString()); + + for (Record rec : records) + { + assertEquals(Type.NS, rec.getType()); + } + } + }.perform(); + } + + public void testQueryNSRecordByA_AssertNoRecordsRetrieved() throws Exception + { + new TestPlan() + { + protected void addRecords() throws Exception + { + ArrayList recs = new ArrayList(); + DnsRecord rec = DNSRecordUtil.createNSRecord("domain.com", "ns.domain.com"); + recs.add(rec); + + rec = DNSRecordUtil.createNSRecord("domain.com", "ns2.domain.com"); + recs.add(rec); + + rec = DNSRecordUtil.createNSRecord("domain2.com", "ns.domain2.com"); + recs.add(rec); + + proxy.addDNS(recs.toArray(new DnsRecord[recs.size()])); + + } + + protected Collection getTestQueries() throws Exception + { + Collection queries = new ArrayList(); + queries.add(new Query("domain.com", Type.A)); + + return queries; + } + + protected void doAssertions(Collection records) throws Exception + { + assertNotNull(records); + assertEquals(0, records.size()); + } + }.perform(); + } + + public void testQueryCNAMERecord_AssertRecordsRetrieved() throws Exception + { + new TestPlan() + { + protected void addRecords() throws Exception + { + ArrayList recs = new ArrayList(); + DnsRecord rec = DNSRecordUtil.createCNAMERecord("domainserver.com", "domain.com"); + recs.add(rec); + + rec = DNSRecordUtil.createCNAMERecord("domainserver2.com", "domain.com"); + recs.add(rec); + + rec = DNSRecordUtil.createCNAMERecord("domain2server.com", "domain2.com"); + recs.add(rec); + + proxy.addDNS(recs.toArray(new DnsRecord[recs.size()])); + + } + + protected Collection getTestQueries() throws Exception + { + Collection queries = new ArrayList(); + queries.add(new Query("domainserver.com", Type.CNAME)); + queries.add(new Query("domainserver2.com", Type.CNAME)); + queries.add(new Query("domain.com", Type.A)); + + return queries; + } + + protected void doAssertions(Collection records) throws Exception + { + assertNotNull(records); + assertEquals(2, records.size()); + assertEquals("domainserver.com.", records.iterator().next().getName().toString()); + + for (Record rec : records) + { + assertEquals(Type.CNAME, rec.getType()); + } + } + }.perform(); + } + + public void testQueryCNAMERecordByA_AssertNoRecordsRetrieved() throws Exception + { + new TestPlan() + { + protected void addRecords() throws Exception + { + ArrayList recs = new ArrayList(); + DnsRecord rec = DNSRecordUtil.createCNAMERecord("domainserver.com", "domain.com"); + recs.add(rec); + + rec = DNSRecordUtil.createCNAMERecord("domainserver2.com", "domain.com"); + recs.add(rec); + + rec = DNSRecordUtil.createCNAMERecord("domain2server.com", "domain2.com"); + recs.add(rec); + + proxy.addDNS(recs.toArray(new DnsRecord[recs.size()])); + + } + + protected Collection getTestQueries() throws Exception + { + Collection queries = new ArrayList(); + queries.add(new Query("domain.com", Type.A)); + + return queries; + } + + protected void doAssertions(Collection records) throws Exception + { + assertNotNull(records); + assertEquals(0, records.size()); + } + }.perform(); + } + + public void testQueryUnsupportedQueryType() throws Exception + { + new TestPlan() + { + protected void addRecords() throws Exception + { + ArrayList recs = new ArrayList(); + DnsRecord rec = DNSRecordUtil.createCNAMERecord("domainserver.com", "domain.com"); + recs.add(rec); + + proxy.addDNS(recs.toArray(new DnsRecord[recs.size()])); + } + + protected Collection getTestQueries() throws Exception + { + Collection queries = new ArrayList(); + queries.add(new Query("domain.com", Type.AAAA)); + + return queries; + } + + protected void doAssertions(Collection records) throws Exception + { + assertNotNull(records); + assertEquals(0, records.size()); + } + }.perform(); + } +} diff --git a/java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/MockDNSStore.java b/java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/MockDNSStore.java new file mode 100644 index 000000000..2fc9b457e --- /dev/null +++ b/java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/MockDNSStore.java @@ -0,0 +1,20 @@ +package org.nhindirect.dns; + +import org.xbill.DNS.Message; + +public class MockDNSStore implements DNSStore +{ + public MockDNSStore() + { + + } + + @Override + public Message get(Message dnsMsg) throws DNSException + { + //return new Message(); + return null; + } + + +} diff --git a/java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/provider/MockConfigDNSStoreProvider.java b/java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/provider/MockConfigDNSStoreProvider.java new file mode 100644 index 000000000..64b90c131 --- /dev/null +++ b/java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/provider/MockConfigDNSStoreProvider.java @@ -0,0 +1,24 @@ +package org.nhindirect.dns.provider; + +import java.net.URL; + +import org.nhindirect.dns.DNSStore; +import org.nhindirect.dns.MockDNSStore; + +public class MockConfigDNSStoreProvider extends AbstractConfigDNSStoreProvider +{ + + public MockConfigDNSStoreProvider(URL configServiceURL) + { + super(configServiceURL); + } + + /** + * {@inheritDoc} + */ + @Override + public DNSStore get() + { + return new MockDNSStore(); + } +} diff --git a/java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/provider/MockDNSStoreProvider.java b/java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/provider/MockDNSStoreProvider.java new file mode 100644 index 000000000..eb5b10570 --- /dev/null +++ b/java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/provider/MockDNSStoreProvider.java @@ -0,0 +1,18 @@ +package org.nhindirect.dns.provider; + +import org.nhindirect.dns.DNSStore; +import org.nhindirect.dns.MockDNSStore; + +import com.google.inject.Provider; + +public class MockDNSStoreProvider implements Provider +{ + /** + * {@inheritDoc} + */ + @Override + public DNSStore get() + { + return new MockDNSStore(); + } +} diff --git a/java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/service/DNSServerService_constructTest.java b/java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/service/DNSServerService_constructTest.java new file mode 100644 index 000000000..f9dd5b074 --- /dev/null +++ b/java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/service/DNSServerService_constructTest.java @@ -0,0 +1,154 @@ +package org.nhindirect.dns.service; + +import java.net.URL; + +import junit.framework.TestCase; + +import org.apache.mina.util.AvailablePortFinder; +import org.nhind.config.ConfigurationServiceProxy; +import org.nhindirect.dns.DNSServerSettings; +import org.nhindirect.dns.util.BaseTestPlan; +import org.nhindirect.dns.util.ConfigServiceRunner; + +public class DNSServerService_constructTest extends TestCase +{ + abstract class TestPlan extends BaseTestPlan + { + protected int port; + protected DNSServerService server = null; + protected ConfigurationServiceProxy proxy; + + protected void setupMocks() throws Exception + { + if (!ConfigServiceRunner.isServiceRunning()) + ConfigServiceRunner.startConfigService(); + + proxy = new ConfigurationServiceProxy(ConfigServiceRunner.getConfigServiceURL()); + + } + + @Override + protected void tearDownMocks() throws Exception + { + if (server != null) + server.stopService(); + } + + @Override + protected void performInner() throws Exception + { + port = AvailablePortFinder.getNextAvailable(1024); + DNSServerSettings settings = new DNSServerSettings(); + settings.setPort(port); + server = new DNSServerService(new URL(ConfigServiceRunner.getConfigServiceURL()), settings); + + doAssertions(); + } + + protected void doAssertions() + { + + } + } + + public void testConstructServer_assertDefaultDNSStore() throws Exception + { + new TestPlan() + { + protected void doAssertions() + { + assertEquals("org.nhindirect.dns.ConfigServiceDNSStore", server.server.getDNSStoreImplName()); + } + + }.perform(); + + } + + public void testConstructServer_nonExistantProviderClass_assertDefaultDNSStore() throws Exception + { + new TestPlan() + { + @Override + public void setupMocks() throws Exception + { + super.setupMocks(); + + System.setProperty(DNSServerService.DNS_STORE_PROVIDER_VAR, "com.cern.bogus.WhoCares"); + } + + @Override + public void tearDownMocks() throws Exception + { + System.setProperty(DNSServerService.DNS_STORE_PROVIDER_VAR, ""); + + super.tearDownMocks(); + } + + protected void doAssertions() + { + assertEquals("org.nhindirect.dns.ConfigServiceDNSStore", server.server.getDNSStoreImplName()); + } + + }.perform(); + + } + + public void testConstructServer_overriddenProviderClass_assertDNSStoreClass() throws Exception + { + new TestPlan() + { + @Override + public void setupMocks() throws Exception + { + super.setupMocks(); + + System.setProperty(DNSServerService.DNS_STORE_PROVIDER_VAR, "org.nhindirect.dns.provider.MockDNSStoreProvider"); + } + + @Override + public void tearDownMocks() throws Exception + { + System.setProperty(DNSServerService.DNS_STORE_PROVIDER_VAR, ""); + + super.tearDownMocks(); + } + + protected void doAssertions() + { + assertEquals("org.nhindirect.dns.MockDNSStore", server.server.getDNSStoreImplName()); + } + + }.perform(); + + } + + + public void testConstructServer_overriddenConfigDNSProviderClass_assertDNSStoreClass() throws Exception + { + new TestPlan() + { + @Override + public void setupMocks() throws Exception + { + super.setupMocks(); + + System.setProperty(DNSServerService.DNS_STORE_PROVIDER_VAR, "org.nhindirect.dns.provider.MockConfigDNSStoreProvider"); + } + + @Override + public void tearDownMocks() throws Exception + { + System.setProperty(DNSServerService.DNS_STORE_PROVIDER_VAR, ""); + + super.tearDownMocks(); + } + + protected void doAssertions() + { + assertEquals("org.nhindirect.dns.MockDNSStore", server.server.getDNSStoreImplName()); + } + + }.perform(); + + } +} diff --git a/java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/tools/CertCommands_addIPKIXCert_Test.java b/java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/tools/CertCommands_addIPKIXCert_Test.java new file mode 100644 index 000000000..3f95c1d1f --- /dev/null +++ b/java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/tools/CertCommands_addIPKIXCert_Test.java @@ -0,0 +1,174 @@ +package org.nhindirect.dns.tools; + +import java.net.URL; + +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; + +import junit.framework.TestCase; + +import org.apache.mina.util.AvailablePortFinder; +import org.nhind.config.Certificate; +import org.nhind.config.ConfigurationServiceProxy; +import org.nhind.config.DnsRecord; +import org.nhindirect.dns.ConfigServiceDNSStore; +import org.nhindirect.dns.DNSServer; +import org.nhindirect.dns.DNSServerSettings; +import org.nhindirect.dns.util.BaseTestPlan; +import org.nhindirect.dns.util.ConfigServiceRunner; +import org.xbill.DNS.Type; + +public class CertCommands_addIPKIXCert_Test extends TestCase +{ + abstract class TestPlan extends BaseTestPlan + { + protected CertRecordCounterPrinter recordPrinter; + + protected int port; + protected DNSServer server = null; + protected ConfigurationServiceProxy proxy; + protected CertCommands certCommands; + + @Override + protected void setupMocks() throws Exception + { + if (!ConfigServiceRunner.isServiceRunning()) + ConfigServiceRunner.startConfigService(); + + proxy = new ConfigurationServiceProxy(ConfigServiceRunner.getConfigServiceURL()); + + cleanRecords(); + + port = AvailablePortFinder.getNextAvailable(1024); + DNSServerSettings settings = new DNSServerSettings(); + settings.setPort(port); + + server = new DNSServer(new ConfigServiceDNSStore(new URL(ConfigServiceRunner.getConfigServiceURL())), settings); + + server.start(); + + recordPrinter = new CertRecordCounterPrinter(); + + certCommands = new CertCommands(proxy); + certCommands.setRecordPrinter(recordPrinter); + } + + @Override + protected void tearDownMocks() throws Exception + { + if (server != null) + server.stop(); + } + + private void cleanRecords() throws Exception + { + DnsRecord[] rec = proxy.getDNSByType(Type.ANY); + + if (rec != null && rec.length > 0) + proxy.removeDNS(rec); + + rec = proxy.getDNSByType(Type.ANY); + + assertNull(rec); + + Certificate[] certs = proxy.listCertificates(0, 1000, null); + + if (certs != null && certs.length > 0) + { + long[] ids = new long[certs.length]; + int idx = 0; + for (Certificate cert : certs) + ids[idx++] = cert.getId(); + + proxy.removeCertificates(ids); + } + + } + + @Override + protected void performInner() throws Exception + { + Map certsToAdd = getCertsToAdd(); + + + if (certsToAdd.size() > 0) + { + for (Entry certToAdd : certsToAdd.entrySet()) + certCommands.addIPKIXCert(new String[] {certToAdd.getKey(), certToAdd.getValue()}); + } + + Certificate[] importedCerts = null; + try + { + importedCerts = proxy.listCertificates(0, 100, null); + } + catch (Exception e) + { + + } + doAssertions(importedCerts); + } + + protected abstract Map getCertsToAdd() throws Exception; + + protected abstract void doAssertions(Certificate[] importedCerts) throws Exception; + } + + public void testImportPublicCert_addIPKIX_AssertRecordImported() throws Exception + { + new TestPlan() + { + + @Override + protected Map getCertsToAdd() throws Exception + { + Map retCerts = new HashMap(); + + retCerts.put("test.com", "http://test.com/test.der"); + + return retCerts; + } + + @Override + protected void doAssertions(Certificate[] importedCerts) throws Exception + { + assertEquals(1, importedCerts.length); + Certificate cert = importedCerts[0]; + assertEquals("test.com", cert.getOwner()); + assertFalse(cert.isPrivateKey()); + assertEquals("http://test.com/test.der", new String(cert.getData())); + } + }.perform(); + } + + public void testImportPublicCert_invalidProxy_AssertRecordNotImported() throws Exception + { + new TestPlan() + { + @Override + protected void setupMocks() throws Exception + { + super.setupMocks(); + proxy = new ConfigurationServiceProxy("http://boGussite.cdm"); + certCommands.setConfigurationProxy(proxy); + } + + @Override + protected Map getCertsToAdd() throws Exception + { + Map retCerts = new HashMap(); + + retCerts.put("test.com", "http://test.com/test.der"); + + return retCerts; + } + + @Override + protected void doAssertions(Certificate[] importedCerts) throws Exception + { + assertNull(importedCerts); + } + }.perform(); + } +} diff --git a/java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/tools/CertCommands_importPrivateCert_Test.java b/java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/tools/CertCommands_importPrivateCert_Test.java new file mode 100644 index 000000000..c61d3dadd --- /dev/null +++ b/java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/tools/CertCommands_importPrivateCert_Test.java @@ -0,0 +1,250 @@ +package org.nhindirect.dns.tools; + +import java.net.URL; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; + +import junit.framework.TestCase; + +import org.apache.mina.util.AvailablePortFinder; +import org.nhind.config.Certificate; +import org.nhind.config.ConfigurationServiceProxy; +import org.nhind.config.DnsRecord; +import org.nhindirect.dns.ConfigServiceDNSStore; +import org.nhindirect.dns.DNSServer; +import org.nhindirect.dns.DNSServerSettings; +import org.nhindirect.dns.util.BaseTestPlan; +import org.nhindirect.dns.util.ConfigServiceRunner; +import org.xbill.DNS.Type; + +public class CertCommands_importPrivateCert_Test extends TestCase +{ + abstract class TestPlan extends BaseTestPlan + { + protected CertRecordCounterPrinter recordPrinter; + + protected int port; + protected DNSServer server = null; + protected ConfigurationServiceProxy proxy; + protected CertCommands certCommands; + + @Override + protected void setupMocks() throws Exception + { + if (!ConfigServiceRunner.isServiceRunning()) + ConfigServiceRunner.startConfigService(); + + proxy = new ConfigurationServiceProxy(ConfigServiceRunner.getConfigServiceURL()); + + cleanRecords(); + + port = AvailablePortFinder.getNextAvailable(1024); + DNSServerSettings settings = new DNSServerSettings(); + settings.setPort(port); + + server = new DNSServer(new ConfigServiceDNSStore(new URL(ConfigServiceRunner.getConfigServiceURL())), settings); + + server.start(); + + recordPrinter = new CertRecordCounterPrinter(); + + certCommands = new CertCommands(proxy); + certCommands.setRecordPrinter(recordPrinter); + } + + @Override + protected void tearDownMocks() throws Exception + { + if (server != null) + server.stop(); + } + + private void cleanRecords() throws Exception + { + DnsRecord[] rec = proxy.getDNSByType(Type.ANY); + + if (rec != null && rec.length > 0) + proxy.removeDNS(rec); + + rec = proxy.getDNSByType(Type.ANY); + + assertNull(rec); + + Certificate[] certs = proxy.listCertificates(0, 1000, null); + + if (certs != null && certs.length > 0) + { + long[] ids = new long[certs.length]; + int idx = 0; + for (Certificate cert : certs) + ids[idx++] = cert.getId(); + + proxy.removeCertificates(ids); + } + + } + + @Override + protected void performInner() throws Exception + { + Map certsToAdd = getCertFilesToAdd(); + + + if (certsToAdd.size() > 0) + { + for (Entry certToAdd : certsToAdd.entrySet()) + { + String[] args = (certToAdd.getValue() != null) ? new String[] {certToAdd.getKey(), certToAdd.getValue()} : + new String[] {certToAdd.getKey()}; + + certCommands.importPrivateCert(args); + } + } + + Certificate[] importedCerts = null; + try + { + importedCerts = proxy.listCertificates(0, 100, null); + } + catch (Exception e) + { + + } + doAssertions(importedCerts); + } + + protected abstract Map getCertFilesToAdd() throws Exception; + + protected abstract void doAssertions(Certificate[] importedCerts) throws Exception; + } + + public void testImportPrivateCert_importFromFile_nullPassphrash_AssertRecordImported() throws Exception + { + new TestPlan() + { + + @Override + protected Map getCertFilesToAdd() throws Exception + { + Map retCerts = new HashMap(); + + retCerts.put("./src/test/resources/certs/certCheckA.p12", null); + + return retCerts; + } + + @Override + protected void doAssertions(Certificate[] importedCerts) throws Exception + { + assertEquals(1, importedCerts.length); + Certificate cert = importedCerts[0]; + assertEquals("certCheckA@sigCheck.com", cert.getOwner()); + assertTrue(cert.isPrivateKey()); + } + }.perform(); + } + + public void testImportPrivateCert_importFromFile_emptyPassphrash_AssertRecordImported() throws Exception + { + new TestPlan() + { + + @Override + protected Map getCertFilesToAdd() throws Exception + { + Map retCerts = new HashMap(); + + retCerts.put("./src/test/resources/certs/certCheckA.p12", ""); + + return retCerts; + } + + @Override + protected void doAssertions(Certificate[] importedCerts) throws Exception + { + assertEquals(1, importedCerts.length); + Certificate cert = importedCerts[0]; + assertEquals("certCheckA@sigCheck.com", cert.getOwner()); + assertTrue(cert.isPrivateKey()); + } + }.perform(); + } + + public void testImportPrivateCert_importFromFile_invalidPassphrash_AssertRecordNotImported() throws Exception + { + new TestPlan() + { + + @Override + protected Map getCertFilesToAdd() throws Exception + { + Map retCerts = new HashMap(); + + retCerts.put("./src/test/resources/certs/certCheckA.p12", "invalid"); + + return retCerts; + } + + @Override + protected void doAssertions(Certificate[] importedCerts) throws Exception + { + assertNull(importedCerts); + } + }.perform(); + } + + public void testImportPrivateCert_fileDoesNotExist__AssertRecordNotImported() throws Exception + { + new TestPlan() + { + + @Override + protected Map getCertFilesToAdd() throws Exception + { + Map retCerts = new HashMap(); + + retCerts.put("./src/test/resources/certs/gm2552doesnotexist.der", ""); + + return retCerts; + } + + @Override + protected void doAssertions(Certificate[] importedCerts) throws Exception + { + assertNull(importedCerts); + } + }.perform(); + } + + public void testImportPrivateCert_invalidProxy__AssertRecordNotImported() throws Exception + { + new TestPlan() + { + + @Override + protected void setupMocks() throws Exception + { + super.setupMocks(); + proxy = new ConfigurationServiceProxy("http://boGussite.cdm"); + certCommands.setConfigurationProxy(proxy); + } + + @Override + protected Map getCertFilesToAdd() throws Exception + { + Map retCerts = new HashMap(); + + retCerts.put("./src/test/resources/certs/certCheckA@sigCheck.com", ""); + + return retCerts; + } + + @Override + protected void doAssertions(Certificate[] importedCerts) throws Exception + { + assertNull(importedCerts); + } + }.perform(); + } +} diff --git a/java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/tools/CertCommands_importPublicCert_Test.java b/java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/tools/CertCommands_importPublicCert_Test.java new file mode 100644 index 000000000..a36670f75 --- /dev/null +++ b/java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/tools/CertCommands_importPublicCert_Test.java @@ -0,0 +1,195 @@ +package org.nhindirect.dns.tools; + +import java.net.URL; +import java.util.ArrayList; +import java.util.List; + +import org.apache.mina.util.AvailablePortFinder; +import org.nhind.config.Certificate; +import org.nhind.config.ConfigurationServiceProxy; +import org.nhind.config.DnsRecord; +import org.nhindirect.dns.ConfigServiceDNSStore; +import org.nhindirect.dns.DNSServer; +import org.nhindirect.dns.DNSServerSettings; +import org.nhindirect.dns.util.BaseTestPlan; +import org.nhindirect.dns.util.ConfigServiceRunner; +import org.xbill.DNS.Type; + +import junit.framework.TestCase; + +public class CertCommands_importPublicCert_Test extends TestCase +{ + abstract class TestPlan extends BaseTestPlan + { + protected CertRecordCounterPrinter recordPrinter; + + protected int port; + protected DNSServer server = null; + protected ConfigurationServiceProxy proxy; + protected CertCommands certCommands; + + @Override + protected void setupMocks() throws Exception + { + if (!ConfigServiceRunner.isServiceRunning()) + ConfigServiceRunner.startConfigService(); + + proxy = new ConfigurationServiceProxy(ConfigServiceRunner.getConfigServiceURL()); + + cleanRecords(); + + port = AvailablePortFinder.getNextAvailable(1024); + DNSServerSettings settings = new DNSServerSettings(); + settings.setPort(port); + + server = new DNSServer(new ConfigServiceDNSStore(new URL(ConfigServiceRunner.getConfigServiceURL())), settings); + + server.start(); + + recordPrinter = new CertRecordCounterPrinter(); + + certCommands = new CertCommands(proxy); + certCommands.setRecordPrinter(recordPrinter); + } + + @Override + protected void tearDownMocks() throws Exception + { + if (server != null) + server.stop(); + } + + private void cleanRecords() throws Exception + { + DnsRecord[] rec = proxy.getDNSByType(Type.ANY); + + if (rec != null && rec.length > 0) + proxy.removeDNS(rec); + + rec = proxy.getDNSByType(Type.ANY); + + assertNull(rec); + + Certificate[] certs = proxy.listCertificates(0, 1000, null); + + if (certs != null && certs.length > 0) + { + long[] ids = new long[certs.length]; + int idx = 0; + for (Certificate cert : certs) + ids[idx++] = cert.getId(); + + proxy.removeCertificates(ids); + } + + } + + @Override + protected void performInner() throws Exception + { + List certsToAdd = getCertFilesToAdd(); + + + if (certsToAdd.size() > 0) + { + for (String certToAdd : certsToAdd) + certCommands.importPublicCert(new String[] {certToAdd}); + } + + Certificate[] importedCerts = null; + try + { + importedCerts = proxy.listCertificates(0, 100, null); + } + catch (Exception e) + { + + } + doAssertions(importedCerts); + } + + protected abstract List getCertFilesToAdd() throws Exception; + + protected abstract void doAssertions(Certificate[] importedCerts) throws Exception; + } + + public void testImportPublicCert_importFromFile_AssertRecordImported() throws Exception + { + new TestPlan() + { + + @Override + protected List getCertFilesToAdd() throws Exception + { + List retCerts = new ArrayList(); + + retCerts.add("./src/test/resources/certs/gm2552.der"); + + return retCerts; + } + + @Override + protected void doAssertions(Certificate[] importedCerts) throws Exception + { + assertEquals(1, importedCerts.length); + Certificate cert = importedCerts[0]; + assertEquals("gm2552@securehealthemail.com", cert.getOwner()); + assertFalse(cert.isPrivateKey()); + } + }.perform(); + } + + public void testImportPublicCert_fileDoesNotExist_AssertRecordNotImported() throws Exception + { + new TestPlan() + { + + @Override + protected List getCertFilesToAdd() throws Exception + { + List retCerts = new ArrayList(); + + retCerts.add("./src/test/resources/certs/gm2552doesnotexist.der"); + + return retCerts; + } + + @Override + protected void doAssertions(Certificate[] importedCerts) throws Exception + { + assertNull(importedCerts); + } + }.perform(); + } + + public void testImportPublicCert_invalidProxy_AssertRecordNotImported() throws Exception + { + new TestPlan() + { + + @Override + protected void setupMocks() throws Exception + { + super.setupMocks(); + proxy = new ConfigurationServiceProxy("http://boGussite.cdm"); + certCommands.setConfigurationProxy(proxy); + } + + @Override + protected List getCertFilesToAdd() throws Exception + { + List retCerts = new ArrayList(); + + retCerts.add("./src/test/resources/certs/gm2552.der"); + + return retCerts; + } + + @Override + protected void doAssertions(Certificate[] importedCerts) throws Exception + { + assertNull(importedCerts); + } + }.perform(); + } +} diff --git a/java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/tools/CertCommands_listCertsByAddress_Test.java b/java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/tools/CertCommands_listCertsByAddress_Test.java new file mode 100644 index 000000000..108f7f35c --- /dev/null +++ b/java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/tools/CertCommands_listCertsByAddress_Test.java @@ -0,0 +1,329 @@ +package org.nhindirect.dns.tools; + +import java.net.URL; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.apache.mina.util.AvailablePortFinder; +import org.nhind.config.Certificate; +import org.nhind.config.ConfigurationServiceProxy; +import org.nhind.config.DnsRecord; +import org.nhind.config.EntityStatus; +import org.nhindirect.dns.ConfigServiceDNSStore; +import org.nhindirect.dns.DNSServer; +import org.nhindirect.dns.DNSServerSettings; +import org.nhindirect.dns.util.BaseTestPlan; +import org.nhindirect.dns.util.ConfigServiceRunner; +import org.xbill.DNS.Type; + +import junit.framework.TestCase; + +public class CertCommands_listCertsByAddress_Test extends TestCase +{ + abstract class TestPlan extends BaseTestPlan + { + protected CertRecordCounterPrinter recordPrinter; + + protected int port; + protected DNSServer server = null; + protected ConfigurationServiceProxy proxy; + protected CertCommands certCommands; + + @Override + protected void setupMocks() throws Exception + { + if (!ConfigServiceRunner.isServiceRunning()) + ConfigServiceRunner.startConfigService(); + + proxy = new ConfigurationServiceProxy(ConfigServiceRunner.getConfigServiceURL()); + + cleanRecords(); + + port = AvailablePortFinder.getNextAvailable(1024); + DNSServerSettings settings = new DNSServerSettings(); + settings.setPort(port); + + server = new DNSServer(new ConfigServiceDNSStore(new URL(ConfigServiceRunner.getConfigServiceURL())), settings); + + server.start(); + + recordPrinter = new CertRecordCounterPrinter(); + + certCommands = new CertCommands(proxy); + certCommands.setRecordPrinter(recordPrinter); + } + + @Override + protected void tearDownMocks() throws Exception + { + if (server != null) + server.stop(); + } + + private void cleanRecords() throws Exception + { + DnsRecord[] rec = proxy.getDNSByType(Type.ANY); + + if (rec != null && rec.length > 0) + proxy.removeDNS(rec); + + rec = proxy.getDNSByType(Type.ANY); + + assertNull(rec); + + Certificate[] certs = proxy.listCertificates(0, 1000, null); + + if (certs != null && certs.length > 0) + { + long[] ids = new long[certs.length]; + int idx = 0; + for (Certificate cert : certs) + ids[idx++] = cert.getId(); + + proxy.removeCertificates(ids); + } + + } + + @Override + protected void performInner() throws Exception + { + + + List certsToAdd = getCertRecsToAdd(); + + + if (certsToAdd.size() > 0) + { + + proxy.addCertificates(certsToAdd.toArray(new Certificate[certsToAdd.size()])); + } + + List adds = getAddressesToSearch(); + if (adds != null && adds.size() != 0) + for (String add : adds) + certCommands.listCertsByAddress(new String[] {add}); + + doAssertions(); + } + + protected abstract List getCertRecsToAdd() throws Exception; + + protected abstract List getAddressesToSearch() throws Exception; + + protected abstract void doAssertions() throws Exception; + } + + public void testListCertsByAddress_noCertsInStore_AssertNoRecordsFetched() throws Exception + { + new TestPlan() + { + @Override + protected List getAddressesToSearch() throws Exception + { + List retVal = new ArrayList(); + + retVal.add("test.com"); + + return retVal; + } + + @Override + protected List getCertRecsToAdd() throws Exception + { + List retCerts = new ArrayList(); + + return retCerts; + } + + @Override + protected void doAssertions() throws Exception + { + assertEquals(0, recordPrinter.getRecordCount()); + } + }.perform(); + } + + + public void testListCertsByAddress_singleCertInStore_AssertRecordsFetched() throws Exception + { + new TestPlan() + { + @Override + protected List getAddressesToSearch() throws Exception + { + List retVal = new ArrayList(); + + retVal.add("test.com"); + + return retVal; + } + + @Override + protected List getCertRecsToAdd() throws Exception + { + List retCerts = new ArrayList(); + + Certificate cert = new Certificate(); + cert.setOwner("test.com"); + cert.setData("http://localhost/test.der".getBytes()); + cert.setStatus(EntityStatus.ENABLED); + + retCerts.add(cert); + + return retCerts; + } + + @Override + protected void doAssertions() throws Exception + { + assertEquals(1, recordPrinter.getRecordCount()); + + final Collection printedRecords = recordPrinter.getPrintedRecords(); + Certificate cert = printedRecords.iterator().next(); + assertEquals("test.com", cert.getOwner()); + } + }.perform(); + } + + public void testListCertsByAddress_singleCertInStore_multipleSearches_AssertSingleRecordsFetched() throws Exception + { + new TestPlan() + { + @Override + protected List getAddressesToSearch() throws Exception + { + List retVal = new ArrayList(); + + retVal.add("test.com"); + retVal.add("test2.com"); + + return retVal; + } + + @Override + protected List getCertRecsToAdd() throws Exception + { + List retCerts = new ArrayList(); + + Certificate cert = new Certificate(); + cert.setOwner("test.com"); + cert.setData("http://localhost/test.der".getBytes()); + cert.setStatus(EntityStatus.ENABLED); + + retCerts.add(cert); + + return retCerts; + } + + @Override + protected void doAssertions() throws Exception + { + assertEquals(1, recordPrinter.getRecordCount()); + + final Collection printedRecords = recordPrinter.getPrintedRecords(); + Certificate cert = printedRecords.iterator().next(); + assertEquals("test.com", cert.getOwner()); + } + }.perform(); + } + + public void testListCertsByAddress_mutipleCertsInStore_multipleSearches_AssertRecordsFetched() throws Exception + { + new TestPlan() + { + @Override + protected List getAddressesToSearch() throws Exception + { + List retVal = new ArrayList(); + + retVal.add("test1.com"); + retVal.add("test2.com"); + + return retVal; + } + + @Override + protected List getCertRecsToAdd() throws Exception + { + List retCerts = new ArrayList(); + + Certificate cert = new Certificate(); + cert.setOwner("test1.com"); + cert.setData("http://localhost/test1.der".getBytes()); + cert.setStatus(EntityStatus.ENABLED); + + retCerts.add(cert); + + cert = new Certificate(); + cert.setOwner("test2.com"); + cert.setData("http://localhost/test2.der".getBytes()); + cert.setStatus(EntityStatus.ENABLED); + + retCerts.add(cert); + + return retCerts; + } + + @Override + protected void doAssertions() throws Exception + { + assertEquals(2, recordPrinter.getRecordCount()); + + boolean test1Found = false; + boolean test2Found = false; + for (Certificate cert : recordPrinter.getPrintedRecords()) + { + if (cert.getOwner().equals("test1.com")) + test1Found = true; + else if (cert.getOwner().equals("test2.com")) + test2Found = true; + } + + assertTrue(test1Found); + assertTrue(test2Found); + } + }.perform(); + } + + public void testListCertsByAddress_invalidProxy_AssertNoRecordsFetched() throws Exception + { + new TestPlan() + { + @Override + protected void setupMocks() throws Exception + { + super.setupMocks(); + proxy = new ConfigurationServiceProxy("http://boGussite.cdm"); + certCommands.setConfigurationProxy(proxy); + } + + @Override + protected List getAddressesToSearch() throws Exception + { + List retVal = new ArrayList(); + + retVal.add("test.com"); + + return retVal; + } + + @Override + protected List getCertRecsToAdd() throws Exception + { + List retCerts = new ArrayList(); + + + + return retCerts; + } + + @Override + protected void doAssertions() throws Exception + { + assertEquals(0, recordPrinter.getRecordCount()); + } + }.perform(); + } +} diff --git a/java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/tools/CertCommands_listCerts_Test.java b/java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/tools/CertCommands_listCerts_Test.java new file mode 100644 index 000000000..7b12401e9 --- /dev/null +++ b/java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/tools/CertCommands_listCerts_Test.java @@ -0,0 +1,247 @@ +package org.nhindirect.dns.tools; + + +import java.net.URL; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.apache.mina.util.AvailablePortFinder; +import org.nhind.config.Certificate; +import org.nhind.config.ConfigurationServiceProxy; +import org.nhind.config.DnsRecord; +import org.nhind.config.EntityStatus; +import org.nhindirect.dns.ConfigServiceDNSStore; +import org.nhindirect.dns.DNSServer; +import org.nhindirect.dns.DNSServerSettings; + +import org.nhindirect.dns.util.BaseTestPlan; +import org.nhindirect.dns.util.ConfigServiceRunner; + +import org.xbill.DNS.Type; + +import junit.framework.TestCase; + +public class CertCommands_listCerts_Test extends TestCase +{ + abstract class TestPlan extends BaseTestPlan + { + protected CertRecordCounterPrinter recordPrinter; + + protected int port; + protected DNSServer server = null; + protected ConfigurationServiceProxy proxy; + protected CertCommands certCommands; + + @Override + protected void setupMocks() throws Exception + { + if (!ConfigServiceRunner.isServiceRunning()) + ConfigServiceRunner.startConfigService(); + + proxy = new ConfigurationServiceProxy(ConfigServiceRunner.getConfigServiceURL()); + + cleanRecords(); + + port = AvailablePortFinder.getNextAvailable(1024); + DNSServerSettings settings = new DNSServerSettings(); + settings.setPort(port); + + server = new DNSServer(new ConfigServiceDNSStore(new URL(ConfigServiceRunner.getConfigServiceURL())), settings); + + server.start(); + + recordPrinter = new CertRecordCounterPrinter(); + + certCommands = new CertCommands(proxy); + certCommands.setRecordPrinter(recordPrinter); + } + + @Override + protected void tearDownMocks() throws Exception + { + if (server != null) + server.stop(); + } + + private void cleanRecords() throws Exception + { + DnsRecord[] rec = proxy.getDNSByType(Type.ANY); + + if (rec != null && rec.length > 0) + proxy.removeDNS(rec); + + rec = proxy.getDNSByType(Type.ANY); + + assertNull(rec); + + Certificate[] certs = proxy.listCertificates(0, 1000, null); + + if (certs != null && certs.length > 0) + { + long[] ids = new long[certs.length]; + int idx = 0; + for (Certificate cert : certs) + ids[idx++] = cert.getId(); + + proxy.removeCertificates(ids); + } + + } + + @Override + protected void performInner() throws Exception + { + + + List certsToAdd = getCertRecsToAdd(); + + + if (certsToAdd.size() > 0) + { + + proxy.addCertificates(certsToAdd.toArray(new Certificate[certsToAdd.size()])); + } + + certCommands.listCerts(new String[] {}); + + doAssertions(); + } + + protected abstract List getCertRecsToAdd() throws Exception; + + protected abstract void doAssertions() throws Exception; + } + + public void testGetRecords_noCertsInStore_AssertNoRecordFetched() throws Exception + { + new TestPlan() + { + + @Override + protected List getCertRecsToAdd() throws Exception + { + List retCerts = new ArrayList(); + + return retCerts; + } + + @Override + protected void doAssertions() throws Exception + { + assertEquals(0, recordPrinter.getRecordCount()); + } + }.perform(); + } + + public void testGetRecords_singleCertInStore_AssertRecordFetched() throws Exception + { + new TestPlan() + { + + @Override + protected List getCertRecsToAdd() throws Exception + { + List retCerts = new ArrayList(); + + Certificate cert = new Certificate(); + cert.setOwner("test.com"); + cert.setData("http://localhost/test.der".getBytes()); + cert.setStatus(EntityStatus.ENABLED); + + retCerts.add(cert); + + return retCerts; + } + + @Override + protected void doAssertions() throws Exception + { + assertEquals(1, recordPrinter.getRecordCount()); + + final Collection printedRecords = recordPrinter.getPrintedRecords(); + Certificate cert = printedRecords.iterator().next(); + assertEquals("test.com", cert.getOwner()); + } + }.perform(); + } + + public void testGetRecords_multipleCertInStore_AssertRecordFetched() throws Exception + { + new TestPlan() + { + + @Override + protected List getCertRecsToAdd() throws Exception + { + List retCerts = new ArrayList(); + + Certificate cert = new Certificate(); + cert.setOwner("test1.com"); + cert.setData("http://localhost/test1.der".getBytes()); + cert.setStatus(EntityStatus.ENABLED); + + retCerts.add(cert); + + cert = new Certificate(); + cert.setOwner("test2.com"); + cert.setData("http://localhost/test2.der".getBytes()); + cert.setStatus(EntityStatus.ENABLED); + + retCerts.add(cert); + + return retCerts; + } + + @Override + protected void doAssertions() throws Exception + { + assertEquals(2, recordPrinter.getRecordCount()); + + boolean test1Found = false; + boolean test2Found = false; + for (Certificate cert : recordPrinter.getPrintedRecords()) + { + if (cert.getOwner().equals("test1.com")) + test1Found = true; + else if (cert.getOwner().equals("test2.com")) + test2Found = true; + } + + assertTrue(test1Found); + assertTrue(test2Found); + } + }.perform(); + } + + public void testGetRecords_invalidProxy_AssertNoRecordFetched() throws Exception + { + new TestPlan() + { + + @Override + protected void setupMocks() throws Exception + { + super.setupMocks(); + proxy = new ConfigurationServiceProxy("http://boGussite.cdm"); + certCommands.setConfigurationProxy(proxy); + } + + @Override + protected List getCertRecsToAdd() throws Exception + { + List retCerts = new ArrayList(); + + + return retCerts; + } + + @Override + protected void doAssertions() throws Exception + { + assertEquals(0, recordPrinter.getRecordCount()); + + } + }.perform(); + } +} diff --git a/java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/tools/CertCommands_removeCert_Test.java b/java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/tools/CertCommands_removeCert_Test.java new file mode 100644 index 000000000..9d31dbecf --- /dev/null +++ b/java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/tools/CertCommands_removeCert_Test.java @@ -0,0 +1,248 @@ +package org.nhindirect.dns.tools; + +import java.net.URL; +import java.util.ArrayList; +import java.util.List; + +import junit.framework.TestCase; + +import org.apache.mina.util.AvailablePortFinder; +import org.nhind.config.Certificate; +import org.nhind.config.ConfigurationServiceProxy; +import org.nhind.config.DnsRecord; +import org.nhind.config.EntityStatus; +import org.nhindirect.dns.ConfigServiceDNSStore; +import org.nhindirect.dns.DNSServer; +import org.nhindirect.dns.DNSServerSettings; +import org.nhindirect.dns.util.BaseTestPlan; +import org.nhindirect.dns.util.ConfigServiceRunner; +import org.xbill.DNS.Type; + +public class CertCommands_removeCert_Test extends TestCase +{ + abstract class TestPlan extends BaseTestPlan + { + protected CertRecordCounterPrinter recordPrinter; + + protected int port; + protected DNSServer server = null; + protected ConfigurationServiceProxy proxy; + protected CertCommands certCommands; + + @Override + protected void setupMocks() throws Exception + { + if (!ConfigServiceRunner.isServiceRunning()) + ConfigServiceRunner.startConfigService(); + + proxy = new ConfigurationServiceProxy(ConfigServiceRunner.getConfigServiceURL()); + + cleanRecords(); + + port = AvailablePortFinder.getNextAvailable(1024); + DNSServerSettings settings = new DNSServerSettings(); + settings.setPort(port); + + server = new DNSServer(new ConfigServiceDNSStore(new URL(ConfigServiceRunner.getConfigServiceURL())), settings); + + server.start(); + + recordPrinter = new CertRecordCounterPrinter(); + + certCommands = new CertCommands(proxy); + certCommands.setRecordPrinter(recordPrinter); + } + + @Override + protected void tearDownMocks() throws Exception + { + if (server != null) + server.stop(); + } + + private void cleanRecords() throws Exception + { + DnsRecord[] rec = proxy.getDNSByType(Type.ANY); + + if (rec != null && rec.length > 0) + proxy.removeDNS(rec); + + rec = proxy.getDNSByType(Type.ANY); + + assertNull(rec); + + Certificate[] certs = proxy.listCertificates(0, 1000, null); + + if (certs != null && certs.length > 0) + { + long[] ids = new long[certs.length]; + int idx = 0; + for (Certificate cert : certs) + ids[idx++] = cert.getId(); + + proxy.removeCertificates(ids); + } + + } + + @Override + protected void performInner() throws Exception + { + List certsToAdd = getCertsToAdd(); + + try + { + if (certsToAdd.size() > 0) + { + proxy.addCertificates(certsToAdd.toArray(new Certificate[certsToAdd.size()])); + } + } + catch (Exception e) + { + + } + + List certsToRemove = getCertOwnersToRemove(); + for (String certToRemove : certsToRemove) + { + certCommands.removeCert(new String[] {certToRemove}); + } + + Certificate[] importedCerts = null; + try + { + importedCerts = proxy.listCertificates(0, 100, null); + } + catch (Exception e) + { + + } + doAssertions(importedCerts); + } + + protected abstract List getCertsToAdd() throws Exception; + + protected abstract List getCertOwnersToRemove() throws Exception; + + protected abstract void doAssertions(Certificate[] importedCerts) throws Exception; + } + + public void testRemoveCert_ownerExists_AssertRecordRemoved() throws Exception + { + new TestPlan() + { + + @Override + protected List getCertsToAdd() throws Exception + { + List retCerts = new ArrayList(); + + Certificate cert = new Certificate(); + cert.setOwner("test.com"); + cert.setData("http://localhost/test.der".getBytes()); + cert.setStatus(EntityStatus.ENABLED); + + retCerts.add(cert); + + return retCerts; + } + + protected List getCertOwnersToRemove() throws Exception + { + List certsToRemove = new ArrayList(); + certsToRemove.add("test.com"); + + return certsToRemove; + } + + @Override + protected void doAssertions(Certificate[] importedCerts) throws Exception + { + assertNull(importedCerts); + } + }.perform(); + } + + public void testRemoveCert_ownerDoesNotExists_AssertRecordNotRemoved() throws Exception + { + new TestPlan() + { + + @Override + protected List getCertsToAdd() throws Exception + { + List retCerts = new ArrayList(); + + Certificate cert = new Certificate(); + cert.setOwner("test.com"); + cert.setData("http://localhost/test.der".getBytes()); + cert.setStatus(EntityStatus.ENABLED); + + retCerts.add(cert); + + return retCerts; + } + + protected List getCertOwnersToRemove() throws Exception + { + List certsToRemove = new ArrayList(); + certsToRemove.add("test2.com"); + + return certsToRemove; + } + + @Override + protected void doAssertions(Certificate[] importedCerts) throws Exception + { + assertEquals(1, importedCerts.length); + + Certificate cert = importedCerts[0]; + assertEquals("test.com", cert.getOwner()); + } + }.perform(); + } + + public void testRemoveCert_invalidProxy_AssertRecordNotRemoved() throws Exception + { + new TestPlan() + { + + @Override + protected void setupMocks() throws Exception + { + super.setupMocks(); + proxy = new ConfigurationServiceProxy("http://boGussite.cdm"); + certCommands.setConfigurationProxy(proxy); + } + + @Override + protected List getCertsToAdd() throws Exception + { + List retCerts = new ArrayList(); + + Certificate cert = new Certificate(); + cert.setOwner("test.com"); + cert.setData("http://localhost/test.der".getBytes()); + cert.setStatus(EntityStatus.ENABLED); + + retCerts.add(cert); + + return retCerts; + } + + protected List getCertOwnersToRemove() throws Exception + { + List certsToRemove = new ArrayList(); + certsToRemove.add("test2.com"); + + return certsToRemove; + } + + @Override + protected void doAssertions(Certificate[] importedCerts) throws Exception + { + assertNull(importedCerts); + } + }.perform(); + } +} diff --git a/java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/tools/CertRecordCounterPrinter.java b/java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/tools/CertRecordCounterPrinter.java new file mode 100644 index 000000000..4c0bca50d --- /dev/null +++ b/java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/tools/CertRecordCounterPrinter.java @@ -0,0 +1,36 @@ +package org.nhindirect.dns.tools; + +import java.util.ArrayList; +import java.util.Collection; + +import org.nhind.config.Certificate; +import org.nhindirect.dns.tools.printers.CertRecordPrinter; + +public class CertRecordCounterPrinter extends CertRecordPrinter +{ + public Collection printedRecords = new ArrayList(); + + protected int recordCount; + + public CertRecordCounterPrinter() + { + super(); + } + + protected void printRecordInternal(Certificate record) + { + ++recordCount; + printedRecords.add(record); + super.printRecordInternal(record); + } + + public int getRecordCount() + { + return recordCount; + } + + public Collection getPrintedRecords() + { + return printedRecords; + } +} diff --git a/java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/tools/DNSManager_functional_Test.java b/java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/tools/DNSManager_functional_Test.java new file mode 100644 index 000000000..de8363adf --- /dev/null +++ b/java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/tools/DNSManager_functional_Test.java @@ -0,0 +1,615 @@ +package org.nhindirect.dns.tools; + +import java.net.URL; +import java.util.ArrayList; +import java.util.Collection; + +import org.apache.mina.util.AvailablePortFinder; +import org.nhind.config.Certificate; +import org.nhind.config.ConfigurationServiceProxy; +import org.nhind.config.DnsRecord; +import org.nhindirect.dns.ConfigServiceDNSStore; +import org.nhindirect.dns.DNSServer; +import org.nhindirect.dns.DNSServerSettings; +import org.nhindirect.dns.tools.DNSManager; +import org.nhindirect.dns.util.BaseTestPlan; +import org.nhindirect.dns.util.ConfigServiceRunner; +import org.xbill.DNS.ARecord; +import org.xbill.DNS.DClass; +import org.xbill.DNS.MXRecord; +import org.xbill.DNS.Name; +import org.xbill.DNS.Record; +import org.xbill.DNS.SOARecord; +import org.xbill.DNS.Type; + +import junit.framework.TestCase; + +public class DNSManager_functional_Test extends TestCase +{ + abstract class TestPlan extends BaseTestPlan + { + protected static final int ERROR_FUNCTION_FAILED = 0x7877; // bogus error code for our test purposes + + protected int port; + protected DNSServer server = null; + protected ConfigurationServiceProxy proxy; + + protected Record toRecord(DnsRecord rec) throws Exception + { + + return Record.newRecord(Name.fromString(rec.getName()), rec.getType(), rec.getDclass(), rec.getTtl(), rec.getData()); + } + + @Override + protected void setupMocks() throws Exception + { + if (!ConfigServiceRunner.isServiceRunning()) + ConfigServiceRunner.startConfigService(); + + proxy = new ConfigurationServiceProxy(ConfigServiceRunner.getConfigServiceURL()); + + cleanRecords(); + + port = AvailablePortFinder.getNextAvailable(1024); + DNSServerSettings settings = new DNSServerSettings(); + settings.setPort(port); + + server = new DNSServer(new ConfigServiceDNSStore(new URL(ConfigServiceRunner.getConfigServiceURL())), settings); + + server.start(); + DNSManager.setExitOnEndCommands(false); + } + + @Override + protected void performInner() throws Exception + { + Collection commands = getCommands(); + + + for (String[] commandSet : commands) + { + + String[] args = new String[commandSet.length + 2]; + args[0] = "configurl"; + args[1] = ConfigServiceRunner.getConfigServiceURL(); + System.arraycopy(commandSet, 0, args, 2, commandSet.length); + DNSManager.main(args); + } + + doAssertions(); + } + + @Override + protected void tearDownMocks() throws Exception + { + if (server != null) + server.stop(); + } + + private void cleanRecords() throws Exception + { + DnsRecord[] rec = proxy.getDNSByType(Type.ANY); + + if (rec != null && rec.length > 0) + proxy.removeDNS(rec); + + rec = proxy.getDNSByType(Type.ANY); + + assertNull(rec); + + + Certificate[] certs = proxy.getCertificatesForOwner(null, null); + if (certs != null && certs.length > 0) + { + long[] ids = new long[certs.length]; + int cnt = 0; + for (Certificate cert : certs) + ids[cnt++] = cert.getId(); + + proxy.removeCertificates(ids); + } + + certs = proxy.getCertificatesForOwner("", null); + + assertNull(certs); + } + + protected abstract Collection getCommands() throws Exception; + + protected abstract void doAssertions() throws Exception; + } + + public void testAddAName_AssertRecordAdded() throws Exception + { + new TestPlan() + { + @Override + protected Collection getCommands() throws Exception + { + Collection commands = new ArrayList(); + + String[] command = new String[] {"dns_aname_add", "example.domain.com", "127.0.0.1", "3600"}; + commands.add(command); + + return commands; + } + + @Override + protected void doAssertions() throws Exception + { + DnsRecord[] records = proxy.getDNSByType(Type.A); + assertNotNull(records); + assertEquals(1, records.length); + + DnsRecord rec = records[0]; + assertEquals("example.domain.com.", rec.getName()); + assertEquals(3600, rec.getTtl()); + assertEquals(Type.A, rec.getType()); + assertEquals(DClass.IN, rec.getDclass()); + + ARecord aRec = (ARecord)toRecord(rec); + assertEquals(aRec.getAddress().getHostAddress(), "127.0.0.1"); + + } + + }.perform(); + } + + public void testAddDupAName_AssertOneEntry() throws Exception + { + new TestPlan() + { + @Override + protected Collection getCommands() throws Exception + { + Collection commands = new ArrayList(); + + String[] command = new String[] {"dns_aname_add", "example.domain.com", "127.0.0.1", "3600"}; + commands.add(command); + command = new String[] {"dns_aname_add", "example.domain.com", "127.0.0.1", "3600"}; + commands.add(command); + + return commands; + } + + @Override + protected void doAssertions() throws Exception + { + // make sure the first command worked + DnsRecord[] records = proxy.getDNSByType(Type.A); + assertNotNull(records); + assertEquals(1, records.length); + + DnsRecord rec = records[0]; + assertEquals("example.domain.com.", rec.getName()); + assertEquals(3600, rec.getTtl()); + assertEquals(Type.A, rec.getType()); + assertEquals(DClass.IN, rec.getDclass()); + + ARecord aRec = (ARecord)toRecord(rec); + assertEquals(aRec.getAddress().getHostAddress(), "127.0.0.1"); + } + + }.perform(); + } + + public void testAddANameEnsure_AssertRecordAdded() throws Exception + { + new TestPlan() + { + @Override + protected Collection getCommands() throws Exception + { + Collection commands = new ArrayList(); + + String[] command = new String[] {"dns_aname_ensure", "example.domain.com", "127.0.0.1", "3600"}; + commands.add(command); + + return commands; + } + + @Override + protected void doAssertions() throws Exception + { + DnsRecord[] records = proxy.getDNSByType(Type.A); + assertNotNull(records); + assertEquals(1, records.length); + + DnsRecord rec = records[0]; + assertEquals("example.domain.com.", rec.getName()); + assertEquals(3600, rec.getTtl()); + assertEquals(Type.A, rec.getType()); + assertEquals(DClass.IN, rec.getDclass()); + + ARecord aRec = (ARecord)toRecord(rec); + assertEquals(aRec.getAddress().getHostAddress(), "127.0.0.1"); + + } + + }.perform(); + } + + public void testDupANameEnsure_AssertOneEntry() throws Exception + { + new TestPlan() + { + @Override + protected Collection getCommands() throws Exception + { + Collection commands = new ArrayList(); + + String[] command = new String[] {"dns_aname_ensure", "example.domain.com", "127.0.0.1", "3600"}; + commands.add(command); + command = new String[] {"dns_aname_ensure", "example.domain.com", "127.0.0.1", "3600"}; + commands.add(command); + + return commands; + } + + @Override + protected void doAssertions() throws Exception + { + // make sure first command worked + DnsRecord[] records = proxy.getDNSByType(Type.A); + assertNotNull(records); + assertEquals(1, records.length); + + DnsRecord rec = records[0]; + assertEquals("example.domain.com.", rec.getName()); + assertEquals(3600, rec.getTtl()); + assertEquals(Type.A, rec.getType()); + assertEquals(DClass.IN, rec.getDclass()); + + ARecord aRec = (ARecord)toRecord(rec); + assertEquals(aRec.getAddress().getHostAddress(), "127.0.0.1"); + } + + }.perform(); + } + + + public void testAddSOA_AssertRecordAdded() throws Exception + { + new TestPlan() + { + @Override + protected Collection getCommands() throws Exception + { + Collection commands = new ArrayList(); + + String[] command = new String[] {"dns_soa_add", "example.com", "ns1.example.com", "gm2552@example.com", "1", "3600"}; + commands.add(command); + + return commands; + } + + @Override + protected void doAssertions() throws Exception + { + DnsRecord[] records = proxy.getDNSByType(Type.SOA); + assertNotNull(records); + assertEquals(1, records.length); + + DnsRecord rec = records[0]; + assertEquals("example.com.", rec.getName()); + assertEquals(3600, rec.getTtl()); + assertEquals(Type.SOA, rec.getType()); + + SOARecord soaRec = (SOARecord)toRecord(rec); + assertEquals(soaRec.getAdmin().toString(), "gm2552\\@example.com."); + + assertEquals(soaRec.getDClass(), DClass.IN); + assertEquals(soaRec.getHost().toString(), "ns1.example.com."); + assertEquals(soaRec.getName().toString(), "example.com."); + assertEquals(soaRec.getTTL(), 3600); + assertEquals(soaRec.getSerial(), 1); + } + + }.perform(); + } + + public void testAddDupSOA_AssertOneEntry() throws Exception + { + new TestPlan() + { + @Override + protected Collection getCommands() throws Exception + { + Collection commands = new ArrayList(); + + String[] command = new String[] {"dns_soa_add", "example.com", "ns1.example.com", "gm2552@example.com", "1", "3600"}; + commands.add(command); + command = new String[] {"dns_soa_add", "example.com", "ns1.example.com", "gm2552@example.com", "1", "3600"}; + commands.add(command); + + return commands; + } + + @Override + protected void doAssertions() throws Exception + { + DnsRecord[] records = proxy.getDNSByType(Type.SOA); + assertNotNull(records); + assertEquals(1, records.length); + + DnsRecord rec = records[0]; + assertEquals("example.com.", rec.getName()); + assertEquals(3600, rec.getTtl()); + assertEquals(Type.SOA, rec.getType()); + + SOARecord soaRec = (SOARecord)toRecord(rec); + assertEquals(soaRec.getAdmin().toString(), "gm2552\\@example.com."); + + assertEquals(soaRec.getDClass(), DClass.IN); + assertEquals(soaRec.getHost().toString(), "ns1.example.com."); + assertEquals(soaRec.getName().toString(), "example.com."); + assertEquals(soaRec.getTTL(), 3600); + assertEquals(soaRec.getSerial(), 1); + } + + }.perform(); + } + + public void testAddSOAEnsure_AssertRecordAdded() throws Exception + { + new TestPlan() + { + @Override + protected Collection getCommands() throws Exception + { + Collection commands = new ArrayList(); + + String[] command = new String[] {"dns_soa_ensure", "example.com", "ns1.example.com", "gm2552@example.com", "1", "3600"}; + commands.add(command); + + return commands; + } + + @Override + protected void doAssertions() throws Exception + { + DnsRecord[] records = proxy.getDNSByType(Type.SOA); + assertNotNull(records); + assertEquals(1, records.length); + + DnsRecord rec = records[0]; + assertEquals("example.com.", rec.getName()); + assertEquals(3600, rec.getTtl()); + assertEquals(Type.SOA, rec.getType()); + + SOARecord soaRec = (SOARecord)toRecord(rec); + assertEquals(soaRec.getAdmin().toString(), "gm2552\\@example.com."); + + assertEquals(soaRec.getDClass(), DClass.IN); + assertEquals(soaRec.getHost().toString(), "ns1.example.com."); + assertEquals(soaRec.getName().toString(), "example.com."); + assertEquals(soaRec.getTTL(), 3600); + assertEquals(soaRec.getSerial(), 1); + + } + + }.perform(); + } + + public void testDupSOAEnsure_AssertOneEntry() throws Exception + { + new TestPlan() + { + @Override + protected Collection getCommands() throws Exception + { + Collection commands = new ArrayList(); + + String[] command = new String[] {"dns_soa_ensure", "example.com", "ns1.example.com", "gm2552@example.com", "1", "3600"}; + commands.add(command); + command = new String[] {"dns_soa_ensure", "example.com", "ns1.example.com", "gm2552@example.com", "1", "3600"}; + commands.add(command); + + return commands; + } + + @Override + protected void doAssertions() throws Exception + { + DnsRecord[] records = proxy.getDNSByType(Type.SOA); + assertNotNull(records); + assertEquals(1, records.length); + + DnsRecord rec = records[0]; + assertEquals("example.com.", rec.getName()); + assertEquals(3600, rec.getTtl()); + assertEquals(Type.SOA, rec.getType()); + + SOARecord soaRec = (SOARecord)toRecord(rec); + assertEquals(soaRec.getAdmin().toString(), "gm2552\\@example.com."); + + assertEquals(soaRec.getDClass(), DClass.IN); + assertEquals(soaRec.getHost().toString(), "ns1.example.com."); + assertEquals(soaRec.getName().toString(), "example.com."); + assertEquals(soaRec.getTTL(), 3600); + assertEquals(soaRec.getSerial(), 1); + } + + }.perform(); + } + + public void testAddMX_AssertRecordAdded() throws Exception + { + new TestPlan() + { + @Override + protected Collection getCommands() throws Exception + { + Collection commands = new ArrayList(); + + String[] command = new String[] {"dns_mx_add", "domain.com", "mail1.domain.com", "3600", "1"}; + commands.add(command); + + return commands; + } + + @Override + protected void doAssertions() throws Exception + { + DnsRecord[] records = proxy.getDNSByType(Type.MX); + assertNotNull(records); + assertEquals(1, records.length); + + DnsRecord rec = records[0]; + assertEquals("domain.com.", rec.getName()); + assertEquals(3600, rec.getTtl()); + assertEquals(Type.MX, rec.getType()); + assertEquals(DClass.IN, rec.getDclass()); + + MXRecord mxRec = (MXRecord)toRecord(rec); + assertEquals(mxRec.getTarget().toString(), "mail1.domain.com."); + + } + + }.perform(); + } + + public void testAddDupMX_AssertOneEntry() throws Exception + { + new TestPlan() + { + @Override + protected Collection getCommands() throws Exception + { + Collection commands = new ArrayList(); + + String[] command = new String[] {"dns_mx_add", "domain.com", "mail1.domain.com", "3600", "1"}; + commands.add(command); + command = new String[] {"dns_mx_add", "domain.com", "mail1.domain.com", "3600", "1"}; + commands.add(command); + + return commands; + } + + @Override + protected void doAssertions() throws Exception + { + DnsRecord[] records = proxy.getDNSByType(Type.MX); + assertNotNull(records); + assertEquals(1, records.length); + + DnsRecord rec = records[0]; + assertEquals("domain.com.", rec.getName()); + assertEquals(3600, rec.getTtl()); + assertEquals(Type.MX, rec.getType()); + assertEquals(DClass.IN, rec.getDclass()); + + MXRecord mxRec = (MXRecord)toRecord(rec); + assertEquals(mxRec.getTarget().toString(), "mail1.domain.com."); + } + + }.perform(); + } + + public void testAddMXEnsure_AssertRecordAdded() throws Exception + { + new TestPlan() + { + @Override + protected Collection getCommands() throws Exception + { + Collection commands = new ArrayList(); + + String[] command = new String[] {"dns_mx_ensure", "domain.com", "mail1.domain.com", "3600", "1"}; + commands.add(command); + + return commands; + } + + @Override + protected void doAssertions() throws Exception + { + DnsRecord[] records = proxy.getDNSByType(Type.MX); + assertNotNull(records); + assertEquals(1, records.length); + + DnsRecord rec = records[0]; + assertEquals("domain.com.", rec.getName()); + assertEquals(3600, rec.getTtl()); + assertEquals(Type.MX, rec.getType()); + assertEquals(DClass.IN, rec.getDclass()); + + MXRecord mxRec = (MXRecord)toRecord(rec); + assertEquals(mxRec.getTarget().toString(), "mail1.domain.com."); + + } + + }.perform(); + } + + public void testDupMXEnsure_AssertOneEntry() throws Exception + { + new TestPlan() + { + @Override + protected Collection getCommands() throws Exception + { + Collection commands = new ArrayList(); + + String[] command = new String[] {"dns_mx_ensure", "domain.com", "mail1.domain.com", "3600", "1"}; + commands.add(command); + command = new String[] {"dns_mx_ensure", "domain.com", "mail1.domain.com", "3600", "1"}; + commands.add(command); + + return commands; + } + + @Override + protected void doAssertions() throws Exception + { + DnsRecord[] records = proxy.getDNSByType(Type.MX); + assertNotNull(records); + assertEquals(1, records.length); + + DnsRecord rec = records[0]; + assertEquals("domain.com.", rec.getName()); + assertEquals(3600, rec.getTtl()); + assertEquals(Type.MX, rec.getType()); + assertEquals(DClass.IN, rec.getDclass()); + + MXRecord mxRec = (MXRecord)toRecord(rec); + assertEquals(mxRec.getTarget().toString(), "mail1.domain.com."); + } + + }.perform(); + } + + + public void testGetAll_AssertAllRecords() throws Exception + { + new TestPlan() + { + @Override + protected Collection getCommands() throws Exception + { + Collection commands = new ArrayList(); + + String[] command = new String[] {"dns_mx_ensure", "domain.com", "mail1.domain.com", "3600", "1"}; + commands.add(command); + command = new String[] {"dns_soa_ensure", "domain.com", "ns1.domain.com", "gm2552@domain.com", "1", "3600"}; + commands.add(command); + command = new String[] {"dns_aname_ensure", "ns1.domain.com", "10.45.110.23", "3600"}; + commands.add(command); + command = new String[] {"dns_get_all"}; + commands.add(command); + + return commands; + } + + @Override + protected void doAssertions() throws Exception + { + DnsRecord[] records = proxy.getDNSByType(Type.ANY); + assertNotNull(records); + assertEquals(3, records.length); + } + + }.perform(); + } +} + diff --git a/java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/tools/DNSRecordCommands_addARecords_Test.java b/java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/tools/DNSRecordCommands_addARecords_Test.java new file mode 100644 index 000000000..47d4f8a94 --- /dev/null +++ b/java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/tools/DNSRecordCommands_addARecords_Test.java @@ -0,0 +1,277 @@ +package org.nhindirect.dns.tools; + +import java.net.InetAddress; +import java.net.URL; +import java.rmi.RemoteException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import junit.framework.TestCase; + +import org.apache.mina.util.AvailablePortFinder; +import org.nhind.config.ConfigurationServiceProxy; +import org.nhind.config.DnsRecord; +import org.nhindirect.dns.ConfigServiceDNSStore; +import org.nhindirect.dns.DNSServer; +import org.nhindirect.dns.DNSServerSettings; +import org.nhindirect.dns.util.BaseTestPlan; +import org.nhindirect.dns.util.ConfigServiceRunner; +import org.xbill.DNS.ARecord; +import org.xbill.DNS.DClass; +import org.xbill.DNS.Name; +import org.xbill.DNS.Record; +import org.xbill.DNS.Type; + + +public class DNSRecordCommands_addARecords_Test extends TestCase +{ + + + abstract class TestPlan extends BaseTestPlan + { + protected int port; + protected DNSServer server = null; + protected ConfigurationServiceProxy proxy; + protected DNSRecordCommands recordCommands; + + protected Record toRecord(DnsRecord rec) throws Exception + { + return Record.newRecord(Name.fromString(rec.getName()), rec.getType(), rec.getDclass(), rec.getTtl(), rec.getData()); + } + + protected List getARecordsInStore() throws Exception + { + DnsRecord[] records = proxy.getDNSByType(Type.A); + + List retVal; + + if (records == null || records.length == 0) + retVal = Collections.emptyList(); + else + { + retVal = new ArrayList(); + for (DnsRecord record : records) + retVal.add(toRecord(record)); + } + + return retVal; + } + + @Override + protected void setupMocks() throws Exception + { + if (!ConfigServiceRunner.isServiceRunning()) + ConfigServiceRunner.startConfigService(); + + proxy = new ConfigurationServiceProxy(ConfigServiceRunner.getConfigServiceURL()); + + cleanRecords(); + + port = AvailablePortFinder.getNextAvailable(1024); + DNSServerSettings settings = new DNSServerSettings(); + settings.setPort(port); + + server = new DNSServer(new ConfigServiceDNSStore(new URL(ConfigServiceRunner.getConfigServiceURL())), settings); + + server.start(); + + } + + @Override + protected void tearDownMocks() throws Exception + { + if (server != null) + server.stop(); + } + + @Override + protected void performInner() throws Exception + { + recordCommands = new DNSRecordCommands(proxy); + + List recordsToAdd = getRecordsToAdd(); + + + for (Record recordToAdd : recordsToAdd) + { + ARecord rec = (ARecord)recordToAdd; + String[] command = {rec.getName().toString(), rec.getAddress().getHostAddress(), Long.toString(rec.getTTL())}; + recordCommands.addANAME(command); + } + + + doAssertions(getARecordsInStore()); + } + + private void cleanRecords() throws Exception + { + DnsRecord[] rec = proxy.getDNSByType(Type.ANY); + + if (rec != null && rec.length > 0) + proxy.removeDNS(rec); + + rec = proxy.getDNSByType(Type.ANY); + + assertNull(rec); + + } + + + protected abstract List getRecordsToAdd() throws Exception; + + protected abstract void doAssertions(List records) throws Exception; + } + + public void testAddAName_AssertRecordAdded() throws Exception + { + new TestPlan() + { + @Override + protected List getRecordsToAdd() throws Exception + { + List addRecords = new ArrayList(); + addRecords.add(new ARecord(Name.fromString("example.domain.com."), DClass.IN, 3600, InetAddress.getByName("127.0.0.1"))); + + return addRecords; + } + + @Override + protected void doAssertions(List records) throws Exception + { + assertNotNull(records); + assertEquals(1, records.size()); + + Record rec = records.iterator().next(); + assertEquals("example.domain.com.", rec.getName().toString()); + assertEquals(3600, rec.getTTL()); + assertEquals(Type.A, rec.getType()); + assertEquals(DClass.IN, rec.getDClass()); + + ARecord aRec = (ARecord)rec; + assertEquals(aRec.getAddress().getHostAddress(), "127.0.0.1"); + + } + + }.perform(); + } + + public void testAddDupAName_AssertOneEntry() throws Exception + { + new TestPlan() + { + @Override + protected List getRecordsToAdd() throws Exception + { + List addRecords = new ArrayList(); + addRecords.add(new ARecord(Name.fromString("example.domain.com."), DClass.IN, 3600, InetAddress.getByName("127.0.0.1"))); + addRecords.add(new ARecord(Name.fromString("example.domain.com."), DClass.IN, 3600, InetAddress.getByName("127.0.0.1"))); + + return addRecords; + } + + @Override + protected void assertException(Exception exception) throws Exception + { + assertTrue(exception instanceof RuntimeException); + assertNotNull(exception.getCause()); + assertTrue(exception.getCause() instanceof RemoteException); + + // make sure the first command worked + Collection records = getARecordsInStore(); + + assertNotNull(records); + assertEquals(1, records.size()); + + Record rec = records.iterator().next(); + assertEquals("example.domain.com.", rec.getName().toString()); + assertEquals(3600, rec.getTTL()); + assertEquals(Type.A, rec.getType()); + assertEquals(DClass.IN, rec.getDClass()); + + ARecord aRec = (ARecord)rec; + assertEquals(aRec.getAddress().getHostAddress(), "127.0.0.1"); + } + + @Override + protected void doAssertions(List records) throws Exception + { + + } + + }.perform(); + } + + public void testAddMultipleRecords_AssertRecordsAdded() throws Exception + { + new TestPlan() + { + private List addRecords; + + @Override + protected List getRecordsToAdd() throws Exception + { + addRecords = new ArrayList(); + addRecords.add(new ARecord(Name.fromString("example1.domain.com."), DClass.IN, 3600, InetAddress.getByName("127.0.0.1"))); + addRecords.add(new ARecord(Name.fromString("example2.domain.com."), DClass.IN, 3600, InetAddress.getByName("127.0.0.2"))); + addRecords.add(new ARecord(Name.fromString("example3.domain.com."), DClass.IN, 3600, InetAddress.getByName("127.0.0.3"))); + + return addRecords; + } + + + @Override + protected void doAssertions(List records) throws Exception + { + assertNotNull(records); + assertEquals(3, records.size()); + + for (Record record : addRecords) + { + int index = records.indexOf(record); + assertTrue(index > -1); + Record checkRecord = records.get(index); + assertEquals(record, checkRecord); + } + } + + }.perform(); + } + + public void testAddMultipleRecords_SameNameDiffIP_AssertRecordsAdded() throws Exception + { + new TestPlan() + { + private List addRecords; + + @Override + protected List getRecordsToAdd() throws Exception + { + addRecords = new ArrayList(); + addRecords.add(new ARecord(Name.fromString("example.domain.com."), DClass.IN, 3600, InetAddress.getByName("127.0.0.1"))); + addRecords.add(new ARecord(Name.fromString("example.domain.com."), DClass.IN, 3600, InetAddress.getByName("127.0.0.2"))); + addRecords.add(new ARecord(Name.fromString("example.domain.com."), DClass.IN, 3600, InetAddress.getByName("127.0.0.3"))); + + return addRecords; + } + + + @Override + protected void doAssertions(List records) throws Exception + { + assertNotNull(records); + assertEquals(3, records.size()); + + for (Record record : addRecords) + { + int index = records.indexOf(record); + assertTrue(index > -1); + Record checkRecord = records.get(index); + assertEquals(record, checkRecord); + } + } + + }.perform(); + } +} diff --git a/java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/tools/DNSRecordCommands_addMXRecords_Test.java b/java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/tools/DNSRecordCommands_addMXRecords_Test.java new file mode 100644 index 000000000..4c3ce12c5 --- /dev/null +++ b/java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/tools/DNSRecordCommands_addMXRecords_Test.java @@ -0,0 +1,335 @@ +package org.nhindirect.dns.tools; + +import java.net.URL; +import java.rmi.RemoteException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import org.apache.mina.util.AvailablePortFinder; +import org.nhind.config.ConfigurationServiceProxy; +import org.nhind.config.DnsRecord; +import org.nhindirect.dns.ConfigServiceDNSStore; +import org.nhindirect.dns.DNSServer; +import org.nhindirect.dns.DNSServerSettings; +import org.nhindirect.dns.util.BaseTestPlan; +import org.nhindirect.dns.util.ConfigServiceRunner; + +import org.xbill.DNS.DClass; +import org.xbill.DNS.MXRecord; +import org.xbill.DNS.Name; +import org.xbill.DNS.Record; +import org.xbill.DNS.Type; + +import junit.framework.TestCase; + +public class DNSRecordCommands_addMXRecords_Test extends TestCase +{ + abstract class TestPlan extends BaseTestPlan + { + protected int port; + protected DNSServer server = null; + protected ConfigurationServiceProxy proxy; + protected DNSRecordCommands recordCommands; + + protected Record toRecord(DnsRecord rec) throws Exception + { + return Record.newRecord(Name.fromString(rec.getName()), rec.getType(), rec.getDclass(), rec.getTtl(), rec.getData()); + } + + protected List getMXRecordsInStore() throws Exception + { + DnsRecord[] records = proxy.getDNSByType(Type.MX); + + List retVal; + + if (records == null || records.length == 0) + retVal = Collections.emptyList(); + else + { + retVal = new ArrayList(); + for (DnsRecord record : records) + retVal.add(toRecord(record)); + } + + return retVal; + } + + @Override + protected void setupMocks() throws Exception + { + if (!ConfigServiceRunner.isServiceRunning()) + ConfigServiceRunner.startConfigService(); + + proxy = new ConfigurationServiceProxy(ConfigServiceRunner.getConfigServiceURL()); + + cleanRecords(); + + port = AvailablePortFinder.getNextAvailable(1024); + DNSServerSettings settings = new DNSServerSettings(); + settings.setPort(port); + + server = new DNSServer(new ConfigServiceDNSStore(new URL(ConfigServiceRunner.getConfigServiceURL())), settings); + + server.start(); + + } + + @Override + protected void tearDownMocks() throws Exception + { + if (server != null) + server.stop(); + } + + @Override + protected void performInner() throws Exception + { + recordCommands = new DNSRecordCommands(proxy); + + List recordsToAdd = getRecordsToAdd(); + + + for (Record recordToAdd : recordsToAdd) + { + MXRecord rec = (MXRecord)recordToAdd; + String[] command = {rec.getName().toString(), rec.getTarget().toString(), Long.toString(rec.getTTL()), Integer.toString(rec.getPriority())}; + recordCommands.addMX(command); + } + + + doAssertions(getMXRecordsInStore()); + } + + private void cleanRecords() throws Exception + { + DnsRecord[] rec = proxy.getDNSByType(Type.ANY); + + if (rec != null && rec.length > 0) + proxy.removeDNS(rec); + + rec = proxy.getDNSByType(Type.ANY); + + assertNull(rec); + + } + + + protected abstract List getRecordsToAdd() throws Exception; + + protected abstract void doAssertions(List records) throws Exception; + } + + public void testAddMXName_AssertRecordAdded() throws Exception + { + new TestPlan() + { + @Override + protected List getRecordsToAdd() throws Exception + { + List addRecords = new ArrayList(); + addRecords.add(new MXRecord(Name.fromString("example.domain.com."), DClass.IN, 3600, + 1, Name.fromString("mail1.example.domain.com."))); + + return addRecords; + } + + @Override + protected void doAssertions(List records) throws Exception + { + assertNotNull(records); + assertEquals(1, records.size()); + + Record rec = records.iterator().next(); + assertEquals("example.domain.com.", rec.getName().toString()); + assertEquals(3600, rec.getTTL()); + assertEquals(Type.MX, rec.getType()); + assertEquals(DClass.IN, rec.getDClass()); + + MXRecord mxRec = (MXRecord)rec; + assertEquals("mail1.example.domain.com.", mxRec.getTarget().toString()); + assertEquals(1, mxRec.getPriority()); + + } + + }.perform(); + } + + public void testAddDupMX_AssertOneEntry() throws Exception + { + new TestPlan() + { + @Override + protected List getRecordsToAdd() throws Exception + { + List addRecords = new ArrayList(); + addRecords.add(new MXRecord(Name.fromString("example.domain.com."), DClass.IN, 3600, + 1, Name.fromString("mail1.example.domain.com."))); + + addRecords.add(new MXRecord(Name.fromString("example.domain.com."), DClass.IN, 3600, + 1, Name.fromString("mail1.example.domain.com."))); + + return addRecords; + } + + @Override + protected void assertException(Exception exception) throws Exception + { + assertTrue(exception instanceof RuntimeException); + assertNotNull(exception.getCause()); + assertTrue(exception.getCause() instanceof RemoteException); + + // make sure the first command worked + Collection records = getMXRecordsInStore(); + + assertNotNull(records); + assertEquals(1, records.size()); + + assertNotNull(records); + assertEquals(1, records.size()); + + Record rec = records.iterator().next(); + assertEquals("example.domain.com.", rec.getName().toString()); + assertEquals(3600, rec.getTTL()); + assertEquals(Type.MX, rec.getType()); + assertEquals(DClass.IN, rec.getDClass()); + + MXRecord mxRec = (MXRecord)rec; + assertEquals("mail1.example.domain.com.", mxRec.getTarget().toString()); + assertEquals(1, mxRec.getPriority()); + } + + @Override + protected void doAssertions(List records) throws Exception + { + + } + + }.perform(); + } + + public void testAddMultipleRecords_AssertRecordsAdded() throws Exception + { + new TestPlan() + { + private List addRecords; + + @Override + protected List getRecordsToAdd() throws Exception + { + addRecords = new ArrayList(); + List addRecords = new ArrayList(); + addRecords.add(new MXRecord(Name.fromString("example1.domain.com."), DClass.IN, 3600, + 1, Name.fromString("mail1.example.domain.com."))); + + addRecords.add(new MXRecord(Name.fromString("example2.domain.com."), DClass.IN, 3600, + 1, Name.fromString("mail2.example.domain.com."))); + + addRecords.add(new MXRecord(Name.fromString("example3.domain.com."), DClass.IN, 3600, + 1, Name.fromString("mail3.example.domain.com."))); + + return addRecords; + } + + + @Override + protected void doAssertions(List records) throws Exception + { + assertNotNull(records); + assertEquals(3, records.size()); + + for (Record record : addRecords) + { + int index = records.indexOf(record); + assertTrue(index > -1); + Record checkRecord = records.get(index); + assertEquals(record, checkRecord); + } + } + + }.perform(); + } + + public void testAddMultipleRecords_SameNameDiffTarget_AssertRecordsAdded() throws Exception + { + new TestPlan() + { + private List addRecords; + + @Override + protected List getRecordsToAdd() throws Exception + { + addRecords = new ArrayList(); + addRecords.add(new MXRecord(Name.fromString("example1.domain.com."), DClass.IN, 3600, + 1, Name.fromString("mail1.example.domain.com."))); + + addRecords.add(new MXRecord(Name.fromString("example1.domain.com."), DClass.IN, 3600, + 1, Name.fromString("mail2.example.domain.com."))); + + addRecords.add(new MXRecord(Name.fromString("example1.domain.com."), DClass.IN, 3600, + 1, Name.fromString("mail3.example.domain.com."))); + + return addRecords; + } + + + @Override + protected void doAssertions(List records) throws Exception + { + assertNotNull(records); + assertEquals(3, records.size()); + + for (Record record : addRecords) + { + int index = records.indexOf(record); + assertTrue(index > -1); + Record checkRecord = records.get(index); + assertEquals(record, checkRecord); + } + } + + }.perform(); + } + + public void testAddMultipleRecords_SameNameAndTargetDiffPriorities_AssertRecordsAdded() throws Exception + { + new TestPlan() + { + private List addRecords; + + @Override + protected List getRecordsToAdd() throws Exception + { + addRecords = new ArrayList(); + addRecords.add(new MXRecord(Name.fromString("example1.domain.com."), DClass.IN, 3600, + 1, Name.fromString("mail1.example.domain.com."))); + + addRecords.add(new MXRecord(Name.fromString("example1.domain.com."), DClass.IN, 3600, + 2, Name.fromString("mail2.example.domain.com."))); + + addRecords.add(new MXRecord(Name.fromString("example3.domain.com."), DClass.IN, 3600, + 1, Name.fromString("mail3.example.domain.com."))); + + return addRecords; + } + + + @Override + protected void doAssertions(List records) throws Exception + { + assertNotNull(records); + assertEquals(3, records.size()); + + for (Record record : addRecords) + { + int index = records.indexOf(record); + assertTrue(index > -1); + Record checkRecord = records.get(index); + assertEquals(record, checkRecord); + } + } + + }.perform(); + } +} diff --git a/java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/tools/DNSRecordCommands_addSAORecords_Test.java b/java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/tools/DNSRecordCommands_addSAORecords_Test.java new file mode 100644 index 000000000..7c7cbb598 --- /dev/null +++ b/java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/tools/DNSRecordCommands_addSAORecords_Test.java @@ -0,0 +1,247 @@ +package org.nhindirect.dns.tools; + +import java.net.URL; +import java.rmi.RemoteException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import org.apache.mina.util.AvailablePortFinder; +import org.nhind.config.ConfigurationServiceProxy; +import org.nhind.config.DnsRecord; +import org.nhindirect.dns.ConfigServiceDNSStore; +import org.nhindirect.dns.DNSServer; +import org.nhindirect.dns.DNSServerSettings; +import org.nhindirect.dns.util.BaseTestPlan; +import org.nhindirect.dns.util.ConfigServiceRunner; +import org.xbill.DNS.DClass; +import org.xbill.DNS.Name; +import org.xbill.DNS.Record; +import org.xbill.DNS.SOARecord; +import org.xbill.DNS.Type; + +import junit.framework.TestCase; + +public class DNSRecordCommands_addSAORecords_Test extends TestCase +{ + abstract class TestPlan extends BaseTestPlan + { + protected int port; + protected DNSServer server = null; + protected ConfigurationServiceProxy proxy; + protected DNSRecordCommands recordCommands; + + protected Record toRecord(DnsRecord rec) throws Exception + { + return Record.newRecord(Name.fromString(rec.getName()), rec.getType(), rec.getDclass(), rec.getTtl(), rec.getData()); + } + + protected List getSOARecordsInStore() throws Exception + { + DnsRecord[] records = proxy.getDNSByType(Type.SOA); + + List retVal; + + if (records == null || records.length == 0) + retVal = Collections.emptyList(); + else + { + retVal = new ArrayList(); + for (DnsRecord record : records) + retVal.add(toRecord(record)); + } + + return retVal; + } + + @Override + protected void setupMocks() throws Exception + { + if (!ConfigServiceRunner.isServiceRunning()) + ConfigServiceRunner.startConfigService(); + + proxy = new ConfigurationServiceProxy(ConfigServiceRunner.getConfigServiceURL()); + + cleanRecords(); + + port = AvailablePortFinder.getNextAvailable(1024); + DNSServerSettings settings = new DNSServerSettings(); + settings.setPort(port); + + server = new DNSServer(new ConfigServiceDNSStore(new URL(ConfigServiceRunner.getConfigServiceURL())), settings); + + server.start(); + + } + + @Override + protected void tearDownMocks() throws Exception + { + if (server != null) + server.stop(); + } + + @Override + protected void performInner() throws Exception + { + recordCommands = new DNSRecordCommands(proxy); + + List recordsToAdd = getRecordsToAdd(); + + + for (Record recordToAdd : recordsToAdd) + { + SOARecord rec = (SOARecord)recordToAdd; + String[] command = {rec.getName().toString(), rec.getHost().toString(), rec.getAdmin().toString(), + Long.toString(rec.getSerial()), Long.toString(rec.getTTL()), Long.toString(rec.getRefresh()), + Long.toString(rec.getRetry()), Long.toString(rec.getExpire()), Long.toString(rec.getMinimum())}; + recordCommands.addSOA(command); + } + + + doAssertions(getSOARecordsInStore()); + } + + private void cleanRecords() throws Exception + { + DnsRecord[] rec = proxy.getDNSByType(Type.ANY); + + if (rec != null && rec.length > 0) + proxy.removeDNS(rec); + + rec = proxy.getDNSByType(Type.ANY); + + assertNull(rec); + + } + + + protected abstract List getRecordsToAdd() throws Exception; + + protected abstract void doAssertions(List records) throws Exception; + } + + public void testAddSOA_AssertRecordAdded() throws Exception + { + + + new TestPlan() + { + private Record addRecord; + + @Override + protected List getRecordsToAdd() throws Exception + { + List addRecords = new ArrayList(); + addRecord = new SOARecord(Name.fromString("example.domain.com."), DClass.IN, 3600, Name.fromString("ns1.example.domain.com."), + Name.fromString("gm2552.example.domain.com."), 1, 3600, 60, 60, 3600); + + addRecords.add(addRecord); + + return addRecords; + } + + @Override + protected void doAssertions(List records) throws Exception + { + assertNotNull(records); + assertEquals(1, records.size()); + + Record rec = records.iterator().next(); + assertEquals(addRecord, rec); + + } + + }.perform(); + } + + public void testAddDupSOA_AssertOneEntry() throws Exception + { + new TestPlan() + { + private Record addRecord; + + @Override + protected List getRecordsToAdd() throws Exception + { + List addRecords = new ArrayList(); + + addRecords.add(addRecord = new SOARecord(Name.fromString("example.domain.com."), DClass.IN, 3600, Name.fromString("ns1.example.domain.com."), + Name.fromString("gm2552.example.domain.com."), 1, 3600, 60, 60, 3600)); + + addRecords.add(new SOARecord(Name.fromString("example.domain.com."), DClass.IN, 3600, Name.fromString("ns1.example.domain.com."), + Name.fromString("gm2552.example.domain.com."), 1, 3600, 60, 60, 3600)); + + return addRecords; + } + + @Override + protected void assertException(Exception exception) throws Exception + { + assertTrue(exception instanceof RuntimeException); + assertNotNull(exception.getCause()); + assertTrue(exception.getCause() instanceof RemoteException); + + // make sure the first command worked + Collection records = getSOARecordsInStore(); + + assertNotNull(records); + assertEquals(1, records.size()); + Record rec = records.iterator().next(); + assertEquals(addRecord, rec); + + } + + @Override + protected void doAssertions(List records) throws Exception + { + + } + + }.perform(); + } + + public void testAddMultipleSOARecords_AssertRecordsAdded() throws Exception + { + new TestPlan() + { + private List addRecords; + + @Override + protected List getRecordsToAdd() throws Exception + { + addRecords = new ArrayList(); + addRecords.add(new SOARecord(Name.fromString("example.domain.com."), DClass.IN, 3600, Name.fromString("ns1.example.domain.com."), + Name.fromString("gm2552.example.domain.com."), 1, 3600, 60, 60, 3600)); + + addRecords.add(new SOARecord(Name.fromString("example2.domain.com."), DClass.IN, 3600, Name.fromString("ns1.example2.domain.com."), + Name.fromString("gm2552.example2.domain.com."), 1, 3600, 60, 60, 3600)); + + addRecords.add(new SOARecord(Name.fromString("example3.domain.com."), DClass.IN, 3600, Name.fromString("ns1.example3.domain.com."), + Name.fromString("gm2552.example3.domain.com."), 1, 3600, 60, 60, 3600)); + + return addRecords; + } + + + @Override + protected void doAssertions(List records) throws Exception + { + assertNotNull(records); + assertEquals(3, records.size()); + + for (Record record : addRecords) + { + int index = records.indexOf(record); + assertTrue(index > -1); + Record checkRecord = records.get(index); + assertEquals(record, checkRecord); + } + } + + }.perform(); + } + + +} diff --git a/java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/tools/DNSRecordCommands_ensureARecords_Test.java b/java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/tools/DNSRecordCommands_ensureARecords_Test.java new file mode 100644 index 000000000..5683380d8 --- /dev/null +++ b/java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/tools/DNSRecordCommands_ensureARecords_Test.java @@ -0,0 +1,299 @@ +package org.nhindirect.dns.tools; + +import java.net.InetAddress; +import java.net.URL; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import junit.framework.TestCase; + +import org.apache.axis.AxisFault; +import org.apache.mina.util.AvailablePortFinder; +import org.nhind.config.ConfigurationServiceProxy; +import org.nhind.config.DnsRecord; +import org.nhindirect.dns.ConfigServiceDNSStore; +import org.nhindirect.dns.DNSServer; +import org.nhindirect.dns.DNSServerSettings; +import org.nhindirect.dns.util.BaseTestPlan; +import org.nhindirect.dns.util.ConfigServiceRunner; +import org.xbill.DNS.ARecord; +import org.xbill.DNS.DClass; +import org.xbill.DNS.Name; +import org.xbill.DNS.Record; +import org.xbill.DNS.Type; + +public class DNSRecordCommands_ensureARecords_Test extends TestCase +{ + abstract class TestPlan extends BaseTestPlan + { + protected int port; + protected DNSServer server = null; + protected ConfigurationServiceProxy proxy; + protected DNSRecordCommands recordCommands; + + protected Record toRecord(DnsRecord rec) throws Exception + { + return Record.newRecord(Name.fromString(rec.getName()), rec.getType(), rec.getDclass(), rec.getTtl(), rec.getData()); + } + + protected List getARecordsInStore() throws Exception + { + DnsRecord[] records = proxy.getDNSByType(Type.A); + + List retVal; + + if (records == null || records.length == 0) + retVal = Collections.emptyList(); + else + { + retVal = new ArrayList(); + for (DnsRecord record : records) + retVal.add(toRecord(record)); + } + + return retVal; + } + + @Override + protected void setupMocks() throws Exception + { + if (!ConfigServiceRunner.isServiceRunning()) + ConfigServiceRunner.startConfigService(); + + proxy = new ConfigurationServiceProxy(ConfigServiceRunner.getConfigServiceURL()); + + cleanRecords(); + + port = AvailablePortFinder.getNextAvailable(1024); + DNSServerSettings settings = new DNSServerSettings(); + settings.setPort(port); + + server = new DNSServer(new ConfigServiceDNSStore(new URL(ConfigServiceRunner.getConfigServiceURL())), settings); + + server.start(); + + } + + @Override + protected void tearDownMocks() throws Exception + { + if (server != null) + server.stop(); + } + + @Override + protected void performInner() throws Exception + { + recordCommands = new DNSRecordCommands(proxy); + + List recordsToAdd = getRecordsToAdd(); + + + for (Record recordToAdd : recordsToAdd) + { + ARecord rec = (ARecord)recordToAdd; + String[] command = {rec.getName().toString(), rec.getAddress().getHostAddress(), Long.toString(rec.getTTL())}; + recordCommands.ensureANAME(command); + } + + + doAssertions(getARecordsInStore()); + } + + private void cleanRecords() throws Exception + { + DnsRecord[] rec = proxy.getDNSByType(Type.ANY); + + if (rec != null && rec.length > 0) + proxy.removeDNS(rec); + + rec = proxy.getDNSByType(Type.ANY); + + assertNull(rec); + + } + + + protected abstract List getRecordsToAdd() throws Exception; + + protected abstract void doAssertions(List records) throws Exception; + } + + public void testEnsureAName_AssertRecordAdded() throws Exception + { + new TestPlan() + { + @Override + protected List getRecordsToAdd() throws Exception + { + List addRecords = new ArrayList(); + addRecords.add(new ARecord(Name.fromString("example.domain.com."), DClass.IN, 3600, InetAddress.getByName("127.0.0.1"))); + + return addRecords; + } + + @Override + protected void doAssertions(List records) throws Exception + { + assertNotNull(records); + assertEquals(1, records.size()); + + Record rec = records.iterator().next(); + assertEquals("example.domain.com.", rec.getName().toString()); + assertEquals(3600, rec.getTTL()); + assertEquals(Type.A, rec.getType()); + assertEquals(DClass.IN, rec.getDClass()); + + ARecord aRec = (ARecord)rec; + assertEquals(aRec.getAddress().getHostAddress(), "127.0.0.1"); + + } + + }.perform(); + } + + public void testEnsureAName_AssertOneEntry() throws Exception + { + new TestPlan() + { + @Override + protected List getRecordsToAdd() throws Exception + { + List addRecords = new ArrayList(); + addRecords.add(new ARecord(Name.fromString("example.domain.com."), DClass.IN, 3600, InetAddress.getByName("127.0.0.1"))); + addRecords.add(new ARecord(Name.fromString("example.domain.com."), DClass.IN, 3600, InetAddress.getByName("127.0.0.1"))); + + return addRecords; + } + + @Override + protected void assertException(Exception exception) throws Exception + { + + } + + @Override + protected void doAssertions(List records) throws Exception + { + // make sure the first command worked + + assertNotNull(records); + assertEquals(1, records.size()); + + Record rec = records.iterator().next(); + assertEquals("example.domain.com.", rec.getName().toString()); + assertEquals(3600, rec.getTTL()); + assertEquals(Type.A, rec.getType()); + assertEquals(DClass.IN, rec.getDClass()); + + ARecord aRec = (ARecord)rec; + assertEquals(aRec.getAddress().getHostAddress(), "127.0.0.1"); + } + + }.perform(); + } + + public void testEnsureMultipleRecords_AssertRecordsAdded() throws Exception + { + new TestPlan() + { + private List addRecords; + + @Override + protected List getRecordsToAdd() throws Exception + { + addRecords = new ArrayList(); + addRecords.add(new ARecord(Name.fromString("example1.domain.com."), DClass.IN, 3600, InetAddress.getByName("127.0.0.1"))); + addRecords.add(new ARecord(Name.fromString("example2.domain.com."), DClass.IN, 3600, InetAddress.getByName("127.0.0.2"))); + addRecords.add(new ARecord(Name.fromString("example3.domain.com."), DClass.IN, 3600, InetAddress.getByName("127.0.0.3"))); + + return addRecords; + } + + + @Override + protected void doAssertions(List records) throws Exception + { + assertNotNull(records); + assertEquals(3, records.size()); + + for (Record record : addRecords) + { + int index = records.indexOf(record); + assertTrue(index > -1); + Record checkRecord = records.get(index); + assertEquals(record, checkRecord); + } + } + + }.perform(); + } + + public void testEnsureMultipleRecords_SameNameDiffIP_AssertRecordsAdded() throws Exception + { + new TestPlan() + { + private List addRecords; + + @Override + protected List getRecordsToAdd() throws Exception + { + addRecords = new ArrayList(); + addRecords.add(new ARecord(Name.fromString("example.domain.com."), DClass.IN, 3600, InetAddress.getByName("127.0.0.1"))); + addRecords.add(new ARecord(Name.fromString("example.domain.com."), DClass.IN, 3600, InetAddress.getByName("127.0.0.2"))); + addRecords.add(new ARecord(Name.fromString("example.domain.com."), DClass.IN, 3600, InetAddress.getByName("127.0.0.3"))); + + return addRecords; + } + + + @Override + protected void doAssertions(List records) throws Exception + { + assertNotNull(records); + assertEquals(3, records.size()); + + for (Record record : addRecords) + { + int index = records.indexOf(record); + assertTrue(index > -1); + Record checkRecord = records.get(index); + assertEquals(record, checkRecord); + } + } + + }.perform(); + } + + public void testFailToEnsure_invalidProxy_AssertException() throws Exception + { + new TestPlan() + { + private List addRecords; + + @Override + protected List getRecordsToAdd() throws Exception + { + addRecords = new ArrayList(); + addRecords.add(new ARecord(Name.fromString("example.domain.com."), DClass.IN, 3600, InetAddress.getByName("127.0.0.1"))); + + this.recordCommands.setConfigurationProxy(new ConfigurationServiceProxy("http://localhost:7777/bogusendpoint")); + return addRecords; + } + + @Override + protected void assertException(Exception exception) throws Exception + { + assertNotNull(exception); + assertTrue(exception instanceof RuntimeException); + assertTrue(exception.getCause() instanceof AxisFault); + } + + @Override + protected void doAssertions(List recordsMatched) throws Exception + { + } + }.perform(); + } +} diff --git a/java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/tools/DNSRecordCommands_ensureMXRecords_Test.java b/java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/tools/DNSRecordCommands_ensureMXRecords_Test.java new file mode 100644 index 000000000..1d3a18077 --- /dev/null +++ b/java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/tools/DNSRecordCommands_ensureMXRecords_Test.java @@ -0,0 +1,320 @@ +package org.nhindirect.dns.tools; + +import java.net.URL; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.apache.mina.util.AvailablePortFinder; +import org.nhind.config.ConfigurationServiceProxy; +import org.nhind.config.DnsRecord; +import org.nhindirect.dns.ConfigServiceDNSStore; +import org.nhindirect.dns.DNSServer; +import org.nhindirect.dns.DNSServerSettings; +import org.nhindirect.dns.util.BaseTestPlan; +import org.nhindirect.dns.util.ConfigServiceRunner; +import org.xbill.DNS.DClass; +import org.xbill.DNS.MXRecord; +import org.xbill.DNS.Name; +import org.xbill.DNS.Record; +import org.xbill.DNS.Type; + +import junit.framework.TestCase; + +public class DNSRecordCommands_ensureMXRecords_Test extends TestCase +{ + abstract class TestPlan extends BaseTestPlan + { + protected int port; + protected DNSServer server = null; + protected ConfigurationServiceProxy proxy; + protected DNSRecordCommands recordCommands; + + protected Record toRecord(DnsRecord rec) throws Exception + { + return Record.newRecord(Name.fromString(rec.getName()), rec.getType(), rec.getDclass(), rec.getTtl(), rec.getData()); + } + + protected List getMXRecordsInStore() throws Exception + { + DnsRecord[] records = proxy.getDNSByType(Type.MX); + + List retVal; + + if (records == null || records.length == 0) + retVal = Collections.emptyList(); + else + { + retVal = new ArrayList(); + for (DnsRecord record : records) + retVal.add(toRecord(record)); + } + + return retVal; + } + + @Override + protected void setupMocks() throws Exception + { + if (!ConfigServiceRunner.isServiceRunning()) + ConfigServiceRunner.startConfigService(); + + proxy = new ConfigurationServiceProxy(ConfigServiceRunner.getConfigServiceURL()); + + cleanRecords(); + + port = AvailablePortFinder.getNextAvailable(1024); + DNSServerSettings settings = new DNSServerSettings(); + settings.setPort(port); + + server = new DNSServer(new ConfigServiceDNSStore(new URL(ConfigServiceRunner.getConfigServiceURL())), settings); + + server.start(); + + } + + @Override + protected void tearDownMocks() throws Exception + { + if (server != null) + server.stop(); + } + + @Override + protected void performInner() throws Exception + { + recordCommands = new DNSRecordCommands(proxy); + + List recordsToAdd = getRecordsToAdd(); + + + for (Record recordToAdd : recordsToAdd) + { + MXRecord rec = (MXRecord)recordToAdd; + String[] command = {rec.getName().toString(), rec.getTarget().toString(), Long.toString(rec.getTTL()), Integer.toString(rec.getPriority())}; + recordCommands.ensureMX(command); + } + + + doAssertions(getMXRecordsInStore()); + } + + private void cleanRecords() throws Exception + { + DnsRecord[] rec = proxy.getDNSByType(Type.ANY); + + if (rec != null && rec.length > 0) + proxy.removeDNS(rec); + + rec = proxy.getDNSByType(Type.ANY); + + assertNull(rec); + + } + + + protected abstract List getRecordsToAdd() throws Exception; + + protected abstract void doAssertions(List records) throws Exception; + } + + public void testEnsureMXName_AssertRecordAdded() throws Exception + { + new TestPlan() + { + @Override + protected List getRecordsToAdd() throws Exception + { + List addRecords = new ArrayList(); + addRecords.add(new MXRecord(Name.fromString("example.domain.com."), DClass.IN, 3600, + 1, Name.fromString("mail1.example.domain.com."))); + + return addRecords; + } + + @Override + protected void doAssertions(List records) throws Exception + { + assertNotNull(records); + assertEquals(1, records.size()); + + Record rec = records.iterator().next(); + assertEquals("example.domain.com.", rec.getName().toString()); + assertEquals(3600, rec.getTTL()); + assertEquals(Type.MX, rec.getType()); + assertEquals(DClass.IN, rec.getDClass()); + + MXRecord mxRec = (MXRecord)rec; + assertEquals("mail1.example.domain.com.", mxRec.getTarget().toString()); + assertEquals(1, mxRec.getPriority()); + + } + + }.perform(); + } + + public void testEnsureDupMX_AssertOneEntry() throws Exception + { + new TestPlan() + { + @Override + protected List getRecordsToAdd() throws Exception + { + List addRecords = new ArrayList(); + addRecords.add(new MXRecord(Name.fromString("example.domain.com."), DClass.IN, 3600, + 1, Name.fromString("mail1.example.domain.com."))); + + addRecords.add(new MXRecord(Name.fromString("example.domain.com."), DClass.IN, 3600, + 1, Name.fromString("mail1.example.domain.com."))); + + return addRecords; + } + + + @Override + protected void doAssertions(List records) throws Exception + { + assertNotNull(records); + assertEquals(1, records.size()); + + assertNotNull(records); + assertEquals(1, records.size()); + + Record rec = records.iterator().next(); + assertEquals("example.domain.com.", rec.getName().toString()); + assertEquals(3600, rec.getTTL()); + assertEquals(Type.MX, rec.getType()); + assertEquals(DClass.IN, rec.getDClass()); + + MXRecord mxRec = (MXRecord)rec; + assertEquals("mail1.example.domain.com.", mxRec.getTarget().toString()); + assertEquals(1, mxRec.getPriority()); + } + + }.perform(); + } + + public void testEnsureMultipleRecords_AssertRecordsAdded() throws Exception + { + new TestPlan() + { + private List addRecords; + + @Override + protected List getRecordsToAdd() throws Exception + { + addRecords = new ArrayList(); + List addRecords = new ArrayList(); + addRecords.add(new MXRecord(Name.fromString("example1.domain.com."), DClass.IN, 3600, + 1, Name.fromString("mail1.example.domain.com."))); + + addRecords.add(new MXRecord(Name.fromString("example2.domain.com."), DClass.IN, 3600, + 1, Name.fromString("mail2.example.domain.com."))); + + addRecords.add(new MXRecord(Name.fromString("example3.domain.com."), DClass.IN, 3600, + 1, Name.fromString("mail3.example.domain.com."))); + + return addRecords; + } + + + @Override + protected void doAssertions(List records) throws Exception + { + assertNotNull(records); + assertEquals(3, records.size()); + + for (Record record : addRecords) + { + int index = records.indexOf(record); + assertTrue(index > -1); + Record checkRecord = records.get(index); + assertEquals(record, checkRecord); + } + } + + }.perform(); + } + + public void testEnsureMultipleRecords_SameNameDiffTarget_AssertRecordsAdded() throws Exception + { + new TestPlan() + { + private List addRecords; + + @Override + protected List getRecordsToAdd() throws Exception + { + addRecords = new ArrayList(); + addRecords.add(new MXRecord(Name.fromString("example1.domain.com."), DClass.IN, 3600, + 1, Name.fromString("mail1.example.domain.com."))); + + addRecords.add(new MXRecord(Name.fromString("example1.domain.com."), DClass.IN, 3600, + 1, Name.fromString("mail2.example.domain.com."))); + + addRecords.add(new MXRecord(Name.fromString("example1.domain.com."), DClass.IN, 3600, + 1, Name.fromString("mail3.example.domain.com."))); + + return addRecords; + } + + + @Override + protected void doAssertions(List records) throws Exception + { + assertNotNull(records); + assertEquals(3, records.size()); + + for (Record record : addRecords) + { + int index = records.indexOf(record); + assertTrue(index > -1); + Record checkRecord = records.get(index); + assertEquals(record, checkRecord); + } + } + + }.perform(); + } + + public void testEnsureMultipleRecords_SameNameAndTargetDiffPriorities_AssertRecordsAdded() throws Exception + { + new TestPlan() + { + private List addRecords; + + @Override + protected List getRecordsToAdd() throws Exception + { + addRecords = new ArrayList(); + addRecords.add(new MXRecord(Name.fromString("example1.domain.com."), DClass.IN, 3600, + 1, Name.fromString("mail1.example.domain.com."))); + + addRecords.add(new MXRecord(Name.fromString("example1.domain.com."), DClass.IN, 3600, + 2, Name.fromString("mail2.example.domain.com."))); + + addRecords.add(new MXRecord(Name.fromString("example3.domain.com."), DClass.IN, 3600, + 1, Name.fromString("mail3.example.domain.com."))); + + return addRecords; + } + + + @Override + protected void doAssertions(List records) throws Exception + { + assertNotNull(records); + assertEquals(3, records.size()); + + for (Record record : addRecords) + { + int index = records.indexOf(record); + assertTrue(index > -1); + Record checkRecord = records.get(index); + assertEquals(record, checkRecord); + } + } + + }.perform(); + } +} diff --git a/java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/tools/DNSRecordCommands_ensureSAORecords_Test.java b/java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/tools/DNSRecordCommands_ensureSAORecords_Test.java new file mode 100644 index 000000000..5d2ec92f3 --- /dev/null +++ b/java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/tools/DNSRecordCommands_ensureSAORecords_Test.java @@ -0,0 +1,231 @@ +package org.nhindirect.dns.tools; + +import java.net.URL; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.apache.mina.util.AvailablePortFinder; +import org.nhind.config.ConfigurationServiceProxy; +import org.nhind.config.DnsRecord; +import org.nhindirect.dns.ConfigServiceDNSStore; +import org.nhindirect.dns.DNSServer; +import org.nhindirect.dns.DNSServerSettings; +import org.nhindirect.dns.util.BaseTestPlan; +import org.nhindirect.dns.util.ConfigServiceRunner; +import org.xbill.DNS.DClass; +import org.xbill.DNS.Name; +import org.xbill.DNS.Record; +import org.xbill.DNS.SOARecord; +import org.xbill.DNS.Type; + +import junit.framework.TestCase; + +public class DNSRecordCommands_ensureSAORecords_Test extends TestCase +{ + abstract class TestPlan extends BaseTestPlan + { + protected int port; + protected DNSServer server = null; + protected ConfigurationServiceProxy proxy; + protected DNSRecordCommands recordCommands; + + protected Record toRecord(DnsRecord rec) throws Exception + { + return Record.newRecord(Name.fromString(rec.getName()), rec.getType(), rec.getDclass(), rec.getTtl(), rec.getData()); + } + + protected List getSOARecordsInStore() throws Exception + { + DnsRecord[] records = proxy.getDNSByType(Type.SOA); + + List retVal; + + if (records == null || records.length == 0) + retVal = Collections.emptyList(); + else + { + retVal = new ArrayList(); + for (DnsRecord record : records) + retVal.add(toRecord(record)); + } + + return retVal; + } + + @Override + protected void setupMocks() throws Exception + { + if (!ConfigServiceRunner.isServiceRunning()) + ConfigServiceRunner.startConfigService(); + + proxy = new ConfigurationServiceProxy(ConfigServiceRunner.getConfigServiceURL()); + + cleanRecords(); + + port = AvailablePortFinder.getNextAvailable(1024); + DNSServerSettings settings = new DNSServerSettings(); + settings.setPort(port); + + server = new DNSServer(new ConfigServiceDNSStore(new URL(ConfigServiceRunner.getConfigServiceURL())), settings); + + server.start(); + + } + + @Override + protected void tearDownMocks() throws Exception + { + if (server != null) + server.stop(); + } + + @Override + protected void performInner() throws Exception + { + recordCommands = new DNSRecordCommands(proxy); + + List recordsToAdd = getRecordsToAdd(); + + + for (Record recordToAdd : recordsToAdd) + { + SOARecord rec = (SOARecord)recordToAdd; + String[] command = {rec.getName().toString(), rec.getHost().toString(), rec.getAdmin().toString(), + Long.toString(rec.getSerial()), Long.toString(rec.getTTL()), Long.toString(rec.getRefresh()), + Long.toString(rec.getRetry()), Long.toString(rec.getExpire()), Long.toString(rec.getMinimum())}; + recordCommands.ensureSOA(command); + } + + + doAssertions(getSOARecordsInStore()); + } + + private void cleanRecords() throws Exception + { + DnsRecord[] rec = proxy.getDNSByType(Type.ANY); + + if (rec != null && rec.length > 0) + proxy.removeDNS(rec); + + rec = proxy.getDNSByType(Type.ANY); + + assertNull(rec); + + } + + + protected abstract List getRecordsToAdd() throws Exception; + + protected abstract void doAssertions(List records) throws Exception; + } + + public void testEnsureSOA_AssertRecordAdded() throws Exception + { + + + new TestPlan() + { + private Record addRecord; + + @Override + protected List getRecordsToAdd() throws Exception + { + List addRecords = new ArrayList(); + addRecord = new SOARecord(Name.fromString("example.domain.com."), DClass.IN, 3600, Name.fromString("ns1.example.domain.com."), + Name.fromString("gm2552.example.domain.com."), 1, 3600, 60, 60, 3600); + addRecords.add(addRecord); + + return addRecords; + } + + @Override + protected void doAssertions(List records) throws Exception + { + assertNotNull(records); + assertEquals(1, records.size()); + + Record rec = records.iterator().next(); + assertEquals(addRecord, rec); + + } + + }.perform(); + } + + public void testEnsureDupSOA_AssertOneEntry() throws Exception + { + new TestPlan() + { + private Record addRecord; + + @Override + protected List getRecordsToAdd() throws Exception + { + List addRecords = new ArrayList(); + + addRecords.add(addRecord = new SOARecord(Name.fromString("example.domain.com."), DClass.IN, 3600, Name.fromString("ns1.example.domain.com."), + Name.fromString("gm2552.example.domain.com."), 1, 3600, 60, 60, 3600)); + + addRecords.add(new SOARecord(Name.fromString("example.domain.com."), DClass.IN, 3600, Name.fromString("ns1.example.domain.com."), + Name.fromString("gm2552.example.domain.com."), 1, 3600, 60, 60, 3600)); + + return addRecords; + } + + @Override + protected void doAssertions(List records) throws Exception + { + + assertNotNull(records); + assertEquals(1, records.size()); + Record rec = records.iterator().next(); + assertEquals(addRecord, rec); + + } + + }.perform(); + } + + public void testEnsureMultipleSOARecords_AssertRecordsAdded() throws Exception + { + new TestPlan() + { + private List addRecords; + + @Override + protected List getRecordsToAdd() throws Exception + { + addRecords = new ArrayList(); + addRecords.add(new SOARecord(Name.fromString("example.domain.com."), DClass.IN, 3600, Name.fromString("ns1.example.domain.com."), + Name.fromString("gm2552.example.domain.com."), 1, 3600, 60, 60, 3600)); + + addRecords.add(new SOARecord(Name.fromString("example2.domain.com."), DClass.IN, 3600, Name.fromString("ns1.example2.domain.com."), + Name.fromString("gm2552.example2.domain.com."), 1, 3600, 60, 60, 3600)); + + addRecords.add(new SOARecord(Name.fromString("example3.domain.com."), DClass.IN, 3600, Name.fromString("ns1.example3.domain.com."), + Name.fromString("gm2552.example3.domain.com."), 1, 3600, 60, 60, 3600)); + + return addRecords; + } + + + @Override + protected void doAssertions(List records) throws Exception + { + assertNotNull(records); + assertEquals(3, records.size()); + + for (Record record : addRecords) + { + int index = records.indexOf(record); + assertTrue(index > -1); + Record checkRecord = records.get(index); + assertEquals(record, checkRecord); + } + } + + }.perform(); + } + +} diff --git a/java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/tools/DNSRecordCommands_getAll_Test.java b/java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/tools/DNSRecordCommands_getAll_Test.java new file mode 100644 index 000000000..7b0edfa8a --- /dev/null +++ b/java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/tools/DNSRecordCommands_getAll_Test.java @@ -0,0 +1,235 @@ +package org.nhindirect.dns.tools; + +import java.net.InetAddress; +import java.net.URL; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.apache.axis.AxisFault; +import org.apache.mina.util.AvailablePortFinder; +import org.nhind.config.ConfigurationServiceProxy; +import org.nhind.config.DnsRecord; +import org.nhindirect.dns.ConfigServiceDNSStore; +import org.nhindirect.dns.DNSServer; +import org.nhindirect.dns.DNSServerSettings; +import org.nhindirect.dns.util.BaseTestPlan; +import org.nhindirect.dns.util.ConfigServiceRunner; +import org.xbill.DNS.ARecord; +import org.xbill.DNS.DClass; +import org.xbill.DNS.MXRecord; +import org.xbill.DNS.Name; +import org.xbill.DNS.Record; +import org.xbill.DNS.SOARecord; +import org.xbill.DNS.Type; + +import junit.framework.TestCase; + +public class DNSRecordCommands_getAll_Test extends TestCase +{ + abstract class TestPlan extends BaseTestPlan + { + protected MockDNSRecordPrinter recordPrinter; + + protected int port; + protected DNSServer server = null; + protected ConfigurationServiceProxy proxy; + protected DNSRecordCommands recordCommands; + + protected Record toRecord(DnsRecord rec) throws Exception + { + return Record.newRecord(Name.fromString(rec.getName()), rec.getType(), rec.getDclass(), rec.getTtl(), rec.getData()); + } + + protected DnsRecord fromRecord(Record rec) throws Exception + { + DnsRecord newRec = new DnsRecord(); + newRec.setData(rec.rdataToWireCanonical()); + newRec.setDclass(rec.getDClass()); + newRec.setName(rec.getName().toString()); + newRec.setTtl(rec.getTTL()); + newRec.setType(rec.getType()); + + return newRec; + } + + + @Override + protected void setupMocks() throws Exception + { + if (!ConfigServiceRunner.isServiceRunning()) + ConfigServiceRunner.startConfigService(); + + proxy = new ConfigurationServiceProxy(ConfigServiceRunner.getConfigServiceURL()); + + cleanRecords(); + + port = AvailablePortFinder.getNextAvailable(1024); + DNSServerSettings settings = new DNSServerSettings(); + settings.setPort(port); + + server = new DNSServer(new ConfigServiceDNSStore(new URL(ConfigServiceRunner.getConfigServiceURL())), settings); + + server.start(); + + recordPrinter = new MockDNSRecordPrinter(); + } + + @Override + protected void tearDownMocks() throws Exception + { + if (server != null) + server.stop(); + } + + @Override + protected void performInner() throws Exception + { + recordCommands = new DNSRecordCommands(proxy); + recordCommands.setRecordPrinter(recordPrinter); + + List recordsToAdd = getRecordsToAdd(); + + + if (recordsToAdd.size() > 0) + { + DnsRecord[] addRecs = new DnsRecord[recordsToAdd.size()]; + int cnt = 0; + for (Record recordToAdd : recordsToAdd) + { + addRecs[cnt++] = fromRecord(recordToAdd); + } + + proxy.addDNS(addRecs); + } + + recordCommands.getAll(new String[] {}); + + List matchedRecords = new ArrayList(); + for (DnsRecord matchedRecord : recordPrinter.printedRecords) + matchedRecords.add(toRecord(matchedRecord)); + + doAssertions(matchedRecords); + } + + private void cleanRecords() throws Exception + { + DnsRecord[] rec = proxy.getDNSByType(Type.ANY); + + if (rec != null && rec.length > 0) + proxy.removeDNS(rec); + + rec = proxy.getDNSByType(Type.ANY); + + assertNull(rec); + + } + + protected abstract List getRecordsToAdd() throws Exception; + + protected abstract void doAssertions(List recordsMatched) throws Exception; + } + + + public void testGetRecords_AssertAllRecordFetched() throws Exception + { + new TestPlan() + { + private List recordsToAdd; + + @Override + protected List getRecordsToAdd() throws Exception + { + recordsToAdd = new ArrayList(); + + recordsToAdd.add(new ARecord(Name.fromString("example.domain.com."), DClass.IN, 3600, InetAddress.getByName("127.0.0.1"))); + recordsToAdd.add(new ARecord(Name.fromString("example.domain.com."), DClass.IN, 3600, InetAddress.getByName("127.0.0.2"))); + + recordsToAdd.add(new MXRecord(Name.fromString("example.domain.com."), DClass.IN, 3600, + 1, Name.fromString("mail1.example.domain.com."))); + recordsToAdd.add(new MXRecord(Name.fromString("example.domain.com."), DClass.IN, 3600, + 1, Name.fromString("mail2.exampl2.domain.com."))); + + recordsToAdd.add(new SOARecord(Name.fromString("example.domain.com."), DClass.IN, 3600, Name.fromString("ns1.example.domain.com."), + Name.fromString("gm2552.example.domain.com."), 1, 3600, 60, 60, 3600)); + + + recordsToAdd.add(new ARecord(Name.fromString("domain.com."), DClass.IN, 3600, InetAddress.getByName("127.0.0.5"))); + + recordsToAdd.add(new MXRecord(Name.fromString("domain.com."), DClass.IN, 3600, + 1, Name.fromString("mail1.domain.com."))); + + recordsToAdd.add(new SOARecord(Name.fromString("domain.com."), DClass.IN, 3600, Name.fromString("ns1.domain.com."), + Name.fromString("gm2552.domain.com."), 1, 3600, 60, 60, 3600)); + + recordsToAdd.add(new ARecord(Name.fromString("example2.domain.com."), DClass.IN, 3600, InetAddress.getByName("127.0.0.5"))); + + return recordsToAdd; + } + + @Override + protected void doAssertions(List recordsMatched) throws Exception + { + assertNotNull(recordsMatched); + assertEquals(9, recordsMatched.size()); + + for (Record record : recordsToAdd) + { + int index = recordsToAdd.indexOf(record); + assertTrue(index > -1); + Record checkRecord = recordsToAdd.get(index); + assertEquals(record, checkRecord); + } + } + }.perform(); + } + + public void testGetRecords_emptyStore_AssertNoRecordsFetched() throws Exception + { + new TestPlan() + { + + @Override + protected List getRecordsToAdd() throws Exception + { + + return Collections.emptyList(); + } + + + @Override + protected void doAssertions(List recordsMatched) throws Exception + { + assertNotNull(recordsMatched); + assertEquals(0, recordsMatched.size()); + } + }.perform(); + } + + public void testFailureToFetchRecords_invalidProxy_AssertException() throws Exception + { + new TestPlan() + { + + @Override + protected List getRecordsToAdd() throws Exception + { + this.recordCommands.setConfigurationProxy(new ConfigurationServiceProxy("http://localhost:7777/bogusendpoint")); + return Collections.emptyList(); + } + + @Override + protected void assertException(Exception exception) throws Exception + { + assertNotNull(exception); + assertTrue(exception instanceof RuntimeException); + assertTrue(exception.getCause() instanceof AxisFault); + } + + @Override + protected void doAssertions(List recordsMatched) throws Exception + { + } + }.perform(); + } +} diff --git a/java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/tools/DNSRecordCommands_getByRecordId_Test.java b/java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/tools/DNSRecordCommands_getByRecordId_Test.java new file mode 100644 index 000000000..03caa99a1 --- /dev/null +++ b/java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/tools/DNSRecordCommands_getByRecordId_Test.java @@ -0,0 +1,420 @@ +package org.nhindirect.dns.tools; + +import java.net.InetAddress; +import java.net.URL; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import junit.framework.TestCase; + + +import org.apache.axis.AxisFault; +import org.apache.mina.util.AvailablePortFinder; + +import org.nhind.config.ConfigurationServiceProxy; +import org.nhind.config.DnsRecord; +import org.nhindirect.dns.ConfigServiceDNSStore; +import org.nhindirect.dns.DNSServer; +import org.nhindirect.dns.DNSServerSettings; +import org.nhindirect.dns.util.BaseTestPlan; +import org.nhindirect.dns.util.ConfigServiceRunner; +import org.xbill.DNS.ARecord; +import org.xbill.DNS.DClass; +import org.xbill.DNS.MXRecord; +import org.xbill.DNS.Name; +import org.xbill.DNS.Record; +import org.xbill.DNS.SOARecord; +import org.xbill.DNS.Type; + +public class DNSRecordCommands_getByRecordId_Test extends TestCase +{ + abstract class TestPlan extends BaseTestPlan + { + protected MockDNSRecordPrinter recordPrinter; + + protected int port; + protected DNSServer server = null; + protected ConfigurationServiceProxy proxy; + protected DNSRecordCommands recordCommands; + + protected Record toRecord(DnsRecord rec) throws Exception + { + return Record.newRecord(Name.fromString(rec.getName()), rec.getType(), rec.getDclass(), rec.getTtl(), rec.getData()); + } + + protected DnsRecord fromRecord(Record rec) throws Exception + { + DnsRecord newRec = new DnsRecord(); + newRec.setData(rec.rdataToWireCanonical()); + newRec.setDclass(rec.getDClass()); + newRec.setName(rec.getName().toString()); + newRec.setTtl(rec.getTTL()); + newRec.setType(rec.getType()); + + return newRec; + } + + protected List getRecordsInStore(int type) throws Exception + { + DnsRecord[] records = proxy.getDNSByType(type); + + List retVal; + + if (records == null || records.length == 0) + retVal = Collections.emptyList(); + else + { + retVal = new ArrayList(); + for (DnsRecord record : records) + retVal.add(toRecord(record)); + } + + return retVal; + } + + @Override + protected void setupMocks() throws Exception + { + if (!ConfigServiceRunner.isServiceRunning()) + ConfigServiceRunner.startConfigService(); + + proxy = new ConfigurationServiceProxy(ConfigServiceRunner.getConfigServiceURL()); + + cleanRecords(); + + port = AvailablePortFinder.getNextAvailable(1024); + DNSServerSettings settings = new DNSServerSettings(); + settings.setPort(port); + + server = new DNSServer(new ConfigServiceDNSStore(new URL(ConfigServiceRunner.getConfigServiceURL())), settings); + + server.start(); + + recordPrinter = new MockDNSRecordPrinter(); + } + + @Override + protected void tearDownMocks() throws Exception + { + if (server != null) + server.stop(); + } + + @Override + protected void performInner() throws Exception + { + recordCommands = new DNSRecordCommands(proxy); + recordCommands.setRecordPrinter(recordPrinter); + + List recordsToAdd = getRecordsToAdd(); + + + DnsRecord[] addRecs = new DnsRecord[recordsToAdd.size()]; + int cnt = 0; + for (Record recordToAdd : recordsToAdd) + { + addRecs[cnt++] = fromRecord(recordToAdd); + } + + proxy.addDNS(addRecs); + + addRecs = proxy.getDNSByType(Type.ANY); + + List recordsToFetch = getRecordsToFetch(Arrays.asList(addRecs)); + for (DnsRecord recordToRemove : recordsToFetch) + { + switch (recordToRemove.getType()) + { + case Type.A: + recordCommands.getANAME(new String[] {Long.toString(recordToRemove.getId())}); + break; + case Type.MX: + recordCommands.getMX(new String[] {Long.toString(recordToRemove.getId())}); + break; + case Type.SOA: + recordCommands.getSOA(new String[] {Long.toString(recordToRemove.getId())}); + break; + + } + } + + doAssertions(); + } + + private void cleanRecords() throws Exception + { + DnsRecord[] rec = proxy.getDNSByType(Type.ANY); + + if (rec != null && rec.length > 0) + proxy.removeDNS(rec); + + rec = proxy.getDNSByType(Type.ANY); + + assertNull(rec); + + } + + + protected abstract List getRecordsToAdd() throws Exception; + + protected abstract List getRecordsToFetch(List addedRecords) throws Exception; + + protected abstract void doAssertions() throws Exception; + } + + public void testGetAllRecords_AssertAllRecordFetched() throws Exception + { + new TestPlan() + { + @Override + protected List getRecordsToAdd() throws Exception + { + List recordsToAdd = new ArrayList(); + + recordsToAdd.add(new ARecord(Name.fromString("example.domain.com."), DClass.IN, 3600, InetAddress.getByName("127.0.0.1"))); + recordsToAdd.add(new ARecord(Name.fromString("example.domain.com."), DClass.IN, 3600, InetAddress.getByName("127.0.0.2"))); + recordsToAdd.add(new MXRecord(Name.fromString("example.domain.com."), DClass.IN, 3600, + 1, Name.fromString("mail1.example.domain.com."))); + recordsToAdd.add(new MXRecord(Name.fromString("example2.domain.com."), DClass.IN, 3600, + 1, Name.fromString("mail2.exampl2.domain.com."))); + + recordsToAdd.add(new SOARecord(Name.fromString("example.domain.com."), DClass.IN, 3600, Name.fromString("ns1.example.domain.com."), + Name.fromString("gm2552.example.domain.com."), 1, 3600, 60, 60, 3600)); + + return recordsToAdd; + } + + @Override + protected List getRecordsToFetch(List addedRecords) throws Exception + { + return addedRecords; + } + + @Override + protected void doAssertions() throws Exception + { + assertEquals(5, recordPrinter.printRecordCalled); + } + }.perform(); + } + + public void testGetOnlyARecords_AssertOnlyARecordsFetched() throws Exception + { + new TestPlan() + { + @Override + protected List getRecordsToAdd() throws Exception + { + List recordsToAdd = new ArrayList(); + + recordsToAdd.add(new ARecord(Name.fromString("example.domain.com."), DClass.IN, 3600, InetAddress.getByName("127.0.0.1"))); + recordsToAdd.add(new ARecord(Name.fromString("example.domain.com."), DClass.IN, 3600, InetAddress.getByName("127.0.0.2"))); + recordsToAdd.add(new ARecord(Name.fromString("example.domain.com."), DClass.IN, 3600, InetAddress.getByName("127.0.0.3"))); + recordsToAdd.add(new MXRecord(Name.fromString("example.domain.com."), DClass.IN, 3600, + 1, Name.fromString("mail1.example.domain.com."))); + recordsToAdd.add(new MXRecord(Name.fromString("example2.domain.com."), DClass.IN, 3600, + 1, Name.fromString("mail2.exampl2.domain.com."))); + + recordsToAdd.add(new SOARecord(Name.fromString("example.domain.com."), DClass.IN, 3600, Name.fromString("ns1.example.domain.com."), + Name.fromString("gm2552.example.domain.com."), 1, 3600, 60, 60, 3600)); + + return recordsToAdd; + } + + @Override + protected List getRecordsToFetch(List addedRecords) throws Exception + { + List retVal = new ArrayList(); + + for (DnsRecord addedRecord : addedRecords) + { + if (addedRecord.getType() == Type.A) + retVal.add(addedRecord); + } + + return retVal; + } + + @Override + protected void doAssertions() throws Exception + { + assertEquals(3, recordPrinter.printRecordCalled); + } + }.perform(); + } + + public void testGetAllExceptARecords_AssertNoARecordsFetched() throws Exception + { + new TestPlan() + { + @Override + protected List getRecordsToAdd() throws Exception + { + List recordsToAdd = new ArrayList(); + + recordsToAdd.add(new ARecord(Name.fromString("example.domain.com."), DClass.IN, 3600, InetAddress.getByName("127.0.0.1"))); + + recordsToAdd.add(new MXRecord(Name.fromString("example.domain.com."), DClass.IN, 3600, + 1, Name.fromString("mail1.example.domain.com."))); + recordsToAdd.add(new MXRecord(Name.fromString("example2.domain.com."), DClass.IN, 3600, + 1, Name.fromString("mail2.exampl2.domain.com."))); + + + recordsToAdd.add(new SOARecord(Name.fromString("example.domain.com."), DClass.IN, 3600, Name.fromString("ns1.example.domain.com."), + Name.fromString("gm2552.example.domain.com."), 1, 3600, 60, 60, 3600)); + + recordsToAdd.add(new SOARecord(Name.fromString("example2.domain.com."), DClass.IN, 3600, Name.fromString("ns1.example2.domain.com."), + Name.fromString("gm2552.example2.domain.com."), 1, 3600, 60, 60, 3600)); + + return recordsToAdd; + } + + @Override + protected List getRecordsToFetch(List addedRecords) throws Exception + { + List retVal = new ArrayList(); + + for (DnsRecord addedRecord : addedRecords) + { + if (addedRecord.getType() != Type.A) + retVal.add(addedRecord); + } + + return retVal; + } + + @Override + protected void doAssertions() throws Exception + { + assertEquals(4, recordPrinter.printRecordCalled); + } + }.perform(); + } + + public void testReturnNoRecords_noIdsProvided_AssertNoRecordsFetched() throws Exception + { + new TestPlan() + { + @Override + protected List getRecordsToAdd() throws Exception + { + List recordsToAdd = new ArrayList(); + + recordsToAdd.add(new ARecord(Name.fromString("example.domain.com."), DClass.IN, 3600, InetAddress.getByName("127.0.0.1"))); + + recordsToAdd.add(new MXRecord(Name.fromString("example.domain.com."), DClass.IN, 3600, + 1, Name.fromString("mail1.example.domain.com."))); + recordsToAdd.add(new MXRecord(Name.fromString("example2.domain.com."), DClass.IN, 3600, + 1, Name.fromString("mail2.exampl2.domain.com."))); + + + recordsToAdd.add(new SOARecord(Name.fromString("example.domain.com."), DClass.IN, 3600, Name.fromString("ns1.example.domain.com."), + Name.fromString("gm2552.example.domain.com."), 1, 3600, 60, 60, 3600)); + + recordsToAdd.add(new SOARecord(Name.fromString("example2.domain.com."), DClass.IN, 3600, Name.fromString("ns1.example2.domain.com."), + Name.fromString("gm2552.example2.domain.com."), 1, 3600, 60, 60, 3600)); + + return recordsToAdd; + } + + @Override + protected List getRecordsToFetch(List addedRecords) throws Exception + { + List retVal = Collections.emptyList(); + return retVal; + } + + @Override + protected void doAssertions() throws Exception + { + assertEquals(0, recordPrinter.printRecordCalled); + } + }.perform(); + } + + public void testReturnNoRecords_noMatchingIds_AssertNoRecordsFetched() throws Exception + { + new TestPlan() + { + private List recordsToAdd; + + @Override + protected List getRecordsToAdd() throws Exception + { + recordsToAdd = new ArrayList(); + + recordsToAdd.add(new ARecord(Name.fromString("example.domain.com."), DClass.IN, 3600, InetAddress.getByName("127.0.0.1"))); + + return recordsToAdd; + } + + @Override + protected List getRecordsToFetch(List addedRecords) throws Exception + { + DnsRecord largestRecord = null; + // find the record with the highest id and add 1 to it + for (DnsRecord record : addedRecords) + { + if (largestRecord == null || largestRecord.getId() < record.getId()) + largestRecord = record; + } + + largestRecord.setId(largestRecord.getId() + 1); + + return new ArrayList(Arrays.asList(largestRecord)); + } + + + @Override + protected void doAssertions() throws Exception + { + assertEquals(0, recordPrinter.printRecordCalled); + } + + }.perform(); + } + + public void testFailureToGetRecords_invalidProxy_AssertException() throws Exception + { + new TestPlan() + { + private List recordsToAdd; + + @Override + protected List getRecordsToAdd() throws Exception + { + recordsToAdd = new ArrayList(); + + recordsToAdd.add(new ARecord(Name.fromString("example.domain.com."), DClass.IN, 3600, InetAddress.getByName("127.0.0.1"))); + + return recordsToAdd; + } + + @Override + protected List getRecordsToFetch(List addedRecords) throws Exception + { + this.recordCommands.setConfigurationProxy(new ConfigurationServiceProxy("http://localhost:7777/bogusendpoint")); + return addedRecords; + } + + @Override + protected void assertException(Exception exception) throws Exception + { + assertNotNull(exception); + assertTrue(exception instanceof RuntimeException); + assertTrue(exception.getCause() instanceof AxisFault); + + + } + + @Override + protected void doAssertions() throws Exception + { + + } + + }.perform(); + } +} diff --git a/java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/tools/DNSRecordCommands_importRecords_Test.java b/java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/tools/DNSRecordCommands_importRecords_Test.java new file mode 100644 index 000000000..72803ae7e --- /dev/null +++ b/java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/tools/DNSRecordCommands_importRecords_Test.java @@ -0,0 +1,338 @@ +package org.nhindirect.dns.tools; + +import java.io.File; +import java.net.URL; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.apache.commons.io.FileUtils; +import org.apache.mina.util.AvailablePortFinder; +import org.nhind.config.ConfigurationServiceProxy; +import org.nhind.config.DnsRecord; +import org.nhindirect.dns.ConfigServiceDNSStore; +import org.nhindirect.dns.DNSServer; +import org.nhindirect.dns.DNSServerSettings; +import org.nhindirect.dns.util.BaseTestPlan; +import org.nhindirect.dns.util.ConfigServiceRunner; +import org.xbill.DNS.Name; +import org.xbill.DNS.Record; +import org.xbill.DNS.Section; +import org.xbill.DNS.Type; +import org.xbill.DNS.WireParseException; + +import junit.framework.TestCase; + +public class DNSRecordCommands_importRecords_Test extends TestCase +{ + abstract class TestPlan extends BaseTestPlan + { + protected int port; + protected DNSServer server = null; + protected ConfigurationServiceProxy proxy; + protected DNSRecordCommands recordCommands; + + protected List getRecordsInStore(int type) throws Exception + { + DnsRecord[] records = proxy.getDNSByType(type); + + List retVal; + + if (records == null || records.length == 0) + retVal = Collections.emptyList(); + else + { + retVal = new ArrayList(); + for (DnsRecord record : records) + retVal.add(toRecord(record)); + } + + return retVal; + } + + protected Record toRecord(DnsRecord rec) throws Exception + { + return Record.newRecord(Name.fromString(rec.getName()), rec.getType(), rec.getDclass(), rec.getTtl(), rec.getData()); + } + + protected Record fromFile(File file) throws Exception + { + return Record.fromWire(FileUtils.readFileToByteArray(file), Section.ANSWER); + } + + @Override + protected void setupMocks() throws Exception + { + if (!ConfigServiceRunner.isServiceRunning()) + ConfigServiceRunner.startConfigService(); + + proxy = new ConfigurationServiceProxy(ConfigServiceRunner.getConfigServiceURL()); + + cleanRecords(); + + port = AvailablePortFinder.getNextAvailable(1024); + DNSServerSettings settings = new DNSServerSettings(); + settings.setPort(port); + + server = new DNSServer(new ConfigServiceDNSStore(new URL(ConfigServiceRunner.getConfigServiceURL())), settings); + + server.start(); + + } + + @Override + protected void tearDownMocks() throws Exception + { + if (server != null) + server.stop(); + } + + @Override + protected void performInner() throws Exception + { + recordCommands = new DNSRecordCommands(proxy); + + int type = getRecordTypeToImport(); + + String[] fileToImport = {getRecordFileToImport().getAbsolutePath()}; + switch (type) + { + case Type.A: + recordCommands.importAddress(fileToImport); + break; + case Type.MX: + recordCommands.mXImport(fileToImport); + break; + case Type.SOA: + recordCommands.sOAImport(fileToImport); + break; + } + + + doAssertions(getRecordsInStore(type)); + } + + private void cleanRecords() throws Exception + { + DnsRecord[] rec = proxy.getDNSByType(Type.ANY); + + if (rec != null && rec.length > 0) + proxy.removeDNS(rec); + + rec = proxy.getDNSByType(Type.ANY); + + assertNull(rec); + + } + + + protected abstract File getRecordFileToImport() throws Exception; + + protected abstract int getRecordTypeToImport() throws Exception; + + protected abstract void doAssertions(List records) throws Exception; + } + + public void testImportARecord_AssertRecordAdded() throws Exception + { + new TestPlan() + { + private String fileName = "src/test/resources/dnsrecords/example.domain.com.a"; + + @Override + protected File getRecordFileToImport() throws Exception + { + return new File(fileName); + } + + @Override + protected int getRecordTypeToImport() throws Exception + { + return Type.A; + } + + @Override + protected void doAssertions(List records) throws Exception + { + assertNotNull(records); + assertEquals(1, records.size()); + + Record rec = fromFile(new File(fileName)); + Record checkRec = records.get(0); + assertEquals(checkRec, rec); + + } + + }.perform(); + } + + public void testImportMXRecord_AssertRecordAdded() throws Exception + { + new TestPlan() + { + private String fileName = "src/test/resources/dnsrecords/example.domain.com.mx"; + + @Override + protected File getRecordFileToImport() throws Exception + { + return new File(fileName); + } + + @Override + protected int getRecordTypeToImport() throws Exception + { + return Type.MX; + } + + @Override + protected void doAssertions(List records) throws Exception + { + assertNotNull(records); + assertEquals(1, records.size()); + + Record rec = fromFile(new File(fileName)); + Record checkRec = records.get(0); + assertEquals(checkRec, rec); + + } + + }.perform(); + } + + public void testImportSOARecord_AssertRecordAdded() throws Exception + { + new TestPlan() + { + private String fileName = "src/test/resources/dnsrecords/example.domain.com.soa"; + + @Override + protected File getRecordFileToImport() throws Exception + { + return new File(fileName); + } + + @Override + protected int getRecordTypeToImport() throws Exception + { + return Type.SOA; + } + + @Override + protected void doAssertions(List records) throws Exception + { + assertNotNull(records); + assertEquals(1, records.size()); + + Record rec = fromFile(new File(fileName)); + Record checkRec = records.get(0); + assertEquals(checkRec, rec); + + } + + }.perform(); + } + + public void testImportRecord_fileNotFound_AssertException() throws Exception + { + new TestPlan() + { + private String fileName = "src/test/resources/dnsrecords/example.domain.com.bogus"; + + @Override + protected File getRecordFileToImport() throws Exception + { + return new File(fileName); + } + + @Override + protected int getRecordTypeToImport() throws Exception + { + return Type.SOA; + } + + @Override + protected void assertException(Exception exception) throws Exception + { + assertNotNull(exception); + assertTrue(exception instanceof IllegalArgumentException); + } + + @Override + protected void doAssertions(List records) throws Exception + { + + } + + }.perform(); + } + + public void testImportRecord_corruptRecordFile_AssertException() throws Exception + { + new TestPlan() + { + private String fileName = "src/test/resources/dnsrecords/example.domain.com.corrupt"; + + @Override + protected File getRecordFileToImport() throws Exception + { + return new File(fileName); + } + + @Override + protected int getRecordTypeToImport() throws Exception + { + return Type.SOA; + } + + @Override + protected void assertException(Exception exception) throws Exception + { + assertNotNull(exception); + assertTrue(exception instanceof RuntimeException); + assertNotNull(exception.getCause()); + assertNotNull(exception.getCause() instanceof WireParseException); + } + + @Override + protected void doAssertions(List records) throws Exception + { + + } + + }.perform(); + } + + public void testImportRecord_incorrectType_AssertException() throws Exception + { + new TestPlan() + { + private String fileName = "src/test/resources/dnsrecords/example.domain.com.a"; + + @Override + protected File getRecordFileToImport() throws Exception + { + return new File(fileName); + } + + @Override + protected int getRecordTypeToImport() throws Exception + { + return Type.MX; + } + + @Override + protected void assertException(Exception exception) throws Exception + { + assertNotNull(exception); + assertTrue(exception instanceof IllegalArgumentException); + } + + @Override + protected void doAssertions(List records) throws Exception + { + + } + + }.perform(); + } +} diff --git a/java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/tools/DNSRecordCommands_matchARecords_Test.java b/java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/tools/DNSRecordCommands_matchARecords_Test.java new file mode 100644 index 000000000..b5f6324d9 --- /dev/null +++ b/java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/tools/DNSRecordCommands_matchARecords_Test.java @@ -0,0 +1,347 @@ +package org.nhindirect.dns.tools; + +import java.net.InetAddress; +import java.net.URL; +import java.util.ArrayList; +import java.util.List; + +import org.apache.axis.AxisFault; +import org.apache.mina.util.AvailablePortFinder; +import org.nhind.config.ConfigurationServiceProxy; +import org.nhind.config.DnsRecord; +import org.nhindirect.dns.ConfigServiceDNSStore; +import org.nhindirect.dns.DNSServer; +import org.nhindirect.dns.DNSServerSettings; +import org.nhindirect.dns.util.BaseTestPlan; +import org.nhindirect.dns.util.ConfigServiceRunner; +import org.xbill.DNS.ARecord; +import org.xbill.DNS.DClass; +import org.xbill.DNS.MXRecord; +import org.xbill.DNS.Name; +import org.xbill.DNS.Record; +import org.xbill.DNS.Type; + +import junit.framework.TestCase; + +public class DNSRecordCommands_matchARecords_Test extends TestCase +{ + abstract class TestPlan extends BaseTestPlan + { + protected MockDNSRecordPrinter recordPrinter; + + protected int port; + protected DNSServer server = null; + protected ConfigurationServiceProxy proxy; + protected DNSRecordCommands recordCommands; + + protected Record toRecord(DnsRecord rec) throws Exception + { + return Record.newRecord(Name.fromString(rec.getName()), rec.getType(), rec.getDclass(), rec.getTtl(), rec.getData()); + } + + protected DnsRecord fromRecord(Record rec) throws Exception + { + DnsRecord newRec = new DnsRecord(); + newRec.setData(rec.rdataToWireCanonical()); + newRec.setDclass(rec.getDClass()); + newRec.setName(rec.getName().toString()); + newRec.setTtl(rec.getTTL()); + newRec.setType(rec.getType()); + + return newRec; + } + + + @Override + protected void setupMocks() throws Exception + { + if (!ConfigServiceRunner.isServiceRunning()) + ConfigServiceRunner.startConfigService(); + + proxy = new ConfigurationServiceProxy(ConfigServiceRunner.getConfigServiceURL()); + + cleanRecords(); + + port = AvailablePortFinder.getNextAvailable(1024); + DNSServerSettings settings = new DNSServerSettings(); + settings.setPort(port); + + server = new DNSServer(new ConfigServiceDNSStore(new URL(ConfigServiceRunner.getConfigServiceURL())), settings); + + server.start(); + + recordPrinter = new MockDNSRecordPrinter(); + } + + @Override + protected void tearDownMocks() throws Exception + { + if (server != null) + server.stop(); + } + + @Override + protected void performInner() throws Exception + { + recordCommands = new DNSRecordCommands(proxy); + recordCommands.setRecordPrinter(recordPrinter); + + List recordsToAdd = getRecordsToAdd(); + + + DnsRecord[] addRecs = new DnsRecord[recordsToAdd.size()]; + int cnt = 0; + for (Record recordToAdd : recordsToAdd) + { + addRecs[cnt++] = fromRecord(recordToAdd); + } + + proxy.addDNS(addRecs); + + + String matchName = getNameToMatch(); + + recordCommands.matchAName(new String[] {matchName}); + + List matchedRecords = new ArrayList(); + for (DnsRecord matchedRecord : recordPrinter.printedRecords) + matchedRecords.add(toRecord(matchedRecord)); + + doAssertions(matchedRecords); + } + + private void cleanRecords() throws Exception + { + DnsRecord[] rec = proxy.getDNSByType(Type.ANY); + + if (rec != null && rec.length > 0) + proxy.removeDNS(rec); + + rec = proxy.getDNSByType(Type.ANY); + + assertNull(rec); + + } + + protected abstract List getRecordsToAdd() throws Exception; + + protected abstract String getNameToMatch() throws Exception; + + protected abstract void doAssertions(List recordsMatched) throws Exception; + } + + public void testMatchRecords_AssertAllRecordMatched() throws Exception + { + new TestPlan() + { + private List recordsToAdd; + + @Override + protected List getRecordsToAdd() throws Exception + { + recordsToAdd = new ArrayList(); + + recordsToAdd.add(new ARecord(Name.fromString("example.domain.com."), DClass.IN, 3600, InetAddress.getByName("127.0.0.1"))); + recordsToAdd.add(new ARecord(Name.fromString("example.domain.com."), DClass.IN, 3600, InetAddress.getByName("127.0.0.2"))); + + + return recordsToAdd; + } + + @Override + protected String getNameToMatch() throws Exception + { + return "example.domain.com"; + } + + @Override + protected void doAssertions(List recordsMatched) throws Exception + { + assertNotNull(recordsMatched); + assertEquals(2, recordsMatched.size()); + + for (Record record : recordsToAdd) + { + int index = recordsToAdd.indexOf(record); + assertTrue(index > -1); + Record checkRecord = recordsToAdd.get(index); + assertEquals(record, checkRecord); + } + } + }.perform(); + } + + public void testMatchRecords_aRecordsOnly_AssertOnlyARecordMatched() throws Exception + { + new TestPlan() + { + private List recordsToAdd; + private List expectedMatchedRecords; + @Override + protected List getRecordsToAdd() throws Exception + { + recordsToAdd = new ArrayList(); + expectedMatchedRecords = new ArrayList(); + + Record recToAdd = new ARecord(Name.fromString("example.domain.com."), DClass.IN, 3600, InetAddress.getByName("127.0.0.1")); + recordsToAdd.add(recToAdd); + expectedMatchedRecords.add(recToAdd); + + + recToAdd = new ARecord(Name.fromString("example.domain.com."), DClass.IN, 3600, InetAddress.getByName("127.0.0.2")); + recordsToAdd.add(recToAdd); + expectedMatchedRecords.add(recToAdd); + + recordsToAdd.add(new MXRecord(Name.fromString("example.domain.com."), DClass.IN, 3600, + 1, Name.fromString("mail1.example.domain.com."))); + + return recordsToAdd; + } + + @Override + protected String getNameToMatch() throws Exception + { + return "example.domain.com"; + } + + @Override + protected void doAssertions(List recordsMatched) throws Exception + { + assertNotNull(recordsMatched); + assertEquals(2, recordsMatched.size()); + + for (Record record : expectedMatchedRecords) + { + int index = recordsToAdd.indexOf(record); + assertTrue(index > -1); + Record checkRecord = recordsToAdd.get(index); + assertEquals(record, checkRecord); + } + } + }.perform(); + } + + public void testMatchRecords_specificDomain_AssertOnlyDomainRecordMatched() throws Exception + { + new TestPlan() + { + private List recordsToAdd; + private List expectedMatchedRecords; + + @Override + protected List getRecordsToAdd() throws Exception + { + recordsToAdd = new ArrayList(); + expectedMatchedRecords = new ArrayList(); + + Record recToAdd = new ARecord(Name.fromString("example.domain.com."), DClass.IN, 3600, InetAddress.getByName("127.0.0.1")); + recordsToAdd.add(recToAdd); + expectedMatchedRecords.add(recToAdd); + + + recToAdd = new ARecord(Name.fromString("example.domain.com."), DClass.IN, 3600, InetAddress.getByName("127.0.0.2")); + recordsToAdd.add(recToAdd); + expectedMatchedRecords.add(recToAdd); + + recordsToAdd.add(new ARecord(Name.fromString("domain.com."), DClass.IN, 3600, InetAddress.getByName("127.0.0.3"))); + + + return recordsToAdd; + } + + @Override + protected String getNameToMatch() throws Exception + { + return "example.domain.com"; + } + + @Override + protected void doAssertions(List recordsMatched) throws Exception + { + assertNotNull(recordsMatched); + assertEquals(2, recordsMatched.size()); + + for (Record record : expectedMatchedRecords) + { + int index = recordsToAdd.indexOf(record); + assertTrue(index > -1); + Record checkRecord = recordsToAdd.get(index); + assertEquals(record, checkRecord); + } + } + }.perform(); + } + + public void testMatchNoRecords_matchingParentDomain_AssertNoRecordMatched() throws Exception + { + new TestPlan() + { + private List recordsToAdd; + + @Override + protected List getRecordsToAdd() throws Exception + { + recordsToAdd = new ArrayList(); + + recordsToAdd.add(new ARecord(Name.fromString("example.domain.com."), DClass.IN, 3600, InetAddress.getByName("127.0.0.1"))); + recordsToAdd.add(new ARecord(Name.fromString("example.domain.com."), DClass.IN, 3600, InetAddress.getByName("127.0.0.2"))); + + + return recordsToAdd; + } + + @Override + protected String getNameToMatch() throws Exception + { + return "domain.com"; + } + + @Override + protected void doAssertions(List recordsMatched) throws Exception + { + assertNotNull(recordsMatched); + assertEquals(0, recordsMatched.size()); + + } + }.perform(); + } + + public void testFailureToMatch_invalidProxy_AssertException() throws Exception + { + new TestPlan() + { + + @Override + protected List getRecordsToAdd() throws Exception + { + ArrayList recordsToAdd = new ArrayList(); + + recordsToAdd.add(new ARecord(Name.fromString("example.domain.com."), DClass.IN, 3600, InetAddress.getByName("127.0.0.1"))); + + return recordsToAdd; + } + + @Override + protected String getNameToMatch() throws Exception + { + recordCommands.setConfigurationProxy(new ConfigurationServiceProxy("http://localhost:7777/bogusendpoint")); + return "example.domain.com"; + } + + @Override + protected void assertException(Exception exception) throws Exception + { + assertNotNull(exception); + assertTrue(exception instanceof RuntimeException); + assertTrue(exception.getCause() instanceof AxisFault); + + } + + @Override + protected void doAssertions(List recordsMatched) throws Exception + { + } + + }.perform(); + } +} diff --git a/java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/tools/DNSRecordCommands_matchAllTypes_Test.java b/java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/tools/DNSRecordCommands_matchAllTypes_Test.java new file mode 100644 index 000000000..d71112b31 --- /dev/null +++ b/java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/tools/DNSRecordCommands_matchAllTypes_Test.java @@ -0,0 +1,399 @@ +package org.nhindirect.dns.tools; + +import java.net.InetAddress; +import java.net.URL; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import junit.framework.TestCase; + +import org.apache.axis.AxisFault; +import org.apache.mina.util.AvailablePortFinder; +import org.nhind.config.ConfigurationServiceProxy; +import org.nhind.config.DnsRecord; +import org.nhindirect.dns.ConfigServiceDNSStore; +import org.nhindirect.dns.DNSServer; +import org.nhindirect.dns.DNSServerSettings; +import org.nhindirect.dns.util.BaseTestPlan; +import org.nhindirect.dns.util.ConfigServiceRunner; +import org.xbill.DNS.ARecord; +import org.xbill.DNS.DClass; +import org.xbill.DNS.MXRecord; +import org.xbill.DNS.Name; +import org.xbill.DNS.Record; +import org.xbill.DNS.SOARecord; +import org.xbill.DNS.Type; + +public class DNSRecordCommands_matchAllTypes_Test extends TestCase +{ + abstract class TestPlan extends BaseTestPlan + { + protected MockDNSRecordPrinter recordPrinter; + + protected int port; + protected DNSServer server = null; + protected ConfigurationServiceProxy proxy; + protected DNSRecordCommands recordCommands; + + protected Record toRecord(DnsRecord rec) throws Exception + { + return Record.newRecord(Name.fromString(rec.getName()), rec.getType(), rec.getDclass(), rec.getTtl(), rec.getData()); + } + + protected DnsRecord fromRecord(Record rec) throws Exception + { + DnsRecord newRec = new DnsRecord(); + newRec.setData(rec.rdataToWireCanonical()); + newRec.setDclass(rec.getDClass()); + newRec.setName(rec.getName().toString()); + newRec.setTtl(rec.getTTL()); + newRec.setType(rec.getType()); + + return newRec; + } + + + @Override + protected void setupMocks() throws Exception + { + if (!ConfigServiceRunner.isServiceRunning()) + ConfigServiceRunner.startConfigService(); + + proxy = new ConfigurationServiceProxy(ConfigServiceRunner.getConfigServiceURL()); + + cleanRecords(); + + port = AvailablePortFinder.getNextAvailable(1024); + DNSServerSettings settings = new DNSServerSettings(); + settings.setPort(port); + + server = new DNSServer(new ConfigServiceDNSStore(new URL(ConfigServiceRunner.getConfigServiceURL())), settings); + + server.start(); + + recordPrinter = new MockDNSRecordPrinter(); + } + + @Override + protected void tearDownMocks() throws Exception + { + if (server != null) + server.stop(); + } + + @Override + protected void performInner() throws Exception + { + recordCommands = new DNSRecordCommands(proxy); + recordCommands.setRecordPrinter(recordPrinter); + + List recordsToAdd = getRecordsToAdd(); + + + DnsRecord[] addRecs = new DnsRecord[recordsToAdd.size()]; + int cnt = 0; + for (Record recordToAdd : recordsToAdd) + { + addRecs[cnt++] = fromRecord(recordToAdd); + } + + proxy.addDNS(addRecs); + + + String matchName = getNameToMatch(); + + recordCommands.match(new String[] {matchName}); + + List matchedRecords = new ArrayList(); + for (DnsRecord matchedRecord : recordPrinter.printedRecords) + matchedRecords.add(toRecord(matchedRecord)); + + doAssertions(matchedRecords); + } + + private void cleanRecords() throws Exception + { + DnsRecord[] rec = proxy.getDNSByType(Type.ANY); + + if (rec != null && rec.length > 0) + proxy.removeDNS(rec); + + rec = proxy.getDNSByType(Type.ANY); + + assertNull(rec); + + } + + protected abstract List getRecordsToAdd() throws Exception; + + protected abstract String getNameToMatch() throws Exception; + + protected abstract void doAssertions(List recordsMatched) throws Exception; + } + + public void testMatchRecords_AssertAllRecordMatched() throws Exception + { + new TestPlan() + { + private List recordsToAdd; + + @Override + protected List getRecordsToAdd() throws Exception + { + recordsToAdd = new ArrayList(); + + recordsToAdd.add(new ARecord(Name.fromString("example.domain.com."), DClass.IN, 3600, InetAddress.getByName("127.0.0.1"))); + recordsToAdd.add(new ARecord(Name.fromString("example.domain.com."), DClass.IN, 3600, InetAddress.getByName("127.0.0.2"))); + recordsToAdd.add(new MXRecord(Name.fromString("example.domain.com."), DClass.IN, 3600, + 1, Name.fromString("mail1.example.domain.com."))); + recordsToAdd.add(new MXRecord(Name.fromString("example.domain.com."), DClass.IN, 3600, + 1, Name.fromString("mail2.exampl2.domain.com."))); + + recordsToAdd.add(new SOARecord(Name.fromString("example.domain.com."), DClass.IN, 3600, Name.fromString("ns1.example.domain.com."), + Name.fromString("gm2552.example.domain.com."), 1, 3600, 60, 60, 3600)); + + return recordsToAdd; + } + + @Override + protected String getNameToMatch() throws Exception + { + return "example.domain.com"; + } + + @Override + protected void doAssertions(List recordsMatched) throws Exception + { + assertNotNull(recordsMatched); + assertEquals(5, recordsMatched.size()); + + for (Record record : recordsToAdd) + { + int index = recordsToAdd.indexOf(record); + assertTrue(index > -1); + Record checkRecord = recordsToAdd.get(index); + assertEquals(record, checkRecord); + } + } + }.perform(); + } + + + public void testMatchRecords_subdomainSearch_AssertOnlySubDomainRecordsMatched() throws Exception + { + new TestPlan() + { + private List recordsToAdd; + + @Override + protected List getRecordsToAdd() throws Exception + { + recordsToAdd = new ArrayList(); + + recordsToAdd.add(new ARecord(Name.fromString("example.domain.com."), DClass.IN, 3600, InetAddress.getByName("127.0.0.1"))); + recordsToAdd.add(new ARecord(Name.fromString("example.domain.com."), DClass.IN, 3600, InetAddress.getByName("127.0.0.2"))); + + recordsToAdd.add(new MXRecord(Name.fromString("example.domain.com."), DClass.IN, 3600, + 1, Name.fromString("mail1.example.domain.com."))); + recordsToAdd.add(new MXRecord(Name.fromString("example.domain.com."), DClass.IN, 3600, + 1, Name.fromString("mail2.exampl2.domain.com."))); + + recordsToAdd.add(new SOARecord(Name.fromString("example.domain.com."), DClass.IN, 3600, Name.fromString("ns1.example.domain.com."), + Name.fromString("gm2552.example.domain.com."), 1, 3600, 60, 60, 3600)); + + + recordsToAdd.add(new ARecord(Name.fromString("domain.com."), DClass.IN, 3600, InetAddress.getByName("127.0.0.5"))); + + recordsToAdd.add(new MXRecord(Name.fromString("domain.com."), DClass.IN, 3600, + 1, Name.fromString("mail1.domain.com."))); + + recordsToAdd.add(new SOARecord(Name.fromString("domain.com."), DClass.IN, 3600, Name.fromString("ns1.domain.com."), + Name.fromString("gm2552.domain.com."), 1, 3600, 60, 60, 3600)); + + recordsToAdd.add(new ARecord(Name.fromString("example2.domain.com."), DClass.IN, 3600, InetAddress.getByName("127.0.0.5"))); + + return recordsToAdd; + } + + @Override + protected String getNameToMatch() throws Exception + { + return "example.domain.com"; + } + + @Override + protected void doAssertions(List recordsMatched) throws Exception + { + assertNotNull(recordsMatched); + assertEquals(5, recordsMatched.size()); + + for (Record record : recordsToAdd) + { + int index = recordsToAdd.indexOf(record); + assertTrue(index > -1); + Record checkRecord = recordsToAdd.get(index); + assertEquals(record, checkRecord); + } + } + }.perform(); + } + + public void testMatchRecords_parentSearch_AssertParentAndChildDomainRecordsMatched() throws Exception + { + new TestPlan() + { + private List recordsToAdd; + + @Override + protected List getRecordsToAdd() throws Exception + { + recordsToAdd = new ArrayList(); + + recordsToAdd.add(new ARecord(Name.fromString("example.domain.com."), DClass.IN, 3600, InetAddress.getByName("127.0.0.1"))); + recordsToAdd.add(new ARecord(Name.fromString("example.domain.com."), DClass.IN, 3600, InetAddress.getByName("127.0.0.2"))); + + recordsToAdd.add(new MXRecord(Name.fromString("example.domain.com."), DClass.IN, 3600, + 1, Name.fromString("mail1.example.domain.com."))); + recordsToAdd.add(new MXRecord(Name.fromString("example.domain.com."), DClass.IN, 3600, + 1, Name.fromString("mail2.exampl2.domain.com."))); + + recordsToAdd.add(new SOARecord(Name.fromString("example.domain.com."), DClass.IN, 3600, Name.fromString("ns1.example.domain.com."), + Name.fromString("gm2552.example.domain.com."), 1, 3600, 60, 60, 3600)); + + + recordsToAdd.add(new ARecord(Name.fromString("domain.com."), DClass.IN, 3600, InetAddress.getByName("127.0.0.5"))); + + recordsToAdd.add(new MXRecord(Name.fromString("domain.com."), DClass.IN, 3600, + 1, Name.fromString("mail1.domain.com."))); + + recordsToAdd.add(new SOARecord(Name.fromString("domain.com."), DClass.IN, 3600, Name.fromString("ns1.domain.com."), + Name.fromString("gm2552.domain.com."), 1, 3600, 60, 60, 3600)); + + recordsToAdd.add(new ARecord(Name.fromString("example2.domain.com."), DClass.IN, 3600, InetAddress.getByName("127.0.0.5"))); + + return recordsToAdd; + } + + @Override + protected String getNameToMatch() throws Exception + { + return "domain.com"; + } + + @Override + protected void doAssertions(List recordsMatched) throws Exception + { + assertNotNull(recordsMatched); + assertEquals(9, recordsMatched.size()); + + for (Record record : recordsToAdd) + { + int index = recordsToAdd.indexOf(record); + assertTrue(index > -1); + Record checkRecord = recordsToAdd.get(index); + assertEquals(record, checkRecord); + } + } + }.perform(); + } + + public void testMatchRecords_subdomainSearch_AssertNoRecordsMatched() throws Exception + { + new TestPlan() + { + private List recordsToAdd; + + @Override + protected List getRecordsToAdd() throws Exception + { + recordsToAdd = new ArrayList(); + + recordsToAdd.add(new ARecord(Name.fromString("domain.com."), DClass.IN, 3600, InetAddress.getByName("127.0.0.5"))); + + recordsToAdd.add(new MXRecord(Name.fromString("domain.com."), DClass.IN, 3600, + 1, Name.fromString("mail1.domain.com."))); + + recordsToAdd.add(new SOARecord(Name.fromString("domain.com."), DClass.IN, 3600, Name.fromString("ns1.domain.com."), + Name.fromString("gm2552.domain.com."), 1, 3600, 60, 60, 3600)); + + recordsToAdd.add(new ARecord(Name.fromString("example2.domain.com."), DClass.IN, 3600, InetAddress.getByName("127.0.0.5"))); + + return recordsToAdd; + } + + @Override + protected String getNameToMatch() throws Exception + { + return "example.domain.com"; + } + + @Override + protected void doAssertions(List recordsMatched) throws Exception + { + assertNotNull(recordsMatched); + assertEquals(0, recordsMatched.size()); + } + }.perform(); + } + + public void testMatchRecords_emptyStore_AssertNoRecordsMatched() throws Exception + { + new TestPlan() + { + + @Override + protected List getRecordsToAdd() throws Exception + { + + return Collections.emptyList(); + } + + @Override + protected String getNameToMatch() throws Exception + { + return "example.domain.com"; + } + + @Override + protected void doAssertions(List recordsMatched) throws Exception + { + assertNotNull(recordsMatched); + assertEquals(0, recordsMatched.size()); + } + }.perform(); + } + + public void testFailureToMatchRecords_invalidProxy_AssertException() throws Exception + { + new TestPlan() + { + + @Override + protected List getRecordsToAdd() throws Exception + { + return Collections.emptyList(); + } + + @Override + protected String getNameToMatch() throws Exception + { + this.recordCommands.setConfigurationProxy(new ConfigurationServiceProxy("http://localhost:7777/bogusendpoint")); + return "example.domain.com"; + } + + @Override + protected void assertException(Exception exception) throws Exception + { + assertNotNull(exception); + assertTrue(exception instanceof RuntimeException); + assertTrue(exception.getCause() instanceof AxisFault); + } + + @Override + protected void doAssertions(List recordsMatched) throws Exception + { + } + }.perform(); + } +} diff --git a/java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/tools/DNSRecordCommands_matchMXRecords_Test.java b/java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/tools/DNSRecordCommands_matchMXRecords_Test.java new file mode 100644 index 000000000..dfee98d2f --- /dev/null +++ b/java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/tools/DNSRecordCommands_matchMXRecords_Test.java @@ -0,0 +1,363 @@ +package org.nhindirect.dns.tools; + +import java.net.InetAddress; +import java.net.URL; +import java.util.ArrayList; +import java.util.List; + +import org.apache.axis.AxisFault; +import org.apache.mina.util.AvailablePortFinder; +import org.nhind.config.ConfigurationServiceProxy; +import org.nhind.config.DnsRecord; +import org.nhindirect.dns.ConfigServiceDNSStore; +import org.nhindirect.dns.DNSServer; +import org.nhindirect.dns.DNSServerSettings; +import org.nhindirect.dns.util.BaseTestPlan; +import org.nhindirect.dns.util.ConfigServiceRunner; +import org.xbill.DNS.ARecord; +import org.xbill.DNS.DClass; +import org.xbill.DNS.MXRecord; +import org.xbill.DNS.Name; +import org.xbill.DNS.Record; +import org.xbill.DNS.Type; + +import junit.framework.TestCase; + +public class DNSRecordCommands_matchMXRecords_Test extends TestCase +{ + abstract class TestPlan extends BaseTestPlan + { + protected MockDNSRecordPrinter recordPrinter; + + protected int port; + protected DNSServer server = null; + protected ConfigurationServiceProxy proxy; + protected DNSRecordCommands recordCommands; + + protected Record toRecord(DnsRecord rec) throws Exception + { + return Record.newRecord(Name.fromString(rec.getName()), rec.getType(), rec.getDclass(), rec.getTtl(), rec.getData()); + } + + protected DnsRecord fromRecord(Record rec) throws Exception + { + DnsRecord newRec = new DnsRecord(); + newRec.setData(rec.rdataToWireCanonical()); + newRec.setDclass(rec.getDClass()); + newRec.setName(rec.getName().toString()); + newRec.setTtl(rec.getTTL()); + newRec.setType(rec.getType()); + + return newRec; + } + + + @Override + protected void setupMocks() throws Exception + { + if (!ConfigServiceRunner.isServiceRunning()) + ConfigServiceRunner.startConfigService(); + + proxy = new ConfigurationServiceProxy(ConfigServiceRunner.getConfigServiceURL()); + + cleanRecords(); + + port = AvailablePortFinder.getNextAvailable(1024); + DNSServerSettings settings = new DNSServerSettings(); + settings.setPort(port); + + server = new DNSServer(new ConfigServiceDNSStore(new URL(ConfigServiceRunner.getConfigServiceURL())), settings); + + server.start(); + + recordPrinter = new MockDNSRecordPrinter(); + } + + @Override + protected void tearDownMocks() throws Exception + { + if (server != null) + server.stop(); + } + + @Override + protected void performInner() throws Exception + { + recordCommands = new DNSRecordCommands(proxy); + recordCommands.setRecordPrinter(recordPrinter); + + List recordsToAdd = getRecordsToAdd(); + + + DnsRecord[] addRecs = new DnsRecord[recordsToAdd.size()]; + int cnt = 0; + for (Record recordToAdd : recordsToAdd) + { + addRecs[cnt++] = fromRecord(recordToAdd); + } + + proxy.addDNS(addRecs); + + + String matchName = getNameToMatch(); + + recordCommands.matchMX(new String[] {matchName}); + + List matchedRecords = new ArrayList(); + for (DnsRecord matchedRecord : recordPrinter.printedRecords) + matchedRecords.add(toRecord(matchedRecord)); + + doAssertions(matchedRecords); + } + + private void cleanRecords() throws Exception + { + DnsRecord[] rec = proxy.getDNSByType(Type.ANY); + + if (rec != null && rec.length > 0) + proxy.removeDNS(rec); + + rec = proxy.getDNSByType(Type.ANY); + + assertNull(rec); + + } + + protected abstract List getRecordsToAdd() throws Exception; + + protected abstract String getNameToMatch() throws Exception; + + protected abstract void doAssertions(List recordsMatched) throws Exception; + } + + public void testMatchRecords_AssertAllRecordMatched() throws Exception + { + new TestPlan() + { + private List recordsToAdd; + + @Override + protected List getRecordsToAdd() throws Exception + { + recordsToAdd = new ArrayList(); + + + recordsToAdd.add(new MXRecord(Name.fromString("example.domain.com."), DClass.IN, 3600, + 1, Name.fromString("mail1.example.domain.com."))); + + recordsToAdd.add(new MXRecord(Name.fromString("example.domain.com."), DClass.IN, 3600, + 1, Name.fromString("mail2.example.domain.com."))); + + + return recordsToAdd; + } + + @Override + protected String getNameToMatch() throws Exception + { + return "example.domain.com"; + } + + @Override + protected void doAssertions(List recordsMatched) throws Exception + { + assertNotNull(recordsMatched); + assertEquals(2, recordsMatched.size()); + + for (Record record : recordsToAdd) + { + int index = recordsToAdd.indexOf(record); + assertTrue(index > -1); + Record checkRecord = recordsToAdd.get(index); + assertEquals(record, checkRecord); + } + } + }.perform(); + } + + + public void testMatchRecords_mxRecordsOnly_AssertOnlyMXRecordMatched() throws Exception + { + new TestPlan() + { + private List recordsToAdd; + private List expectedMatchedRecords; + @Override + protected List getRecordsToAdd() throws Exception + { + recordsToAdd = new ArrayList(); + expectedMatchedRecords = new ArrayList(); + + Record recToAdd = new MXRecord(Name.fromString("example.domain.com."), DClass.IN, 3600, + 1, Name.fromString("mail1.example.domain.com.")); + recordsToAdd.add(recToAdd); + expectedMatchedRecords.add(recToAdd); + + recToAdd = new MXRecord(Name.fromString("example.domain.com."), DClass.IN, 3600, + 1, Name.fromString("mail2.example.domain.com.")); + recordsToAdd.add(recToAdd); + expectedMatchedRecords.add(recToAdd); + + recordsToAdd.add(new ARecord(Name.fromString("example.domain.com."), DClass.IN, 3600, InetAddress.getByName("127.0.0.1"))); + + return recordsToAdd; + } + + @Override + protected String getNameToMatch() throws Exception + { + return "example.domain.com"; + } + + @Override + protected void doAssertions(List recordsMatched) throws Exception + { + assertNotNull(recordsMatched); + assertEquals(2, recordsMatched.size()); + + for (Record record : expectedMatchedRecords) + { + int index = recordsToAdd.indexOf(record); + assertTrue(index > -1); + Record checkRecord = recordsToAdd.get(index); + assertEquals(record, checkRecord); + } + } + }.perform(); + } + + + public void testMatchRecords_specificDomain_AssertOnlyDomainRecordMatched() throws Exception + { + new TestPlan() + { + private List recordsToAdd; + private List expectedMatchedRecords; + + @Override + protected List getRecordsToAdd() throws Exception + { + recordsToAdd = new ArrayList(); + expectedMatchedRecords = new ArrayList(); + + Record recToAdd = new MXRecord(Name.fromString("example.domain.com."), DClass.IN, 3600, + 1, Name.fromString("mail1.example.domain.com.")); + recordsToAdd.add(recToAdd); + expectedMatchedRecords.add(recToAdd); + + recToAdd = new MXRecord(Name.fromString("example.domain.com."), DClass.IN, 3600, + 1, Name.fromString("mail2.example.domain.com.")); + recordsToAdd.add(recToAdd); + expectedMatchedRecords.add(recToAdd); + + recordsToAdd.add(new MXRecord(Name.fromString("example2.domain.com."), DClass.IN, 3600, + 1, Name.fromString("mail1.example2.domain.com."))); + + return recordsToAdd; + } + + @Override + protected String getNameToMatch() throws Exception + { + return "example.domain.com"; + } + + @Override + protected void doAssertions(List recordsMatched) throws Exception + { + assertNotNull(recordsMatched); + assertEquals(2, recordsMatched.size()); + + for (Record record : expectedMatchedRecords) + { + int index = recordsToAdd.indexOf(record); + assertTrue(index > -1); + Record checkRecord = recordsToAdd.get(index); + assertEquals(record, checkRecord); + } + } + }.perform(); + } + + + public void testMatchNoRecords_matchingParentDomain_AssertNoRecordMatched() throws Exception + { + new TestPlan() + { + private List recordsToAdd; + + @Override + protected List getRecordsToAdd() throws Exception + { + recordsToAdd = new ArrayList(); + + + recordsToAdd.add(new MXRecord(Name.fromString("example.domain.com."), DClass.IN, 3600, + 1, Name.fromString("mail1.example.domain.com."))); + + recordsToAdd.add(new MXRecord(Name.fromString("example.domain.com."), DClass.IN, 3600, + 1, Name.fromString("mail2.example.domain.com."))); + + + return recordsToAdd; + } + + @Override + protected String getNameToMatch() throws Exception + { + return "domain.com"; + } + + @Override + protected void doAssertions(List recordsMatched) throws Exception + { + assertNotNull(recordsMatched); + assertEquals(0, recordsMatched.size()); + + } + }.perform(); + } + + + public void testFailureToMatch_invalidProxy_AssertException() throws Exception + { + new TestPlan() + { + + @Override + protected List getRecordsToAdd() throws Exception + { + ArrayList recordsToAdd = new ArrayList(); + + + recordsToAdd.add(new MXRecord(Name.fromString("example.domain.com."), DClass.IN, 3600, + 1, Name.fromString("mail1.example.domain.com."))); + + return recordsToAdd; + } + + @Override + protected String getNameToMatch() throws Exception + { + recordCommands.setConfigurationProxy(new ConfigurationServiceProxy("http://localhost:7777/bogusendpoint")); + return "example.domain.com"; + } + + @Override + protected void assertException(Exception exception) throws Exception + { + assertNotNull(exception); + assertTrue(exception instanceof RuntimeException); + assertTrue(exception.getCause() instanceof AxisFault); + + } + + @Override + protected void doAssertions(List recordsMatched) throws Exception + { + } + + }.perform(); + } + +} diff --git a/java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/tools/DNSRecordCommands_matchSOARecords_Test.java b/java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/tools/DNSRecordCommands_matchSOARecords_Test.java new file mode 100644 index 000000000..a51a9adeb --- /dev/null +++ b/java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/tools/DNSRecordCommands_matchSOARecords_Test.java @@ -0,0 +1,366 @@ +package org.nhindirect.dns.tools; + +import java.net.InetAddress; +import java.net.URL; +import java.util.ArrayList; +import java.util.List; + +import org.apache.axis.AxisFault; +import org.apache.mina.util.AvailablePortFinder; +import org.nhind.config.ConfigurationServiceProxy; +import org.nhind.config.DnsRecord; +import org.nhindirect.dns.ConfigServiceDNSStore; +import org.nhindirect.dns.DNSServer; +import org.nhindirect.dns.DNSServerSettings; +import org.nhindirect.dns.util.BaseTestPlan; +import org.nhindirect.dns.util.ConfigServiceRunner; +import org.xbill.DNS.ARecord; +import org.xbill.DNS.DClass; +import org.xbill.DNS.MXRecord; +import org.xbill.DNS.Name; +import org.xbill.DNS.Record; +import org.xbill.DNS.SOARecord; +import org.xbill.DNS.Type; + +import junit.framework.TestCase; + +public class DNSRecordCommands_matchSOARecords_Test extends TestCase +{ + abstract class TestPlan extends BaseTestPlan + { + protected MockDNSRecordPrinter recordPrinter; + + protected int port; + protected DNSServer server = null; + protected ConfigurationServiceProxy proxy; + protected DNSRecordCommands recordCommands; + + protected Record toRecord(DnsRecord rec) throws Exception + { + return Record.newRecord(Name.fromString(rec.getName()), rec.getType(), rec.getDclass(), rec.getTtl(), rec.getData()); + } + + protected DnsRecord fromRecord(Record rec) throws Exception + { + DnsRecord newRec = new DnsRecord(); + newRec.setData(rec.rdataToWireCanonical()); + newRec.setDclass(rec.getDClass()); + newRec.setName(rec.getName().toString()); + newRec.setTtl(rec.getTTL()); + newRec.setType(rec.getType()); + + return newRec; + } + + + @Override + protected void setupMocks() throws Exception + { + if (!ConfigServiceRunner.isServiceRunning()) + ConfigServiceRunner.startConfigService(); + + proxy = new ConfigurationServiceProxy(ConfigServiceRunner.getConfigServiceURL()); + + cleanRecords(); + + port = AvailablePortFinder.getNextAvailable(1024); + DNSServerSettings settings = new DNSServerSettings(); + settings.setPort(port); + + server = new DNSServer(new ConfigServiceDNSStore(new URL(ConfigServiceRunner.getConfigServiceURL())), settings); + + server.start(); + + recordPrinter = new MockDNSRecordPrinter(); + } + + @Override + protected void tearDownMocks() throws Exception + { + if (server != null) + server.stop(); + } + + @Override + protected void performInner() throws Exception + { + recordCommands = new DNSRecordCommands(proxy); + recordCommands.setRecordPrinter(recordPrinter); + + List recordsToAdd = getRecordsToAdd(); + + + DnsRecord[] addRecs = new DnsRecord[recordsToAdd.size()]; + int cnt = 0; + for (Record recordToAdd : recordsToAdd) + { + addRecs[cnt++] = fromRecord(recordToAdd); + } + + proxy.addDNS(addRecs); + + + String matchName = getNameToMatch(); + + recordCommands.matchSOA(new String[] {matchName}); + + List matchedRecords = new ArrayList(); + for (DnsRecord matchedRecord : recordPrinter.printedRecords) + matchedRecords.add(toRecord(matchedRecord)); + + doAssertions(matchedRecords); + } + + private void cleanRecords() throws Exception + { + DnsRecord[] rec = proxy.getDNSByType(Type.ANY); + + if (rec != null && rec.length > 0) + proxy.removeDNS(rec); + + rec = proxy.getDNSByType(Type.ANY); + + assertNull(rec); + + } + + protected abstract List getRecordsToAdd() throws Exception; + + protected abstract String getNameToMatch() throws Exception; + + protected abstract void doAssertions(List recordsMatched) throws Exception; + } + + public void testMatchRecords_AssertAllRecordMatched() throws Exception + { + new TestPlan() + { + private List recordsToAdd; + + @Override + protected List getRecordsToAdd() throws Exception + { + recordsToAdd = new ArrayList(); + + Record addRecord = new SOARecord(Name.fromString("example.domain.com."), DClass.IN, 3600, Name.fromString("ns1.example.domain.com."), + Name.fromString("gm2552.example.domain.com."), 1, 3600, 60, 60, 3600); + recordsToAdd.add(addRecord); + + + return recordsToAdd; + } + + @Override + protected String getNameToMatch() throws Exception + { + return "example.domain.com"; + } + + @Override + protected void doAssertions(List recordsMatched) throws Exception + { + assertNotNull(recordsMatched); + assertEquals(1, recordsMatched.size()); + + for (Record record : recordsToAdd) + { + int index = recordsToAdd.indexOf(record); + assertTrue(index > -1); + Record checkRecord = recordsToAdd.get(index); + assertEquals(record, checkRecord); + } + } + }.perform(); + } + + + public void testMatchRecords_soaRecordsOnly_AssertOnlySOARecordMatched() throws Exception + { + new TestPlan() + { + private List recordsToAdd; + private List expectedMatchedRecords; + @Override + protected List getRecordsToAdd() throws Exception + { + + recordsToAdd = new ArrayList(); + expectedMatchedRecords = new ArrayList(); + + Record addRecord = new SOARecord(Name.fromString("example.domain.com."), DClass.IN, 3600, Name.fromString("ns1.example.domain.com."), + Name.fromString("gm2552.example.domain.com."), 1, 3600, 60, 60, 3600); + recordsToAdd.add(addRecord); + expectedMatchedRecords.add(addRecord); + + + addRecord = new MXRecord(Name.fromString("example.domain.com."), DClass.IN, 3600, + 1, Name.fromString("mail1.example.domain.com.")); + recordsToAdd.add(addRecord); + expectedMatchedRecords.add(addRecord); + + addRecord = new MXRecord(Name.fromString("example.domain.com."), DClass.IN, 3600, + 1, Name.fromString("mail2.example.domain.com.")); + recordsToAdd.add(addRecord); + expectedMatchedRecords.add(addRecord); + + recordsToAdd.add(new ARecord(Name.fromString("example.domain.com."), DClass.IN, 3600, InetAddress.getByName("127.0.0.1"))); + + return recordsToAdd; + } + + @Override + protected String getNameToMatch() throws Exception + { + return "example.domain.com"; + } + + @Override + protected void doAssertions(List recordsMatched) throws Exception + { + assertNotNull(recordsMatched); + assertEquals(1, recordsMatched.size()); + + for (Record record : expectedMatchedRecords) + { + int index = recordsToAdd.indexOf(record); + assertTrue(index > -1); + Record checkRecord = recordsToAdd.get(index); + assertEquals(record, checkRecord); + } + } + }.perform(); + } + + + public void testMatchRecords_specificDomain_AssertOnlyDomainRecordMatched() throws Exception + { + new TestPlan() + { + private List recordsToAdd; + private List expectedMatchedRecords; + + @Override + protected List getRecordsToAdd() throws Exception + { + recordsToAdd = new ArrayList(); + expectedMatchedRecords = new ArrayList(); + + Record addRecord = new SOARecord(Name.fromString("example.domain.com."), DClass.IN, 3600, Name.fromString("ns1.example.domain.com."), + Name.fromString("gm2552.example.domain.com."), 1, 3600, 60, 60, 3600); + recordsToAdd.add(addRecord); + expectedMatchedRecords.add(addRecord); + + addRecord = new SOARecord(Name.fromString("example2.domain.com."), DClass.IN, 3600, Name.fromString("ns1.example2.domain.com."), + Name.fromString("gm2552.example2.domain.com."), 1, 3600, 60, 60, 3600); + recordsToAdd.add(addRecord); + + + return recordsToAdd; + } + + @Override + protected String getNameToMatch() throws Exception + { + return "example.domain.com"; + } + + @Override + protected void doAssertions(List recordsMatched) throws Exception + { + assertNotNull(recordsMatched); + assertEquals(1, recordsMatched.size()); + + for (Record record : expectedMatchedRecords) + { + int index = recordsToAdd.indexOf(record); + assertTrue(index > -1); + Record checkRecord = recordsToAdd.get(index); + assertEquals(record, checkRecord); + } + } + }.perform(); + } + + + public void testMatchNoRecords_matchingParentDomain_AssertNoRecordMatched() throws Exception + { + new TestPlan() + { + private List recordsToAdd; + + @Override + protected List getRecordsToAdd() throws Exception + { + recordsToAdd = new ArrayList(); + + Record addRecord = new SOARecord(Name.fromString("example.domain.com."), DClass.IN, 3600, Name.fromString("ns1.example.domain.com."), + Name.fromString("gm2552.example.domain.com."), 1, 3600, 60, 60, 3600); + recordsToAdd.add(addRecord); + + addRecord = new SOARecord(Name.fromString("example2.domain.com."), DClass.IN, 3600, Name.fromString("ns1.example2.domain.com."), + Name.fromString("gm2552.example2.domain.com."), 1, 3600, 60, 60, 3600); + recordsToAdd.add(addRecord); + + + return recordsToAdd; + } + + @Override + protected String getNameToMatch() throws Exception + { + return "domain.com"; + } + + @Override + protected void doAssertions(List recordsMatched) throws Exception + { + assertNotNull(recordsMatched); + assertEquals(0, recordsMatched.size()); + + } + }.perform(); + } + + + public void testFailureToMatch_invalidProxy_AssertException() throws Exception + { + new TestPlan() + { + + @Override + protected List getRecordsToAdd() throws Exception + { + ArrayList recordsToAdd = new ArrayList(); + + Record addRecord = new SOARecord(Name.fromString("example2.domain.com."), DClass.IN, 3600, Name.fromString("ns1.example2.domain.com."), + Name.fromString("gm2552.example2.domain.com."), 1, 3600, 60, 60, 3600); + recordsToAdd.add(addRecord); + + return recordsToAdd; + } + + @Override + protected String getNameToMatch() throws Exception + { + recordCommands.setConfigurationProxy(new ConfigurationServiceProxy("http://localhost:7777/bogusendpoint")); + return "example.domain.com"; + } + + @Override + protected void assertException(Exception exception) throws Exception + { + assertNotNull(exception); + assertTrue(exception instanceof RuntimeException); + assertTrue(exception.getCause() instanceof AxisFault); + + } + + @Override + protected void doAssertions(List recordsMatched) throws Exception + { + } + + }.perform(); + } + +} diff --git a/java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/tools/DNSRecordCommands_removeRecords_Test.java b/java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/tools/DNSRecordCommands_removeRecords_Test.java new file mode 100644 index 000000000..3af3530d9 --- /dev/null +++ b/java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/tools/DNSRecordCommands_removeRecords_Test.java @@ -0,0 +1,368 @@ +package org.nhindirect.dns.tools; + +import java.net.InetAddress; +import java.net.URL; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import junit.framework.TestCase; + +import org.apache.axis.AxisFault; +import org.apache.mina.util.AvailablePortFinder; +import org.nhind.config.ConfigurationServiceProxy; +import org.nhind.config.DnsRecord; +import org.nhindirect.dns.ConfigServiceDNSStore; +import org.nhindirect.dns.DNSServer; +import org.nhindirect.dns.DNSServerSettings; +import org.nhindirect.dns.util.BaseTestPlan; +import org.nhindirect.dns.util.ConfigServiceRunner; +import org.xbill.DNS.ARecord; +import org.xbill.DNS.DClass; +import org.xbill.DNS.MXRecord; +import org.xbill.DNS.Name; +import org.xbill.DNS.Record; +import org.xbill.DNS.SOARecord; +import org.xbill.DNS.Type; + +public class DNSRecordCommands_removeRecords_Test extends TestCase +{ + abstract class TestPlan extends BaseTestPlan + { + protected int port; + protected DNSServer server = null; + protected ConfigurationServiceProxy proxy; + protected DNSRecordCommands recordCommands; + + protected Record toRecord(DnsRecord rec) throws Exception + { + return Record.newRecord(Name.fromString(rec.getName()), rec.getType(), rec.getDclass(), rec.getTtl(), rec.getData()); + } + + protected DnsRecord fromRecord(Record rec) throws Exception + { + DnsRecord newRec = new DnsRecord(); + newRec.setData(rec.rdataToWireCanonical()); + newRec.setDclass(rec.getDClass()); + newRec.setName(rec.getName().toString()); + newRec.setTtl(rec.getTTL()); + newRec.setType(rec.getType()); + + return newRec; + } + + + protected List getRecordsInStore(int type) throws Exception + { + DnsRecord[] records = proxy.getDNSByType(type); + + List retVal; + + if (records == null || records.length == 0) + retVal = Collections.emptyList(); + else + { + retVal = new ArrayList(); + for (DnsRecord record : records) + retVal.add(toRecord(record)); + } + + return retVal; + } + + @Override + protected void setupMocks() throws Exception + { + if (!ConfigServiceRunner.isServiceRunning()) + ConfigServiceRunner.startConfigService(); + + proxy = new ConfigurationServiceProxy(ConfigServiceRunner.getConfigServiceURL()); + + cleanRecords(); + + port = AvailablePortFinder.getNextAvailable(1024); + DNSServerSettings settings = new DNSServerSettings(); + settings.setPort(port); + + server = new DNSServer(new ConfigServiceDNSStore(new URL(ConfigServiceRunner.getConfigServiceURL())), settings); + + server.start(); + + } + + @Override + protected void tearDownMocks() throws Exception + { + if (server != null) + server.stop(); + } + + @Override + protected void performInner() throws Exception + { + recordCommands = new DNSRecordCommands(proxy); + + List recordsToAdd = getRecordsToAdd(); + + + DnsRecord[] addRecs = new DnsRecord[recordsToAdd.size()]; + int cnt = 0; + for (Record recordToAdd : recordsToAdd) + { + addRecs[cnt++] = fromRecord(recordToAdd); + } + + proxy.addDNS(addRecs); + + addRecs = proxy.getDNSByType(Type.ANY); + + + List recordsToRemove = getRecordsToRemove(Arrays.asList(addRecs)); + for (DnsRecord recordToRemove : recordsToRemove) + { + switch (recordToRemove.getType()) + { + case Type.A: + recordCommands.removeANAME(new String[] {Long.toString(recordToRemove.getId())}); + break; + case Type.MX: + recordCommands.removeMX(new String[] {Long.toString(recordToRemove.getId())}); + break; + case Type.SOA: + recordCommands.removeSOA(new String[] {Long.toString(recordToRemove.getId())}); + break; + + } + + } + + doAssertions(getRecordsInStore(Type.ANY)); + } + + private void cleanRecords() throws Exception + { + DnsRecord[] rec = proxy.getDNSByType(Type.ANY); + + if (rec != null && rec.length > 0) + proxy.removeDNS(rec); + + rec = proxy.getDNSByType(Type.ANY); + + assertNull(rec); + + } + + + protected abstract List getRecordsToAdd() throws Exception; + + protected abstract List getRecordsToRemove(List addedRecords) throws Exception; + + protected abstract void doAssertions(List remainingRecords) throws Exception; + } + + public void testRemoveAllRecords_AssertRecordRemove() throws Exception + { + new TestPlan() + { + private List recordsToAdd; + + @Override + protected List getRecordsToAdd() throws Exception + { + recordsToAdd = new ArrayList(); + + recordsToAdd.add(new ARecord(Name.fromString("example.domain.com."), DClass.IN, 3600, InetAddress.getByName("127.0.0.1"))); + recordsToAdd.add(new MXRecord(Name.fromString("example.domain.com."), DClass.IN, 3600, + 1, Name.fromString("mail1.example.domain.com."))); + recordsToAdd.add(new SOARecord(Name.fromString("example.domain.com."), DClass.IN, 3600, Name.fromString("ns1.example.domain.com."), + Name.fromString("gm2552.example.domain.com."), 1, 3600, 60, 60, 3600)); + + return recordsToAdd; + } + + @Override + protected List getRecordsToRemove(List addedRecords) throws Exception + { + return addedRecords; + } + + @Override + protected void doAssertions(List remainingRecords) throws Exception + { + assertNotNull(remainingRecords); + assertEquals(0, remainingRecords.size()); + } + + }.perform(); + } + + + public void testRemoveOnlyARecords_AssertOnlyARecordRemoved() throws Exception + { + new TestPlan() + { + private List recordsToAdd; + private List expectedRemainingRecords; + + + @Override + protected List getRecordsToAdd() throws Exception + { + recordsToAdd = new ArrayList(); + + recordsToAdd.add(new ARecord(Name.fromString("example.domain.com."), DClass.IN, 3600, InetAddress.getByName("127.0.0.1"))); + recordsToAdd.add(new ARecord(Name.fromString("example.domain.com."), DClass.IN, 3600, InetAddress.getByName("127.0.0.2"))); + recordsToAdd.add(new MXRecord(Name.fromString("example.domain.com."), DClass.IN, 3600, + 1, Name.fromString("mail1.example.domain.com."))); + recordsToAdd.add(new MXRecord(Name.fromString("example2.domain.com."), DClass.IN, 3600, + 1, Name.fromString("mail2.exampl2.domain.com."))); + + recordsToAdd.add(new SOARecord(Name.fromString("example.domain.com."), DClass.IN, 3600, Name.fromString("ns1.example.domain.com."), + Name.fromString("gm2552.example.domain.com."), 1, 3600, 60, 60, 3600)); + + return recordsToAdd; + } + + @Override + protected List getRecordsToRemove(List addedRecords) throws Exception + { + List retVal = new ArrayList(); + expectedRemainingRecords = new ArrayList(); + + for (DnsRecord addedRecord : addedRecords) + { + if (addedRecord.getType() == Type.A) + retVal.add(addedRecord); + else + expectedRemainingRecords.add(toRecord(addedRecord)); + } + + return retVal; + } + + @Override + protected void doAssertions(List remainingRecords) throws Exception + { + assertNotNull(remainingRecords); + assertEquals(3, remainingRecords.size()); + + for (Record expectedRemaining : expectedRemainingRecords) + { + int index = remainingRecords.indexOf(expectedRemaining); + assertTrue(index >= 0); + + Record checkRecord = remainingRecords.get(index); + assertTrue(checkRecord.getType() != Type.A); + assertEquals(checkRecord, expectedRemaining); + } + } + + }.perform(); + } + + public void testRemoveAllExceptARecords_AssertAllExceptARecordRemoved() throws Exception + { + new TestPlan() + { + private List recordsToAdd; + private List expectedRemainingRecords; + + + @Override + protected List getRecordsToAdd() throws Exception + { + recordsToAdd = new ArrayList(); + + recordsToAdd.add(new ARecord(Name.fromString("example.domain.com."), DClass.IN, 3600, InetAddress.getByName("127.0.0.1"))); + recordsToAdd.add(new ARecord(Name.fromString("example.domain.com."), DClass.IN, 3600, InetAddress.getByName("127.0.0.2"))); + recordsToAdd.add(new MXRecord(Name.fromString("example.domain.com."), DClass.IN, 3600, + 1, Name.fromString("mail1.example.domain.com."))); + recordsToAdd.add(new MXRecord(Name.fromString("example2.domain.com."), DClass.IN, 3600, + 1, Name.fromString("mail2.exampl2.domain.com."))); + + recordsToAdd.add(new SOARecord(Name.fromString("example.domain.com."), DClass.IN, 3600, Name.fromString("ns1.example.domain.com."), + Name.fromString("gm2552.example.domain.com."), 1, 3600, 60, 60, 3600)); + + return recordsToAdd; + } + + @Override + protected List getRecordsToRemove(List addedRecords) throws Exception + { + List retVal = new ArrayList(); + expectedRemainingRecords = new ArrayList(); + + for (DnsRecord addedRecord : addedRecords) + { + if (addedRecord.getType() != Type.A) + retVal.add(addedRecord); + else + expectedRemainingRecords.add(toRecord(addedRecord)); + } + + return retVal; + } + + @Override + protected void doAssertions(List remainingRecords) throws Exception + { + assertNotNull(remainingRecords); + assertEquals(2, remainingRecords.size()); + + for (Record expectedRemaining : expectedRemainingRecords) + { + int index = remainingRecords.indexOf(expectedRemaining); + assertTrue(index >= 0); + + Record checkRecord = remainingRecords.get(index); + assertTrue(checkRecord.getType() == Type.A); + assertEquals(checkRecord, expectedRemaining); + } + } + + }.perform(); + } + + public void testFailureToRemove_invalidProxy_AssertException() throws Exception + { + new TestPlan() + { + private List recordsToAdd; + + @Override + protected List getRecordsToAdd() throws Exception + { + recordsToAdd = new ArrayList(); + + recordsToAdd.add(new ARecord(Name.fromString("example.domain.com."), DClass.IN, 3600, InetAddress.getByName("127.0.0.1"))); + + return recordsToAdd; + } + + @Override + protected List getRecordsToRemove(List addedRecords) throws Exception + { + this.recordCommands.setConfigurationProxy(new ConfigurationServiceProxy("http://localhost:7777/bogusendpoint")); + return addedRecords; + } + + @Override + protected void assertException(Exception exception) throws Exception + { + assertNotNull(exception); + assertTrue(exception instanceof RuntimeException); + assertTrue(exception.getCause() instanceof AxisFault); + + + } + + @Override + protected void doAssertions(List remainingRecords) throws Exception + { + + } + + }.perform(); + } +} diff --git a/java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/tools/MockDNSRecordPrinter.java b/java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/tools/MockDNSRecordPrinter.java new file mode 100644 index 000000000..f778f6738 --- /dev/null +++ b/java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/tools/MockDNSRecordPrinter.java @@ -0,0 +1,40 @@ +package org.nhindirect.dns.tools; + +import java.util.ArrayList; +import java.util.Collection; + +import org.nhind.config.DnsRecord; + +public class MockDNSRecordPrinter implements DNSRecordPrinter +{ + + public Collection printedRecords = new ArrayList(); + public int printRecordCalled = 0; + public int printCollectionCalled = 0; + public int printArrayCalled = 0; + + @Override + public void print(Collection records) + { + ++printCollectionCalled; + for (DnsRecord rec : records) + print(rec); + + } + + @Override + public void print(DnsRecord record) + { + ++printRecordCalled; + printedRecords.add(record); + } + + @Override + public void print(DnsRecord[] records) + { + ++printArrayCalled; + for (DnsRecord rec : records) + print(rec); + } + +} diff --git a/java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/util/BaseTestPlan.java b/java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/util/BaseTestPlan.java new file mode 100644 index 000000000..3f5f06429 --- /dev/null +++ b/java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/util/BaseTestPlan.java @@ -0,0 +1,44 @@ +package org.nhindirect.dns.util; + +public abstract class BaseTestPlan +{ + public void perform() throws Exception + { + try + { + setupMocks(); + Exception exception = null; + try + { + performInner(); + } + catch (Exception e) + { + exception = e; + } + assertException(exception); + } + finally + { + tearDownMocks(); + } + } + + protected abstract void performInner() throws Exception; + + protected void setupMocks() throws Exception + { + } + + protected void tearDownMocks() throws Exception + { + } + + protected void assertException(Exception exception) throws Exception + { + if (exception != null) + { + throw exception; + } + } +} \ No newline at end of file diff --git a/java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/util/ConfigServiceRunner.java b/java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/util/ConfigServiceRunner.java new file mode 100644 index 000000000..5db64c9a9 --- /dev/null +++ b/java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/util/ConfigServiceRunner.java @@ -0,0 +1,64 @@ +package org.nhindirect.dns.util; + +import org.apache.mina.util.AvailablePortFinder; +import org.mortbay.jetty.Server; +import org.mortbay.jetty.bio.SocketConnector; +import org.mortbay.jetty.webapp.WebAppContext; + +public class ConfigServiceRunner +{ + private static Server server; + private static int HTTPPort; + private static String configServiceURL; + + public synchronized static void startConfigService() throws Exception + { + + if (server == null) + { + /* + * Setup the configuration service server + */ + server = new Server(); + SocketConnector connector = new SocketConnector(); + + HTTPPort = AvailablePortFinder.getNextAvailable( 1024 ); + connector.setPort(HTTPPort); + + WebAppContext context = new WebAppContext(); + + context.setContextPath("/config"); + context.setServer(server); + context.setWar("war/config-service.war"); + + server.setSendServerVersion(false); + server.addConnector(connector); + server.addHandler(context); + + server.start(); + + configServiceURL = "http://localhost:" + HTTPPort + "/config/ConfigurationService"; + + } + } + + + public synchronized static boolean isServiceRunning() + { + return (server != null && server.isRunning()); + } + + public synchronized static void shutDownConfigService() throws Exception + { + if (isServiceRunning()) + { + server.stop(); + server = null; + } + } + + public synchronized static String getConfigServiceURL() + { + return configServiceURL; + } +} diff --git a/java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/util/DNSRecordUtil.java b/java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/util/DNSRecordUtil.java new file mode 100644 index 000000000..c41f011e9 --- /dev/null +++ b/java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/util/DNSRecordUtil.java @@ -0,0 +1,142 @@ +package org.nhindirect.dns.util; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.InputStream; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.util.HashMap; +import java.util.Map; + +import javax.security.auth.x500.X500Principal; + +import org.apache.commons.io.FileUtils; +import org.nhind.config.DnsRecord; +import org.nhindirect.config.store.DNSRecord; +import org.nhindirect.config.store.util.DNSRecordUtils; +import org.xbill.DNS.DClass; +import org.xbill.DNS.NSRecord; +import org.xbill.DNS.CNAMERecord; +import org.xbill.DNS.Name; + +public class DNSRecordUtil +{ + private static final String certBasePath = "src/test/resources/certs/"; + + private static DnsRecord toDnsRecord(DNSRecord rec) + { + DnsRecord retVal = new DnsRecord(); + + retVal.setData(rec.getData()); + retVal.setDclass(rec.getDclass()); + retVal.setName(rec.getName()); + retVal.setTtl(rec.getTtl()); + retVal.setType(rec.getType()); + + return retVal; + } + + public static String getCertOwner(X509Certificate cert) + { + X500Principal prin = cert.getSubjectX500Principal(); + + // get the domain name + Map oidMap = new HashMap(); + oidMap.put("1.2.840.113549.1.9.1", "EMAILADDRESS"); // OID for email address + String prinName = prin.getName(X500Principal.RFC1779, oidMap); + + // see if there is an email address first in the DN + String searchString = "EMAILADDRESS="; + int index = prinName.indexOf(searchString); + if (index == -1) + { + searchString = "CN="; + // no Email.. check the CN + index = prinName.indexOf(searchString); + if (index == -1) + return ""; // no CN... nothing else that can be done from here + } + + // look for a "," to find the end of this attribute + int endIndex = prinName.indexOf(",", index); + String address; + if (endIndex > -1) + address = prinName.substring(index + searchString.length(), endIndex); + else + address= prinName.substring(index + searchString.length()); + + return address; + } + + public static X509Certificate loadCertificate(String certFileName) throws Exception + { + File fl = new File(certBasePath + certFileName); + + InputStream str = new ByteArrayInputStream(FileUtils.readFileToByteArray(fl)); + + X509Certificate retVal = (X509Certificate)CertificateFactory.getInstance("X.509").generateCertificate(str); + + str.close(); + + return retVal; + } + + + public static DnsRecord createARecord(String name, String ip) throws Exception + { + DNSRecord rec = DNSRecordUtils.createARecord(name, 86400L, ip); + + return toDnsRecord(rec); + } + + public static DnsRecord createCERTRecord(String name, X509Certificate cert) throws Exception + { + DNSRecord rec = DNSRecordUtils.createX509CERTRecord(name, 86400L, cert); + + return toDnsRecord(rec); + } + + public static DnsRecord createMXRecord(String name, String target, int priority) throws Exception + { + DNSRecord rec = DNSRecordUtils.createMXRecord(name, target, 86400L, priority); + + return toDnsRecord(rec); + } + + public static DnsRecord createSOARecord(String name, String nameServer, String hostmaster) throws Exception + { + DNSRecord rec = DNSRecordUtils.createSOARecord(name, 3600L, nameServer, hostmaster, 1, 3600L, 600L, 604800L, 3600L); + + return toDnsRecord(rec); + } + + public static DnsRecord createNSRecord(String name, String target) throws Exception + { + + if (!name.endsWith(".")) + name = name + "."; + + if (!target.endsWith(".")) + target = target + "."; + + NSRecord rec = new NSRecord(Name.fromString(name), DClass.IN, 86400L, Name.fromString(target)); + + return toDnsRecord(DNSRecord.fromWire(rec.toWireCanonical())); + + } + + public static DnsRecord createCNAMERecord(String name, String target) throws Exception + { + + if (!name.endsWith(".")) + name = name + "."; + + if (!target.endsWith(".")) + target = target + "."; + + CNAMERecord rec = new CNAMERecord(Name.fromString(name), DClass.IN, 86400L, Name.fromString(target)); + + return toDnsRecord(DNSRecord.fromWire(rec.toWireCanonical())); + + } +} diff --git a/java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/util/IPUtils.java b/java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/util/IPUtils.java new file mode 100644 index 000000000..de243bf4c --- /dev/null +++ b/java/tags/dns-2.0.1/src/test/java/org/nhindirect/dns/util/IPUtils.java @@ -0,0 +1,40 @@ +package org.nhindirect.dns.util; + +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.List; + +public class IPUtils +{ + public static String[] getDNSLocalIps() + { + final List ips = new ArrayList(); + ips.add("127.0.0.1"); + + try + { + final Enumeration netInts = NetworkInterface.getNetworkInterfaces(); + while (netInts.hasMoreElements()) + { + final NetworkInterface netInt = netInts.nextElement(); + + final Enumeration addrs = netInt.getInetAddresses(); + while (addrs.hasMoreElements()) + { + final InetAddress addr = addrs.nextElement(); + if (!(addr instanceof Inet6Address)) + ips.add(addr.getHostAddress()); + } + } + } + catch (Exception e) + { + + } + + return ips.toArray(new String[ips.size()]); + } +} diff --git a/java/tags/dns-2.0.1/src/test/resources/certs/bob.der b/java/tags/dns-2.0.1/src/test/resources/certs/bob.der new file mode 100644 index 0000000000000000000000000000000000000000..533c15b71bff772de9c063ac08ef56c0cd0dc84f GIT binary patch literal 899 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 literal 0 HcmV?d00001 diff --git a/java/tags/dns-2.0.1/src/test/resources/certs/mshost.der b/java/tags/dns-2.0.1/src/test/resources/certs/mshost.der new file mode 100644 index 0000000000000000000000000000000000000000..a354a8db223fad4c03aa85dca89edd288ec5512c GIT binary patch literal 908 zcmXqLV(u_#V*0p%nTe5!i7O=Q?`j4R5HjFp*(v~_n+xP~CELE|(-ZUat`J~m;d&|nyc zLzpSt(NMsE4%q-wQk>z6%V-az_CAELwg!JpjLne7B<}CW5 z=X-Rcfjmf$GK++PSc8bEPpl_r&?3jC^Z$BdrT45T68&<+ARMGXo+ZG*&%kGa*8-0= zx3tWhR4aXbL|p3?K_Uql%0&fwxtYmD`NjEZC7?jpcTO!Tam_2r&&fg7kX)2w(AWWU zB_ktCjX{+`1&nXN)Rsd9OK`g?9?9*=MI{D2AU_I&0+Pvq0Xg=8>6;lC?JA6+^DjuV zzu`T%>yG)l4|B!dZhiiilg&6|f={o-JgcMso~JFH{#5RV<`u=SZLI5;74hXZWafXK z@NVti(>MRbEQvVzV~_o0ixv-kQ2~#H2a)FzpUz+XI>p`Zm5R6X55@^BFH1B(rS_<^ d{O)+L-YD4b^{?QvU;j4L+9#I?%k0Ss004*kD^>si literal 0 HcmV?d00001 diff --git a/java/tags/dns-2.0.1/src/test/resources/certs/ryan.der b/java/tags/dns-2.0.1/src/test/resources/certs/ryan.der new file mode 100644 index 0000000000000000000000000000000000000000..07f08f35685adbd54474431a959ea38d84ec5fb3 GIT binary patch literal 1088 zcmXqLVzDu3VqUs{nTe5!iId^Gzl{Hrhh;hjylk9WZ60mkc^Mg5Ss4r(+YPx5IN6v( zS=fY`LW2zj4fsJE4q*=8%;Mtw(xOa5exM$p5SK8Ub81muYLTIgffPu9TUgjPH6=4S zF-O5OFD<_)H?br$x!6$5Km;VqEX*66nOl&P>X}!PT9jX4C}$u8x0;htj6XRyGt(g% zY@%Lrey)L>IIp3Bfti7kp{ap^fq4{=Yly@(Xq-elmj+cP<|+7Prsk$r0{y3HppNWP zsUo1PLvdh&tTBR$i>ve$jET0;K^b> z_3g76lwA^Jrp=z3bh_+5+lTVmTT|xFs=em2K)m6_^biw9)iVkH+AnpClR91sidr`= z|8;AhjfdXdM_2DF?a4Quoyx!IT4&N9GX|eY=P%Dax;(=E>*C33XLvQO_e?o>0qv5{AB!+q|+-PJ+{A~S^;P8_|g zJd=?>`+LKC^X!S;J0rbQl&7AKH!CjmFX zGnuA=9vf#un+Idt4<|+z7DX}njFOUqVk>?9^73-M;>^Sxz5Jqd{p3X5ijACG(wFaMbMoQ){5$v&NX-kpaBad?(Y?~qy+6=X>_&Q1^Ok>(%c3_F+=6S-84Bp)K{lmQX sO3Ky#+sE8}D=+aq-@IqbNo!U;Ps>)t&}BTWhu=kO2<6TdGyb~@0QCNOYybcN literal 0 HcmV?d00001 diff --git a/java/tags/dns-2.0.1/src/test/resources/certs/umesh.der b/java/tags/dns-2.0.1/src/test/resources/certs/umesh.der new file mode 100644 index 0000000000000000000000000000000000000000..ca80ef5f5b0eee446779b7e66681b319c81f7076 GIT binary patch literal 1077 zcmXqLVlgymVxF~tnTe5!iId^Gzl{Hrhh=&Oylk9WZ60mkc^Mg5Ss4r(+YPx5IN6v( zS=fY`LW2zj4fsJE4q*=8%;Mtw(xOa5exM$p5SK8Ub81muYLTIgffPu9TUgjPH6=4S zF-O5OFD<_)H?br$x!6$5Km;VqEX*66nOl&P>X}!PT9jX4C}$u8x0;htj6XRyGt(g% zY@%Lrey)L>IIp3BfrXK=p{a?vkwFxYYly@(XzZr8OZg0VATDJs%}p)NFw`>8Kz5-t zSk$37HMz7XH6t}Krz9gaH!(8@?Aj(qIpm;ZWMyD(V&rBpXkuhzYW&Brq55TlY=z9+ z`syctrcLQ}X(?i^0}_{l8hriN|R4o1JXg_@1IViJmtd|wt#>+jNf z_}d})y>Uiee4_q-jW`7^tNo4dsVj?UL2IJZ`lzxC#SsfLgfmJyU(GqvOHJC z8f}P~eq}O)ZkNiDj~R8J7RjgZol)91_r39~g^`c@J{Ycf9%lF#l_X$tm3`yu1Ie)IK(yS^4FvkcExCOB%ZdPpWN#=PXSFJvPpS zHV?+OA5M%cEQ(_C86_nJ#a8lpi#hHmYdih1^`pJp9$wfJO1*y5n$p@GdnHw7! zST#xwD|t_6lrWg7&U#qFXz_9R8waOXYEuv0`SE$$I$Pd;PhX+uVvo&a6xGkTg)Vap zjYxMtBk{&KQ2fgNd+*;XA5c6S$+R>t@$2ai3skzwYB>{^q_~J(vi+SMRP$va#j+rpjwWWN8)ivn?Hl-_I!+qVV)Qgm*9 literal 0 HcmV?d00001 diff --git a/java/tags/dns-2.0.1/src/test/resources/dnsrecords/example.domain.com.a b/java/tags/dns-2.0.1/src/test/resources/dnsrecords/example.domain.com.a new file mode 100644 index 0000000000000000000000000000000000000000..a92ee895ccefe7c0cce09c81448b1ed76ad0d35d GIT binary patch literal 34 mcmZQitw_u*$Vp{O$1VRQr0S1V_;-p;1ghQXU;1&WXGa{Ej`!B)YJ%@6axcP NGXsMSkPXCq0swIQ7x(}G literal 0 HcmV?d00001