001/** 002 * 003 * Copyright 2019-2021 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.util.RandomUtil; 035 036import org.jivesoftware.smackx.disco.ServiceDiscoveryManager; 037import org.jivesoftware.smackx.dox.element.DnsIq; 038 039import org.jxmpp.jid.Jid; 040import org.minidns.dnsmessage.DnsMessage; 041import org.minidns.dnsmessage.Question; 042 043public final class DnsOverXmppManager extends Manager { 044 045 private static final Logger LOGGER = Logger.getLogger(DnsOverXmppManager.class.getName()); 046 047 private static final Map<XMPPConnection, DnsOverXmppManager> INSTANCES = new WeakHashMap<>(); 048 049 public static synchronized DnsOverXmppManager getInstanceFor(XMPPConnection connection) { 050 DnsOverXmppManager manager = INSTANCES.get(connection); 051 if (manager == null) { 052 manager = new DnsOverXmppManager(connection); 053 INSTANCES.put(connection, manager); 054 } 055 return manager; 056 } 057 058 private static final String NAMESPACE = DnsIq.NAMESPACE; 059 060 private static DnsOverXmppResolver defaultResolver; 061 062 public static void setDefaultDnsOverXmppResolver(DnsOverXmppResolver resolver) { 063 defaultResolver = resolver; 064 } 065 066 private final ServiceDiscoveryManager serviceDiscoveryManager; 067 068 private DnsOverXmppResolver resolver = defaultResolver; 069 070 private boolean enabled; 071 072 private final AbstractIqRequestHandler dnsIqRequestHandler = new AbstractIqRequestHandler( 073 DnsIq.ELEMENT, DnsIq.NAMESPACE, IQ.Type.get, Mode.async) { 074 075 @Override 076 public IQ handleIQRequest(IQ iqRequest) { 077 DnsOverXmppResolver resolver = DnsOverXmppManager.this.resolver; 078 if (resolver == null) { 079 LOGGER.info("Resolver was null while attempting to handle " + iqRequest); 080 return null; 081 } 082 083 DnsIq dnsIqRequest = (DnsIq) iqRequest; 084 DnsMessage query = dnsIqRequest.getDnsMessage(); 085 086 DnsMessage response; 087 try { 088 response = resolver.resolve(query); 089 } catch (IOException exception) { 090 StanzaError errorBuilder = StanzaError.getBuilder() 091 .setType(StanzaError.Type.CANCEL) 092 .setCondition(Condition.internal_server_error) 093 .setDescriptiveEnText("Exception while resolving your DNS query", exception) 094 .build() 095 ; 096 097 IQ errorResponse = IQ.createErrorResponse(iqRequest, errorBuilder); 098 return errorResponse; 099 } 100 101 if (query.id != response.id) { 102 // The ID may not match because the resolver returned a cached result. 103 response = response.asBuilder().setId(query.id).build(); 104 } 105 106 DnsIq dnsIqResult = new DnsIq(response); 107 dnsIqResult.setType(IQ.Type.result); 108 return dnsIqResult; 109 } 110 }; 111 112 private DnsOverXmppManager(XMPPConnection connection) { 113 super(connection); 114 this.serviceDiscoveryManager = ServiceDiscoveryManager.getInstanceFor(connection); 115 } 116 117 public synchronized void setDnsOverXmppResolver(DnsOverXmppResolver resolver) { 118 this.resolver = resolver; 119 if (resolver == null) { 120 disable(); 121 } 122 } 123 124 public synchronized void enable() { 125 if (enabled) return; 126 127 if (resolver == null) { 128 throw new IllegalStateException("No DnsOverXmppResolver configured"); 129 } 130 131 XMPPConnection connection = connection(); 132 if (connection == null) return; 133 134 connection.registerIQRequestHandler(dnsIqRequestHandler); 135 serviceDiscoveryManager.addFeature(NAMESPACE); 136 } 137 138 public synchronized void disable() { 139 if (!enabled) return; 140 141 XMPPConnection connection = connection(); 142 if (connection == null) return; 143 144 serviceDiscoveryManager.removeFeature(NAMESPACE); 145 connection.unregisterIQRequestHandler(dnsIqRequestHandler); 146 } 147 148 public boolean isSupported(Jid jid) 149 throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { 150 return serviceDiscoveryManager.supportsFeature(jid, NAMESPACE); 151 } 152 153 public DnsMessage query(Jid jid, Question question) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { 154 DnsMessage queryMessage = DnsMessage.builder() 155 .addQuestion(question) 156 .setId(RandomUtil.nextSecureRandomInt()) 157 .setRecursionDesired(true) 158 .build(); 159 return query(jid, queryMessage); 160 } 161 162 public DnsMessage query(Jid jid, DnsMessage query) 163 throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { 164 DnsIq queryIq = new DnsIq(query, jid); 165 166 DnsIq responseIq = connection().sendIqRequestAndWaitForResponse(queryIq); 167 168 return responseIq.getDnsMessage(); 169 } 170}