001/** 002 * 003 * Copyright 2019 Florian Schmaus 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.jivesoftware.smackx.dox; 018 019import java.io.IOException; 020import java.util.Map; 021import java.util.WeakHashMap; 022import java.util.logging.Logger; 023 024import org.jivesoftware.smack.Manager; 025import org.jivesoftware.smack.SmackException.NoResponseException; 026import org.jivesoftware.smack.SmackException.NotConnectedException; 027import org.jivesoftware.smack.XMPPConnection; 028import org.jivesoftware.smack.XMPPException.XMPPErrorException; 029import org.jivesoftware.smack.iqrequest.AbstractIqRequestHandler; 030import org.jivesoftware.smack.iqrequest.IQRequestHandler.Mode; 031import org.jivesoftware.smack.packet.IQ; 032import org.jivesoftware.smack.packet.StanzaError; 033import org.jivesoftware.smack.packet.StanzaError.Condition; 034import org.jivesoftware.smack.packet.StanzaError.Type; 035import org.jivesoftware.smack.util.RandomUtil; 036 037import org.jivesoftware.smackx.disco.ServiceDiscoveryManager; 038import org.jivesoftware.smackx.dox.element.DnsIq; 039 040import org.jxmpp.jid.Jid; 041import org.minidns.dnsmessage.DnsMessage; 042import org.minidns.dnsmessage.Question; 043 044public final class DnsOverXmppManager extends Manager { 045 046 private static final Logger LOGGER = Logger.getLogger(DnsOverXmppManager.class.getName()); 047 048 private static final Map<XMPPConnection, DnsOverXmppManager> INSTANCES = new WeakHashMap<>(); 049 050 public static synchronized DnsOverXmppManager getInstanceFor(XMPPConnection connection) { 051 DnsOverXmppManager manager = INSTANCES.get(connection); 052 if (manager == null) { 053 manager = new DnsOverXmppManager(connection); 054 INSTANCES.put(connection, manager); 055 } 056 return manager; 057 } 058 059 private static final String NAMESPACE = DnsIq.NAMESPACE; 060 061 private static DnsOverXmppResolver defaultResolver; 062 063 public static void setDefaultDnsOverXmppResolver(DnsOverXmppResolver resolver) { 064 defaultResolver = resolver; 065 } 066 067 private final ServiceDiscoveryManager serviceDiscoveryManager; 068 069 private DnsOverXmppResolver resolver = defaultResolver; 070 071 private boolean enabled; 072 073 private final AbstractIqRequestHandler dnsIqRequestHandler = new AbstractIqRequestHandler( 074 DnsIq.ELEMENT, DnsIq.NAMESPACE, IQ.Type.get, Mode.async) { 075 076 @Override 077 public IQ handleIQRequest(IQ iqRequest) { 078 DnsOverXmppResolver resolver = DnsOverXmppManager.this.resolver; 079 if (resolver == null) { 080 LOGGER.info("Resolver was null while attempting to handle " + iqRequest); 081 return null; 082 } 083 084 DnsIq dnsIqRequest = (DnsIq) iqRequest; 085 DnsMessage query = dnsIqRequest.getDnsMessage(); 086 087 DnsMessage response; 088 try { 089 response = resolver.resolve(query); 090 } catch (IOException exception) { 091 StanzaError errorBuilder = StanzaError.getBuilder() 092 .setType(Type.CANCEL) 093 .setCondition(Condition.internal_server_error) 094 .setDescriptiveEnText("Exception while resolving your DNS query", exception) 095 .build() 096 ; 097 098 IQ errorResponse = IQ.createErrorResponse(iqRequest, errorBuilder); 099 return errorResponse; 100 } 101 102 if (query.id != response.id) { 103 // The ID may not match because the resolver returned a cached result. 104 response = response.asBuilder().setId(query.id).build(); 105 } 106 107 DnsIq dnsIqResult = new DnsIq(response); 108 dnsIqResult.setType(IQ.Type.result); 109 return dnsIqResult; 110 } 111 }; 112 113 private DnsOverXmppManager(XMPPConnection connection) { 114 super(connection); 115 this.serviceDiscoveryManager = ServiceDiscoveryManager.getInstanceFor(connection); 116 } 117 118 public synchronized void setDnsOverXmppResolver(DnsOverXmppResolver resolver) { 119 this.resolver = resolver; 120 if (resolver == null) { 121 disable(); 122 } 123 } 124 125 public synchronized void enable() { 126 if (enabled) return; 127 128 if (resolver == null) { 129 throw new IllegalStateException("No DnsOverXmppResolver configured"); 130 } 131 132 XMPPConnection connection = connection(); 133 if (connection == null) return; 134 135 connection.registerIQRequestHandler(dnsIqRequestHandler); 136 serviceDiscoveryManager.addFeature(NAMESPACE); 137 } 138 139 public synchronized void disable() { 140 if (!enabled) return; 141 142 XMPPConnection connection = connection(); 143 if (connection == null) return; 144 145 serviceDiscoveryManager.removeFeature(NAMESPACE); 146 connection.unregisterIQRequestHandler(dnsIqRequestHandler); 147 } 148 149 public boolean isSupported(Jid jid) 150 throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { 151 return serviceDiscoveryManager.supportsFeature(jid, NAMESPACE); 152 } 153 154 public DnsMessage query(Jid jid, Question question) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { 155 DnsMessage queryMessage = DnsMessage.builder() 156 .addQuestion(question) 157 .setId(RandomUtil.nextSecureRandomInt()) 158 .setRecursionDesired(true) 159 .build(); 160 return query(jid, queryMessage); 161 } 162 163 public DnsMessage query(Jid jid, DnsMessage query) 164 throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { 165 DnsIq queryIq = new DnsIq(query, jid); 166 167 DnsIq responseIq = connection().sendIqRequestAndWaitForResponse(queryIq); 168 169 return responseIq.getDnsMessage(); 170 } 171}