diff options
author | Ingo Bauersachs <ingo@jitsi.org> | 2011-12-19 16:26:26 +0000 |
---|---|---|
committer | Ingo Bauersachs <ingo@jitsi.org> | 2011-12-19 16:26:26 +0000 |
commit | 936e6cbd5287262f04d36d12bb822cdc192e6840 (patch) | |
tree | 00df36b332cb2f2af506dc6aa381aefbb1763592 /src/net/java/sip | |
parent | 0d4f8ceebce7cd029a679b02d905d4daf4da9b90 (diff) | |
download | jitsi-936e6cbd5287262f04d36d12bb822cdc192e6840.zip jitsi-936e6cbd5287262f04d36d12bb822cdc192e6840.tar.gz jitsi-936e6cbd5287262f04d36d12bb822cdc192e6840.tar.bz2 |
DNS resolver implementation based on libjunbound
Diffstat (limited to 'src/net/java/sip')
11 files changed, 1488 insertions, 0 deletions
diff --git a/src/net/java/sip/communicator/util/dns/ConfigurableDnssecResolver.java b/src/net/java/sip/communicator/util/dns/ConfigurableDnssecResolver.java new file mode 100644 index 0000000..1d0d5f1 --- /dev/null +++ b/src/net/java/sip/communicator/util/dns/ConfigurableDnssecResolver.java @@ -0,0 +1,345 @@ +/*
+ * Jitsi, the OpenSource Java VoIP and Instant Messaging client.
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ */
+package net.java.sip.communicator.util.dns;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.util.*;
+
+import javax.swing.*;
+
+import net.java.sip.communicator.service.configuration.*;
+import net.java.sip.communicator.service.notification.*;
+import net.java.sip.communicator.service.resources.*;
+import net.java.sip.communicator.util.*;
+import net.java.sip.communicator.util.swing.*;
+
+import org.xbill.DNS.*;
+
+/**
+ * Resolver that wraps a DNSSEC capable resolver and handles validation
+ * failures according to the user's settings.
+ *
+ * @author Ingo Bauersachs
+ */
+public class ConfigurableDnssecResolver
+ extends UnboundResolver
+{
+ private final static Logger logger
+ = Logger.getLogger(ConfigurableDnssecResolver.class);
+
+ /**
+ * Name of the property that defines the default DNSSEC validation
+ * behavior.
+ */
+ public final static String PNAME_DNSSEC_VALIDATION_MODE
+ = "net.java.sip.communicator.util.dns.DNSSEC_VALIDATION_MODE";
+
+ /**
+ * Default value of {@link #PNAME_DNSSEC_VALIDATION_MODE}
+ */
+ public final static String PNAME_BASE_DNSSEC_PIN
+ = "net.java.sip.communicator.util.dns.pin";
+
+ final static String EVENT_TYPE = "DNSSEC_NOTIFICATION";
+
+ private ConfigurationService config
+ = DnsUtilActivator.getConfigurationService();
+ private ResourceManagementService R
+ = DnsUtilActivator.getResources();
+ private Map<String, Date> lastNotifications
+ = new HashMap<String, Date>();
+
+ /**
+ * Creates a new instance of this class. Tries to use the system's
+ * default forwarders.
+ */
+ public ConfigurableDnssecResolver()
+ {
+ super();
+ String forwarders = DnsUtilActivator.getConfigurationService()
+ .getString(DnsUtilActivator.PNAME_DNSSEC_NAMESERVERS);
+ if(!StringUtils.isNullOrEmpty(forwarders, true))
+ {
+ if(logger.isTraceEnabled())
+ {
+ logger.trace("Setting DNSSEC forwarders to: "
+ + Arrays.toString(forwarders.split(",")));
+ }
+ super.setForwarders(forwarders.split(","));
+ }
+ DnsUtilActivator.getNotificationService().
+ registerDefaultNotificationForEvent(
+ ConfigurableDnssecResolver.EVENT_TYPE,
+ NotificationAction.ACTION_POPUP_MESSAGE,
+ null, null);
+ }
+
+ /**
+ * Inspects a DNS answer message and handles validation results according to
+ * the user's preferences.
+ *
+ * @throws DnssecRuntimeException when the validation failed and the user
+ * did not choose to ignore it.
+ */
+ @Override
+ protected void validateMessage(SecureMessage msg)
+ throws DnssecRuntimeException
+ {
+ //---------------------------------------------------------------------
+ // || 1 | 2 | 3 | 4 | 5
+ //---------------------------------------------------------------------
+ // Sec. | Bog. || Ign. | Sec.Only | Sec.Or.Unsig | Warn.Bog | WarnAll
+ //---------------------------------------------------------------------
+ //a) 1 | 0 || ok | ok | ok | ok | ok
+ //b) 0 | 1 || ok | nok | nok | ask | ask
+ //c) 0 | 0 || ok | nok | ok | ok | ask
+ //---------------------------------------------------------------------
+
+ String fqdn = msg.getQuestion().getName().toString();
+ String type = Type.string(msg.getQuestion().getType());
+ String propName = createPropNameUnsigned(fqdn, type);
+ SecureResolveMode defaultAction = Enum.valueOf(SecureResolveMode.class,
+ config.getString(
+ PNAME_DNSSEC_VALIDATION_MODE,
+ SecureResolveMode.WarnIfBogus.name()
+ )
+ );
+ SecureResolveMode pinned = Enum.valueOf(SecureResolveMode.class,
+ config.getString(
+ propName,
+ defaultAction.name()
+ )
+ );
+
+ //create default entry
+ if(pinned == defaultAction)
+ config.setProperty(propName, pinned.name());
+
+ //check domain policy
+
+ //[abc]1, a[2-5]
+ if(pinned == SecureResolveMode.IgnoreDnssec || msg.isSecure())
+ return;
+
+ if(
+ //b2, c2
+ (pinned == SecureResolveMode.SecureOnly && !msg.isSecure())
+ ||
+ //b3
+ (pinned == SecureResolveMode.SecureOrUnsigned && msg.isBogus())
+ )
+ {
+ String text = getExceptionMessage(msg);
+ Date last = lastNotifications.get(text);
+ if(last == null
+ //wait at least 5 minutes before showing the same info again
+ || last.before(new Date(new Date().getTime() - 1000*60*5)))
+ {
+ DnsUtilActivator.getNotificationService().fireNotification(
+ EVENT_TYPE,
+ R.getI18NString("util.dns.INSECURE_ANSWER_TITLE"),
+ text, null, null);
+ lastNotifications.put(text, new Date());
+ }
+ throw new DnssecRuntimeException(text);
+ }
+
+ //c3
+ if(pinned == SecureResolveMode.SecureOrUnsigned && !msg.isBogus())
+ return;
+
+ //c4
+ if(pinned == SecureResolveMode.WarnIfBogus && !msg.isBogus())
+ return;
+
+ //b4, b5, c5
+ String reason = msg.isBogus()
+ ? R.getI18NString("util.dns.DNSSEC_ADVANCED_REASON_BOGUS",
+ new String[]{fqdn, msg.getBogusReason()})
+ : R.getI18NString("util.dns.DNSSEC_ADVANCED_REASON_UNSIGNED",
+ new String[]{type, fqdn});
+ DnssecDialog dlg = new DnssecDialog(fqdn, reason);
+ dlg.setVisible(true);
+ DnssecDialogResult result = dlg.getResult();
+ switch(result)
+ {
+ case Accept:
+ break;
+ case Deny:
+ throw new DnssecRuntimeException(getExceptionMessage(msg));
+ case AlwaysAccept:
+ if(msg.isBogus())
+ config.setProperty(propName,
+ SecureResolveMode.IgnoreDnssec.name());
+ else
+ config.setProperty(propName,
+ SecureResolveMode.WarnIfBogus.name());
+ break;
+ case AlwaysDeny:
+ config.setProperty(propName, SecureResolveMode.SecureOnly);
+ throw new DnssecRuntimeException(getExceptionMessage(msg));
+ }
+ }
+
+ /**
+ * Defines the return code from the DNSSEC verification dialog.
+ */
+ private enum DnssecDialogResult
+ {
+ /** The DNS result shall be accepted. */
+ Accept,
+ /** The result shall be rejected. */
+ Deny,
+ /** The result shall be accepted permanently. */
+ AlwaysAccept,
+ /**
+ * The result shall be rejected automatically unless it is valid
+ * according to DNSSEC.
+ */
+ AlwaysDeny
+ }
+
+ /**
+ * Dialog to ask and warn the user if he wants to continue to accept an
+ * invalid dnssec result.
+ */
+ private class DnssecDialog extends SIPCommDialog implements ActionListener
+ {
+ //UI controls
+ private JPanel pnlAdvanced;
+ private JPanel pnlStandard;
+ private final String domain;
+ private final String reason;
+ private JButton cmdAck;
+ private JButton cmdShowDetails;
+
+ //state
+ private DnssecDialogResult result = DnssecDialogResult.Deny;
+
+ /**
+ * Creates a new instance of this class.
+ * @param domain The FQDN of the domain that failed.
+ * @param reason String describing why the validation failed.
+ */
+ public DnssecDialog(String domain, String reason)
+ {
+ super(false);
+ setModal(true);
+ this.domain = domain;
+ this.reason = reason;
+ initComponents();
+ }
+
+ /**
+ * Creates the UI controls
+ */
+ private void initComponents()
+ {
+ setLayout(new BorderLayout(15, 15));
+ setTitle(R.getI18NString("util.dns.INSECURE_ANSWER_TITLE"));
+
+ // warning text
+ JLabel imgWarning =
+ new JLabel(R.getImage("service.gui.icons.WARNING_ICON"));
+ imgWarning.setBorder(BorderFactory
+ .createEmptyBorder(10, 10, 10, 10));
+ add(imgWarning, BorderLayout.WEST);
+ JLabel lblWarning = new JLabel(
+ R.getI18NString("util.dns.DNSSEC_WARNING", new String[]{
+ R.getSettingsString("service.gui.APPLICATION_NAME"),
+ domain
+ })
+ );
+ add(lblWarning, BorderLayout.CENTER);
+
+ //standard panel (deny option)
+ cmdAck = new JButton(R.getI18NString("service.gui.OK"));
+ cmdAck.addActionListener(this);
+
+ cmdShowDetails = new JButton(
+ R.getI18NString("util.dns.DNSSEC_ADVANCED_OPTIONS"));
+ cmdShowDetails.addActionListener(this);
+
+ pnlStandard = new TransparentPanel(new BorderLayout());
+ pnlStandard.setBorder(BorderFactory
+ .createEmptyBorder(10, 10, 10, 10));
+ pnlStandard.add(cmdShowDetails, BorderLayout.WEST);
+ pnlStandard.add(cmdAck, BorderLayout.EAST);
+ add(pnlStandard, BorderLayout.SOUTH);
+
+ //advanced panel
+ pnlAdvanced = new TransparentPanel(new BorderLayout());
+ JPanel pnlAdvancedButtons = new TransparentPanel(
+ new FlowLayout(FlowLayout.RIGHT));
+ pnlAdvancedButtons.setBorder(BorderFactory
+ .createEmptyBorder(10, 10, 10, 10));
+ pnlAdvanced.add(pnlAdvancedButtons, BorderLayout.EAST);
+ for(DnssecDialogResult r : DnssecDialogResult.values())
+ {
+ JButton cmd = new JButton(R.getI18NString(
+ DnssecDialogResult.class.getName() + "." + r.name()));
+ cmd.setActionCommand(r.name());
+ cmd.addActionListener(this);
+ pnlAdvancedButtons.add(cmd);
+ }
+ JLabel lblReason = new JLabel(reason);
+ lblReason.setBorder(BorderFactory
+ .createEmptyBorder(10, 10, 10, 10));
+ pnlAdvanced.add(lblReason, BorderLayout.NORTH);
+ }
+
+ /**
+ * Handles the events coming from the buttons.
+ */
+ public void actionPerformed(ActionEvent e)
+ {
+ if(e.getSource() == cmdAck)
+ {
+ result = DnssecDialogResult.Deny;
+ dispose();
+ }
+ else if(e.getSource() == cmdShowDetails)
+ {
+ getContentPane().remove(pnlStandard);
+ add(pnlAdvanced, BorderLayout.SOUTH);
+ pack();
+ }
+ else
+ {
+ result = Enum.valueOf(DnssecDialogResult.class,
+ e.getActionCommand());
+ dispose();
+ }
+ }
+
+ /**
+ * Gets the option that user has chosen.
+ * @return the option that user has chosen.
+ */
+ public DnssecDialogResult getResult()
+ {
+ return result;
+ }
+ }
+
+ private String getExceptionMessage(SecureMessage msg)
+ {
+ //TODO parse bogus reason text and translate
+ return msg.getBogusReason() == null
+ ? R.getI18NString(
+ "util.dns.INSECURE_ANSWER_MESSAGE",
+ new String[]{msg.getQuestion().getName().toString()}
+ )
+ : msg.getBogusReason();
+ }
+
+ private String createPropNameUnsigned(String fqdn, String type)
+ {
+ return PNAME_BASE_DNSSEC_PIN + "." + fqdn.replace(".", "__");
+ }
+}
diff --git a/src/net/java/sip/communicator/util/dns/DnsUtilActivator.java b/src/net/java/sip/communicator/util/dns/DnsUtilActivator.java new file mode 100644 index 0000000..58825b1 --- /dev/null +++ b/src/net/java/sip/communicator/util/dns/DnsUtilActivator.java @@ -0,0 +1,180 @@ +/* + * Jitsi, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.util.dns; + +import net.java.sip.communicator.service.configuration.*; +import net.java.sip.communicator.service.notification.*; +import net.java.sip.communicator.service.resources.*; +import net.java.sip.communicator.util.*; + +import org.osgi.framework.*; +import org.xbill.DNS.*; + +/** + * The DNS Util activator registers the DNSSEC resolver if enabled. + * + * @author Emil Ivov + * @author Ingo Bauersachs + */ +public class DnsUtilActivator + implements BundleActivator +{ + /** + * The name of the property that enables or disables the DNSSEC resolver + * (instead of a normal, non-validating local resolver). + */ + public static final String PNAME_DNSSEC_RESOLVER_ENABLED + = "net.java.sip.communicator.util.dns.DNSSEC_ENABLED"; + + /** + * Default value of {@link PNAME_DNSSEC_RESOLVER_ENABLED}. + */ + public static final boolean PDEFAULT_DNSSEC_RESOLVER_ENABLED = false; + + /** + * The name of the property that sets custom nameservers to use for all DNS + * lookups when DNSSEC is enabled. Multiple servers are separated by a comma + * (,). + */ + public static final String PNAME_DNSSEC_NAMESERVERS + = "net.java.sip.communicator.util.dns.DNSSEC_NAMESERVERS"; + + /** + * The <tt>Logger</tt> used by the <tt>UtilActivator</tt> class and its + * instances for logging output. + */ + private static final Logger logger + = Logger.getLogger(DnsUtilActivator.class); + + private static ConfigurationService configurationService; + private static NotificationService notificationService; + private static ResourceManagementService resourceService; + private static BundleContext bundleContext; + + /** + * Calls <tt>Thread.setUncaughtExceptionHandler()</tt> + * + * @param context The execution context of the bundle being started + * (unused). + * @throws Exception If this method throws an exception, this bundle is + * marked as stopped and the Framework will remove this bundle's + * listeners, unregister all services registered by this bundle, and + * release all services used by this bundle. + */ + public void start(BundleContext context) + throws Exception + { + bundleContext = context; + + if(getConfigurationService().getBoolean( + PNAME_DNSSEC_RESOLVER_ENABLED, + PDEFAULT_DNSSEC_RESOLVER_ENABLED)) + { + getNotificationService(). + registerDefaultNotificationForEvent( + ConfigurableDnssecResolver.EVENT_TYPE, + NotificationAction.ACTION_POPUP_MESSAGE, + null, null); + } + refreshResolver(); + } + + /** + * Sets a DNSSEC resolver as default resolver on lookup when DNSSEC is + * enabled; creates a standard lookup otherwise. + */ + public static void refreshResolver() + { + if(getConfigurationService().getBoolean( + PNAME_DNSSEC_RESOLVER_ENABLED, + PDEFAULT_DNSSEC_RESOLVER_ENABLED)) + { + logger.trace("DNSSEC is enabled"); + ConfigurableDnssecResolver res = new ConfigurableDnssecResolver(); + for(int i = 1;;i++) + { + String anchor = getResources().getSettingsString( + "net.java.sip.communicator.util.dns.DS_ROOT." + i); + if(anchor == null) + break; + res.addTrustAnchor(anchor); + if(logger.isTraceEnabled()) + logger.trace("Loaded trust anchor " + anchor); + } + Lookup.setDefaultResolver(res); + } + else + { + logger.trace("DNSSEC is disabled, refresh default config"); + Lookup.refreshDefault(); + } + } + + /** + * Doesn't do anything. + * + * @param context The execution context of the bundle being stopped. + * @throws Exception If this method throws an exception, the bundle is + * still marked as stopped, and the Framework will remove the bundle's + * listeners, unregister all services registered by the bundle, and + * release all services used by the bundle. + */ + public void stop(BundleContext context) + throws Exception + { + } + + /** + * Returns the <tt>ConfigurationService</tt> obtained from the bundle + * context. + * @return the <tt>ConfigurationService</tt> obtained from the bundle + * context + */ + public static ConfigurationService getConfigurationService() + { + if (configurationService == null) + { + configurationService + = ServiceUtils.getService( + bundleContext, + ConfigurationService.class); + } + return configurationService; + } + + /** + * Returns the <tt>NotificationService</tt> obtained from the bundle context. + * + * @return the <tt>NotificationService</tt> obtained from the bundle context + */ + public static NotificationService getNotificationService() + { + if (notificationService == null) + { + notificationService + = ServiceUtils.getService( + bundleContext, + NotificationService.class); + } + return notificationService; + } + + /** + * Returns the service giving access to all application resources. + * + * @return the service giving access to all application resources. + */ + public static ResourceManagementService getResources() + { + if (resourceService == null) + { + resourceService + = ResourceManagementServiceUtils.getService(bundleContext); + } + return resourceService; + } +} diff --git a/src/net/java/sip/communicator/util/dns/DnssecException.java b/src/net/java/sip/communicator/util/dns/DnssecException.java new file mode 100644 index 0000000..d47496d --- /dev/null +++ b/src/net/java/sip/communicator/util/dns/DnssecException.java @@ -0,0 +1,25 @@ +/*
+ * Jitsi, the OpenSource Java VoIP and Instant Messaging client.
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ */
+package net.java.sip.communicator.util.dns;
+
+/**
+ * Checked DNSSEC exception for code that knows how to deal with it.
+ *
+ * @author Ingo Bauersachs
+ */
+public class DnssecException
+ extends Exception
+{
+ /**
+ * Creates a new instance of this class.
+ * @param e the DNSSEC runtime exception to encapsulate.
+ */
+ public DnssecException(DnssecRuntimeException e)
+ {
+ super(e);
+ }
+}
diff --git a/src/net/java/sip/communicator/util/dns/DnssecRuntimeException.java b/src/net/java/sip/communicator/util/dns/DnssecRuntimeException.java new file mode 100644 index 0000000..df91d23 --- /dev/null +++ b/src/net/java/sip/communicator/util/dns/DnssecRuntimeException.java @@ -0,0 +1,30 @@ +/*
+ * Jitsi, the OpenSource Java VoIP and Instant Messaging client.
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ */
+package net.java.sip.communicator.util.dns;
+
+import java.net.UnknownHostException;
+
+/**
+ * Runtime exception that is thrown when a DNSSEC validation failure occurred.
+ * This is not a checked exception or a derivative of
+ * {@link UnknownHostException} so that existing code does not retry the lookup
+ * (potentially in a loop).
+ *
+ * @author Ingo Bauersachs
+ */
+public class DnssecRuntimeException
+ extends RuntimeException
+{
+ /**
+ * Creates a new instance of this class.
+ * @param message The reason why this exception is thrown.
+ */
+ public DnssecRuntimeException(String message)
+ {
+ super(message);
+ }
+}
diff --git a/src/net/java/sip/communicator/util/dns/SecureMessage.java b/src/net/java/sip/communicator/util/dns/SecureMessage.java new file mode 100644 index 0000000..13eb943 --- /dev/null +++ b/src/net/java/sip/communicator/util/dns/SecureMessage.java @@ -0,0 +1,93 @@ +/*
+ * Jitsi, the OpenSource Java VoIP and Instant Messaging client.
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ */
+package net.java.sip.communicator.util.dns;
+
+import java.io.IOException;
+
+import org.xbill.DNS.Message;
+
+/**
+ * DNS Message that adds DNSSEC validation information.
+ *
+ * @author Ingo Bauersachs
+ */
+public class SecureMessage
+ extends Message
+{
+ private boolean secure;
+ private boolean bogus;
+ private String bogusReason;
+
+ /**
+ * Creates a new instance of this class based on data received from an
+ * Unbound resolve.
+ *
+ * @param msg The answer of the Unbound resolver.
+ * @throws IOException
+ */
+ public SecureMessage(UnboundResult msg) throws IOException
+ {
+ super(msg.answerPacket);
+ secure = msg.secure;
+ bogus = msg.bogus;
+ bogusReason = msg.whyBogus;
+ }
+
+ /**
+ * Indicates whether the answer is secure.
+ * @return True, if the result is validated securely.
+ */
+ public boolean isSecure()
+ {
+ return secure;
+ }
+
+ /**
+ * Indicates if there was a validation failure.
+ *
+ * @return If the result was not secure (secure == false), and this result
+ * is due to a security failure, bogus is true.
+ */
+ public boolean isBogus()
+ {
+ return bogus;
+ }
+
+ /**
+ * If the result is bogus this contains a string that describes the failure.
+ *
+ * @return string that describes the failure.
+ */
+ public String getBogusReason()
+ {
+ return bogusReason;
+ }
+
+ /**
+ * Converts the Message to a String. The fields secure, bogus and whyBogus
+ * are append as a comment.
+ */
+ @Override
+ public String toString()
+ {
+ StringBuilder s = new StringBuilder(super.toString());
+ s.append('\n');
+ s.append(";; Secure: ");
+ s.append(secure);
+ s.append('\n');
+ s.append(";; Bogus: ");
+ s.append(bogus);
+ s.append('\n');
+ if(bogus)
+ {
+ s.append(";; Reason: ");
+ s.append(bogusReason);
+ s.append('\n');
+ }
+ return s.toString();
+ }
+}
diff --git a/src/net/java/sip/communicator/util/dns/SecureResolveMode.java b/src/net/java/sip/communicator/util/dns/SecureResolveMode.java new file mode 100644 index 0000000..10756d3 --- /dev/null +++ b/src/net/java/sip/communicator/util/dns/SecureResolveMode.java @@ -0,0 +1,43 @@ +/*
+ * Jitsi, the OpenSource Java VoIP and Instant Messaging client.
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ */
+package net.java.sip.communicator.util.dns;
+
+/**
+ * Defines how DNSSEC validation errors should be handled.
+ *
+ * @author Ingo Bauersachs
+ */
+public enum SecureResolveMode
+{
+ /**
+ * Any DNSSEC data is completely ignored.
+ */
+ IgnoreDnssec,
+
+ /**
+ * The result of a query is only returned if it validated successfully.
+ */
+ SecureOnly,
+
+ /**
+ * The result of a query is returned if it validated successfully or when
+ * the zone is unsigned.
+ */
+ SecureOrUnsigned,
+
+ /**
+ * If the result of a query is bogus (manipulated, incorrect), the user is
+ * to be asked how to proceed.
+ */
+ WarnIfBogus,
+
+ /**
+ * If the result of a query is bogus (manipulated, incorrect) or if the zone
+ * is unsigned, the user is to be asked how to proceed.
+ */
+ WarnIfBogusOrUnsigned
+}
diff --git a/src/net/java/sip/communicator/util/dns/UnboundApi.java b/src/net/java/sip/communicator/util/dns/UnboundApi.java new file mode 100644 index 0000000..f3a08c7 --- /dev/null +++ b/src/net/java/sip/communicator/util/dns/UnboundApi.java @@ -0,0 +1,228 @@ +/*
+ * Jitsi, the OpenSource Java VoIP and Instant Messaging client.
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ */
+package net.java.sip.communicator.util.dns;
+
+/**
+ * Wrapper for the JUnbound JNI wrapper.
+ * <p>
+ * The JavaDoc of these methods is directly copied from libunbound, licensed as
+ * follows:
+ * <p>
+ * Copyright (c) 2007, NLnet Labs. All rights reserved.
+ *
+ * This software is open source.
+ *
+ * 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 NLNET LABS 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 REGENTS 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.
+ *
+ * @author Ingo Bauersachs
+ */
+public class UnboundApi
+{
+ private static boolean isAvailable;
+ private static final Object syncRoot = new Object();
+
+ static
+ {
+ tryLoadUnbound();
+ }
+
+ /**
+ * Attempts to load the Unbound native library. When successful,
+ * {@link #isAvailable()} returns true.
+ */
+ public static void tryLoadUnbound()
+ {
+ synchronized(syncRoot)
+ {
+ try
+ {
+ System.loadLibrary("junbound");
+ isAvailable = true;
+ }
+ catch(UnsatisfiedLinkError e)
+ {
+ isAvailable = false;
+ }
+ }
+ }
+
+ /**
+ * Indicates whether the Unbound library is loaded.
+ * @return True when the JNI wrapper could be loaded, false otherwise.
+ */
+ public static boolean isAvailable()
+ {
+ return isAvailable;
+ }
+
+ /**
+ * Set debug verbosity for the context. Output is directed to stderr. Higher
+ * debug level gives more output.
+ *
+ * @param context context.
+ * @param level The debug level.
+ */
+ public static native void setDebugLevel(long context, int level);
+
+ /**
+ * Create a resolving and validation context.
+ * @return a new context. default initialization. returns NULL on error.
+ */
+ public static native long createContext();
+
+ /**
+ * Destroy a validation context and free all its resources. Outstanding
+ * async queries are killed and callbacks are not called for them.
+ *
+ * @param context context to delete
+ */
+ public static native void deleteContext(long context);
+
+ /**
+ * Set machine to forward DNS queries to, the caching resolver to use.
+ * <p>
+ * IP4 or IP6 address. Forwards all DNS requests to that machine, which is
+ * expected to run a recursive resolver. If the proxy is not DNSSEC-capable,
+ * validation may fail. Can be called several times, in that case the
+ * addresses are used as backup servers.
+ *
+ * @param context context. At this time it is only possible to set
+ * configuration before the first resolve is done.
+ * @param server address, IP4 or IP6 in string format. If the server is
+ * NULL, forwarding is disabled.
+ */
+ public static native void setForwarder(long context, String server);
+
+ /**
+ * Add a trust anchor to the given context.
+ * <p>
+ * The trust anchor is a string, on one line, that holds a valid DNSKEY or
+ * DS RR.
+ *
+ * @param context context. At this time it is only possible to add trusted
+ * keys before the first resolve is done.
+ * @param anchor string, with zone-format RR on one line. [domainname] [TTL
+ * optional] [type] [class optional] [rdata contents]
+ */
+ public static native void addTrustAnchor(long context, String anchor);
+
+ /**
+ * Perform resolution and validation of the target name.
+ *
+ * @param context context. The context is finalized, and can no longer
+ * accept config changes.
+ * @param name domain name in text format (a zero terminated text string).
+ * @param rrtype type of RR in host order, 1 is A (address).
+ * @param rrclass class of RR in host order, 1 is IN (for internet).
+ * @return the result data is returned in a newly allocated result
+ * structure. May be NULL on return, return value is set to an error
+ * in that case (out of memory).
+ * @throws UnboundException when an error occurred.
+ */
+ public static native UnboundResult resolve(long context, String name,
+ int rrtype, int rrclass) throws UnboundException;
+
+ /**
+ * Perform resolution and validation of the target name.
+ * <p>
+ * Asynchronous, after a while, the callback will be called with your data
+ * and the result.
+ *
+ * @param context context. If no thread or process has been created yet to
+ * perform the work in the background, it is created now. The
+ * context is finalized, and can no longer accept config changes.
+ * @param name domain name in text format (a string).
+ * @param rrtype type of RR in host order, 1 is A.
+ * @param rrclass class of RR in host order, 1 is IN (for internet).
+ * @param data this data is your own data (you can pass null), and is passed
+ * on to the callback function.
+ * @param cb this is called on completion of the resolution.
+ * @return an identifier number is returned for the query as it is in
+ * progress. It can be used to cancel the query.
+ * @throws UnboundException when an error occurred.
+ */
+ public static native int resolveAsync(long context, String name,
+ int rrtype, int rrclass, Object data, UnboundCallback cb)
+ throws UnboundException;
+
+ /**
+ * Cancel an async query in progress. Its callback will not be called.
+ *
+ * @param context context.
+ * @param asyncId which query to cancel.
+ * @throws UnboundException This routine can error if the async_id passed
+ * does not exist or has already been delivered. If another
+ * thread is processing results at the same time, the result may
+ * be delivered at the same time and the cancel fails with an
+ * error. Also the cancel can fail due to a system error, no
+ * memory or socket failures.
+ */
+ public static native void cancelAsync(long context, int asyncId)
+ throws UnboundException;
+
+ /**
+ * Convert error value to a human readable string.
+ *
+ * @param code error code from one of the Unbound functions.
+ * @return text string of the error code.
+ */
+ public static native String errorCodeToString(int code);
+
+ /**
+ * Wait for a context to finish with results. Call this routine to continue
+ * processing results from the validating resolver. After the wait, there
+ * are no more outstanding asynchronous queries.
+ *
+ * @param context context.
+ * @throws UnboundException when an error occurred.
+ */
+ public static native void processAsync(long context)
+ throws UnboundException;
+
+ /**
+ * Interface for the async resolve callback.
+ */
+ public interface UnboundCallback
+ {
+ /**
+ * Called on completion of the async resolution.
+ *
+ * @param data the same object as passed to
+ * {@link UnboundApi#resolveAsync(long, String, int, int,
+ * Object, UnboundCallback)}
+ * @param err 0 when a result has been found, an Unbound error code
+ * otherwise
+ * @param result a newly allocated result structure. The result may be
+ * null, in that case err is set.
+ */
+ public void UnboundResolveCallback(Object data, int err,
+ UnboundResult result);
+ }
+}
diff --git a/src/net/java/sip/communicator/util/dns/UnboundException.java b/src/net/java/sip/communicator/util/dns/UnboundException.java new file mode 100644 index 0000000..923d22d --- /dev/null +++ b/src/net/java/sip/communicator/util/dns/UnboundException.java @@ -0,0 +1,26 @@ +/*
+ * Jitsi, the OpenSource Java VoIP and Instant Messaging client.
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ */
+package net.java.sip.communicator.util.dns;
+
+/**
+ * Exception that is being thrown when native Unbound code resulted in an error.
+ *
+ * @author Ingo Bauersachs
+ */
+public class UnboundException
+ extends Exception
+{
+ /**
+ * Creates a new instance of this class.
+ *
+ * @param message the detail message.
+ */
+ public UnboundException(String message)
+ {
+ super(message);
+ }
+}
diff --git a/src/net/java/sip/communicator/util/dns/UnboundResolver.java b/src/net/java/sip/communicator/util/dns/UnboundResolver.java new file mode 100644 index 0000000..ccd706f --- /dev/null +++ b/src/net/java/sip/communicator/util/dns/UnboundResolver.java @@ -0,0 +1,383 @@ +/*
+ * Jitsi, the OpenSource Java VoIP and Instant Messaging client.
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ */
+package net.java.sip.communicator.util.dns;
+
+import java.io.*;
+import java.net.*;
+import java.util.*;
+import java.util.concurrent.*;
+
+import net.java.sip.communicator.util.*;
+
+import org.xbill.DNS.*;
+
+/**
+ * Implementation of the {@link Resolver} interface, wrapping the native NLnet
+ * Labs Unbound resolver. Only the basic methods for queries are supported.
+ *
+ * @author Ingo Bauersachs
+ */
+public class UnboundResolver
+ implements Resolver
+{
+ private final static Logger logger =
+ Logger.getLogger(UnboundResolver.class);
+
+ /**
+ * Helper class to synchronize on asynchronous queries.
+ */
+ private static class CallbackData
+ {
+ /**
+ * The resolver consumer that wishes to be informed when the request
+ * completed.
+ */
+ ResolverListener listener;
+
+ /**
+ * The unbound session context.
+ */
+ long context;
+
+ /**
+ * The ID of the unbound async query.
+ */
+ int asyncId;
+
+ /**
+ * Java synchronization on top of unbound.
+ */
+ CountDownLatch sync = new CountDownLatch(1);
+ }
+
+ /**
+ * Timeout for DNS queries.
+ */
+ private int timeout = 10000;
+
+ /**
+ * The recursive DNS servers answering our queries.
+ */
+ private String[] forwarders;
+
+ /**
+ * DNSSEC trust anchors for signed zones (usually for the root zone).
+ */
+ private List<String> trustAnchors = new LinkedList<String>();
+
+ /**
+ * Pool that executes our queries.
+ */
+ private ExecutorService threadPool;
+
+ /**
+ * Creates a new instance of this class.
+ */
+ public UnboundResolver()
+ {
+ threadPool = Executors.newCachedThreadPool();
+ }
+
+ /**
+ * Sets a list of forwarders to use instead of the system default.
+ *
+ * @param forwarders list of servers to use for our queries.
+ */
+ public void setForwarders(String[] forwarders)
+ {
+ this.forwarders = forwarders;
+ }
+
+ /**
+ * Adds a DNSSEC trust anchor validation of the DNSKEYs.
+ *
+ * @param anchor trust anchor in the form of
+ * "'zone' IN DS 'key tag' 'algorithm' 'digest type' 'digest'"
+ */
+ public void addTrustAnchor(String anchor)
+ {
+ trustAnchors.add(anchor);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public SecureMessage send(final Message query) throws IOException
+ {
+ Future<SecureMessage> future = threadPool.submit(
+ new Callable<SecureMessage>()
+ {
+ public SecureMessage call() throws Exception
+ {
+ if(logger.isDebugEnabled())
+ logger.debug(query);
+
+ SecureMessage secureMessage = null;
+ final long context = prepareContext();
+ try
+ {
+ UnboundResult result = UnboundApi.resolve(
+ context,
+ query.getQuestion().getName().toString(),
+ query.getQuestion().getType(),
+ query.getQuestion().getDClass()
+ );
+ secureMessage = new SecureMessage(result);
+ validateMessage(secureMessage);
+ }
+ finally
+ {
+ UnboundApi.deleteContext(context);
+ if(logger.isDebugEnabled() && secureMessage != null)
+ logger.debug(secureMessage);
+ }
+
+ return secureMessage;
+ }
+ });
+ try
+ {
+ return future.get(timeout, TimeUnit.SECONDS);
+ }
+ catch (InterruptedException e)
+ {
+ logger.error(e);
+ throw new IOException(e.getMessage());
+ }
+ catch (ExecutionException e)
+ {
+ if(e.getCause() instanceof DnssecRuntimeException)
+ throw new DnssecRuntimeException(e.getCause().getMessage());
+ logger.error(e);
+ throw new IOException(e.getMessage());
+ }
+ catch (TimeoutException e)
+ {
+ throw new SocketTimeoutException(e.getMessage());
+ }
+ }
+
+ /**
+ * Method to allow overriders to inspect the message. This class'
+ * implementation does nothing.
+ *
+ * @param msg The message to inspect.
+ * @throws DnssecRuntimeException if the inspector does not want the code to
+ * continue normal processing of the answer.
+ */
+ protected void validateMessage(SecureMessage msg)
+ throws DnssecRuntimeException
+ {
+ }
+
+ /**
+ * Prepares a unbound session context initialized with forwarders and trust
+ * anchors.
+ *
+ * @return The context id
+ */
+ private long prepareContext()
+ {
+ final long context = UnboundApi.createContext();
+ if(logger.isTraceEnabled())
+ UnboundApi.setDebugLevel(context, 100);
+ for(String fwd : forwarders == null
+ ? ResolverConfig.getCurrentConfig().servers()
+ : forwarders)
+ {
+ fwd = fwd.trim();
+ if(NetworkUtils.isValidIPAddress(fwd))
+ {
+ if(fwd.startsWith("["))
+ fwd = fwd.substring(1, fwd.length() - 1);
+ UnboundApi.setForwarder(context, fwd);
+ }
+ }
+ for(String anchor : trustAnchors)
+ {
+ UnboundApi.addTrustAnchor(context, anchor);
+ }
+ return context;
+ }
+
+ /**
+ * Cleans up an Unbound session context.
+ *
+ * @param cbData The helper object of the asynchronous call.
+ * @param cancelAsync Whether an outstanding asynchronous unbound query
+ * should be canceled.
+ */
+ private static synchronized void deleteContext(CallbackData cbData,
+ boolean cancelAsync)
+ {
+ if(cbData.context == 0)
+ return;
+
+ if(cancelAsync)
+ {
+ try
+ {
+ UnboundApi.cancelAsync(cbData.context, cbData.asyncId);
+ }
+ catch (UnboundException ignore)
+ {}
+ }
+ UnboundApi.deleteContext(cbData.context);
+ cbData.context = 0;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.xbill.DNS.Resolver#sendAsync(org.xbill.DNS.Message,
+ * org.xbill.DNS.ResolverListener)
+ */
+ public CallbackData sendAsync(Message query, ResolverListener listener)
+ {
+ if(listener == null)
+ throw new IllegalArgumentException("listener cannot be null");
+
+ final long context = prepareContext();
+ final CallbackData cbData = new CallbackData();
+ cbData.listener = listener;
+ cbData.context = context;
+ int asyncId;
+ try
+ {
+ asyncId = UnboundApi.resolveAsync(
+ context,
+ query.getQuestion().getName().toString(),
+ query.getQuestion().getType(),
+ query.getQuestion().getDClass(),
+ cbData,
+ new UnboundApi.UnboundCallback()
+ {
+ public void UnboundResolveCallback(Object data, int err,
+ UnboundResult result)
+ {
+ CallbackData cbData = (CallbackData)data;
+ deleteContext(cbData, false);
+
+ ResolverListener l = cbData.listener;
+ if(err == 0)
+ {
+ try
+ {
+ l.receiveMessage(data,
+ new SecureMessage(result));
+ }
+ catch (IOException e)
+ {
+ l.handleException(data, e);
+ }
+ }
+ else
+ l.handleException(data,
+ new Exception(
+ UnboundApi.errorCodeToString(err)));
+
+ cbData.sync.countDown();
+ }
+ }
+ );
+ }
+ catch (UnboundException e)
+ {
+ listener.handleException(null, e);
+ return null;
+ }
+ cbData.asyncId = asyncId;
+ threadPool.execute(new Runnable()
+ {
+ public void run()
+ {
+ try
+ {
+ UnboundApi.processAsync(context);
+ }
+ catch(UnboundException ex)
+ {
+ cbData.listener.handleException(this, ex);
+ deleteContext(cbData, false);
+ cbData.sync.countDown();
+ }
+ }
+ });
+ return cbData;
+ }
+
+ /**
+ * Not supported.
+ * @throws UnsupportedOperationException
+ */
+ public void setEDNS(int level)
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Not supported.
+ * @throws UnsupportedOperationException
+ */
+ @SuppressWarnings("rawtypes")
+ public void setEDNS(int level, int payloadSize, int flags, List options)
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Not supported.
+ * @throws UnsupportedOperationException
+ */
+ public void setIgnoreTruncation(boolean flag)
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Not supported.
+ * @throws UnsupportedOperationException
+ */
+ public void setPort(int port)
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Not supported.
+ * @throws UnsupportedOperationException
+ */
+ public void setTCP(boolean flag)
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Not supported.
+ * @throws UnsupportedOperationException
+ */
+ public void setTSIGKey(TSIG key)
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ /* (non-Javadoc)
+ * @see org.xbill.DNS.Resolver#setTimeout(int)
+ */
+ public void setTimeout(int secs)
+ {
+ timeout = secs * 1000;
+ }
+
+ /* (non-Javadoc)
+ * @see org.xbill.DNS.Resolver#setTimeout(int, int)
+ */
+ public void setTimeout(int secs, int msecs)
+ {
+ timeout = secs * 1000 + msecs;
+ }
+}
diff --git a/src/net/java/sip/communicator/util/dns/UnboundResult.java b/src/net/java/sip/communicator/util/dns/UnboundResult.java new file mode 100644 index 0000000..8aa602b --- /dev/null +++ b/src/net/java/sip/communicator/util/dns/UnboundResult.java @@ -0,0 +1,117 @@ +/*
+ * Jitsi, the OpenSource Java VoIP and Instant Messaging client.
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ */
+package net.java.sip.communicator.util.dns;
+
+/**
+ * Class that contains the answer to query processed by the native Unbound
+ * resolver. Corresponds to the <a
+ * href="http://unbound.net/documentation/doxygen/structub__result.html"
+ * >ub_result</a> data structure.
+ *
+ * The fields {@link #data} and {@link #canonname} are not filled.
+ * <p>
+ * The JavaDoc of these fields is directly copied from libunbound, licensed as
+ * follows:
+ * <p>
+ * Copyright (c) 2007, NLnet Labs. All rights reserved.
+ *
+ * This software is open source.
+ *
+ * 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 NLNET LABS 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 REGENTS 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.
+ * @author Ingo Bauersachs
+ */
+public class UnboundResult
+{
+ /**
+ * The original question, name text string.
+ */
+ String qname;
+
+ /**
+ * the type asked for
+ */
+ int qtype;
+
+ /**
+ * the type asked for
+ */
+ int qclass;
+
+
+ /**
+ * a list of network order DNS rdata items, terminated with a NULL pointer,
+ * so that data[0] is the first result entry, data[1] the second, and the
+ * last entry is NULL.
+ */
+ byte[][] data;
+
+ /**
+ * canonical name for the result (the final cname).
+ */
+ String canonname;
+
+ /**
+ * DNS RCODE for the result.
+ */
+ int rcode;
+
+ /**
+ * The DNS answer packet.
+ */
+ byte[] answerPacket;
+
+
+ /**
+ * If there is any data, this is true.
+ */
+ boolean haveData;
+
+ /**
+ * If there was no data, and the domain did not exist, this is true.
+ */
+ boolean nxDomain;
+
+ /**
+ * True, if the result is validated securely.
+ */
+ boolean secure;
+
+ /**
+ * If the result was not secure ({@link #secure} == false), and this result
+ * is due to a security failure, bogus is true.
+ */
+ boolean bogus;
+
+ /**
+ * If the result is bogus this contains a string (zero terminated) that
+ * describes the failure.
+ */
+ String whyBogus;
+}
diff --git a/src/net/java/sip/communicator/util/dns/util.dns.manifest.mf b/src/net/java/sip/communicator/util/dns/util.dns.manifest.mf new file mode 100644 index 0000000..1ec80b3 --- /dev/null +++ b/src/net/java/sip/communicator/util/dns/util.dns.manifest.mf @@ -0,0 +1,18 @@ +Bundle-Activator: net.java.sip.communicator.util.dns.DnsUtilActivator
+Bundle-Name: SIP Communicator DNS Utility Packages
+Bundle-SymbolicName: net.java.sip.communicator.util.dns
+Bundle-Description: A bundle that export packages with DNS utility classes.
+Bundle-Vendor: sip-communicator.org
+Bundle-Version: 0.0.1
+System-Bundle: yes
+Import-Package:
+ org.osgi.framework,
+ net.java.sip.communicator.util,
+ net.java.sip.communicator.util.swing,
+ net.java.sip.communicator.service.resources,
+ net.java.sip.communicator.service.configuration,
+ net.java.sip.communicator.service.notification,
+ sun.net.dns,
+ org.xbill.DNS,
+ javax.swing
+Export-Package: net.java.sip.communicator.util.dns
|