001/** 002 * 003 * Copyright 2017 Paul Schaub 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.jingle.transports.jingle_s5b; 018 019import java.util.ArrayList; 020import java.util.Iterator; 021import java.util.List; 022import java.util.WeakHashMap; 023import java.util.logging.Level; 024import java.util.logging.Logger; 025 026import org.jivesoftware.smack.SmackException; 027import org.jivesoftware.smack.XMPPConnection; 028import org.jivesoftware.smack.XMPPException; 029import org.jivesoftware.smack.packet.IQ; 030import org.jivesoftware.smackx.bytestreams.socks5.Socks5BytestreamManager; 031import org.jivesoftware.smackx.bytestreams.socks5.Socks5Proxy; 032import org.jivesoftware.smackx.bytestreams.socks5.packet.Bytestream; 033import org.jivesoftware.smackx.jingle.JingleSession; 034import org.jivesoftware.smackx.jingle.element.Jingle; 035import org.jivesoftware.smackx.jingle.element.JingleAction; 036import org.jivesoftware.smackx.jingle.element.JingleContent; 037import org.jivesoftware.smackx.jingle.provider.JingleContentProviderManager; 038import org.jivesoftware.smackx.jingle.transports.JingleTransportManager; 039import org.jivesoftware.smackx.jingle.transports.JingleTransportSession; 040import org.jivesoftware.smackx.jingle.transports.jingle_s5b.elements.JingleS5BTransport; 041import org.jivesoftware.smackx.jingle.transports.jingle_s5b.provider.JingleS5BTransportProvider; 042 043import org.jxmpp.jid.FullJid; 044import org.jxmpp.jid.Jid; 045 046/** 047 * Manager for Jingle SOCKS5 Bytestream transports (XEP-0261). 048 */ 049public final class JingleS5BTransportManager extends JingleTransportManager<JingleS5BTransport> { 050 051 private static final Logger LOGGER = Logger.getLogger(JingleS5BTransportManager.class.getName()); 052 053 private static final WeakHashMap<XMPPConnection, JingleS5BTransportManager> INSTANCES = new WeakHashMap<>(); 054 055 private List<Bytestream.StreamHost> localStreamHosts = null; 056 private List<Bytestream.StreamHost> availableStreamHosts = null; 057 058 private static boolean useLocalCandidates = true; 059 private static boolean useExternalCandidates = true; 060 061 private JingleS5BTransportManager(XMPPConnection connection) { 062 super(connection); 063 JingleContentProviderManager.addJingleContentTransportProvider(getNamespace(), new JingleS5BTransportProvider()); 064 } 065 066 public static JingleS5BTransportManager getInstanceFor(XMPPConnection connection) { 067 JingleS5BTransportManager manager = INSTANCES.get(connection); 068 if (manager == null) { 069 manager = new JingleS5BTransportManager(connection); 070 INSTANCES.put(connection, manager); 071 } 072 return manager; 073 } 074 075 @Override 076 public String getNamespace() { 077 return JingleS5BTransport.NAMESPACE_V1; 078 } 079 080 @Override 081 public JingleTransportSession<JingleS5BTransport> transportSession(JingleSession jingleSession) { 082 return new JingleS5BTransportSession(jingleSession); 083 } 084 085 private List<Bytestream.StreamHost> queryAvailableStreamHosts() throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException { 086 Socks5BytestreamManager s5m = Socks5BytestreamManager.getBytestreamManager(getConnection()); 087 List<Jid> proxies = s5m.determineProxies(); 088 return determineStreamHostInfo(proxies); 089 } 090 091 private List<Bytestream.StreamHost> queryLocalStreamHosts() { 092 return Socks5BytestreamManager.getBytestreamManager(getConnection()) 093 .getLocalStreamHost(); 094 } 095 096 public List<Bytestream.StreamHost> getAvailableStreamHosts() throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException { 097 if (availableStreamHosts == null) { 098 availableStreamHosts = queryAvailableStreamHosts(); 099 } 100 return availableStreamHosts; 101 } 102 103 public List<Bytestream.StreamHost> getLocalStreamHosts() { 104 if (localStreamHosts == null) { 105 localStreamHosts = queryLocalStreamHosts(); 106 } 107 return localStreamHosts; 108 } 109 110 public List<Bytestream.StreamHost> determineStreamHostInfo(List<Jid> proxies) { 111 XMPPConnection connection = getConnection(); 112 List<Bytestream.StreamHost> streamHosts = new ArrayList<>(); 113 114 Iterator<Jid> iterator = proxies.iterator(); 115 while (iterator.hasNext()) { 116 Jid proxy = iterator.next(); 117 Bytestream request = new Bytestream(); 118 request.setType(IQ.Type.get); 119 request.setTo(proxy); 120 try { 121 Bytestream response = connection.createStanzaCollectorAndSend(request).nextResultOrThrow(); 122 streamHosts.addAll(response.getStreamHosts()); 123 } 124 catch (Exception e) { 125 iterator.remove(); 126 } 127 } 128 129 return streamHosts; 130 } 131 132 133 @Override 134 public void authenticated(XMPPConnection connection, boolean resumed) { 135 if (!resumed) try { 136 Socks5Proxy socks5Proxy = Socks5Proxy.getSocks5Proxy(); 137 if (!socks5Proxy.isRunning()) { 138 socks5Proxy.start(); 139 } 140 localStreamHosts = queryLocalStreamHosts(); 141 availableStreamHosts = queryAvailableStreamHosts(); 142 } catch (InterruptedException | SmackException.NoResponseException | SmackException.NotConnectedException | XMPPException.XMPPErrorException e) { 143 LOGGER.log(Level.WARNING, "Could not query available StreamHosts: " + e, e); 144 } 145 } 146 147 public Jingle createCandidateUsed(FullJid recipient, FullJid initiator, String sessionId, JingleContent.Senders contentSenders, 148 JingleContent.Creator contentCreator, String contentName, String streamId, 149 String candidateId) { 150 Jingle.Builder jb = Jingle.getBuilder(); 151 jb.setSessionId(sessionId).setInitiator(initiator).setAction(JingleAction.transport_info); 152 153 JingleContent.Builder cb = JingleContent.getBuilder(); 154 cb.setName(contentName).setCreator(contentCreator).setSenders(contentSenders); 155 156 JingleS5BTransport.Builder tb = JingleS5BTransport.getBuilder(); 157 tb.setCandidateUsed(candidateId).setStreamId(streamId); 158 159 Jingle jingle = jb.addJingleContent(cb.setTransport(tb.build()).build()).build(); 160 jingle.setFrom(getConnection().getUser().asFullJidOrThrow()); 161 jingle.setTo(recipient); 162 163 return jingle; 164 } 165 166 public Jingle createCandidateError(FullJid remote, FullJid initiator, String sessionId, JingleContent.Senders senders, JingleContent.Creator creator, String name, String streamId) { 167 Jingle.Builder jb = Jingle.getBuilder(); 168 jb.setSessionId(sessionId).setInitiator(initiator).setAction(JingleAction.transport_info); 169 170 JingleContent.Builder cb = JingleContent.getBuilder(); 171 cb.setName(name).setCreator(creator).setSenders(senders); 172 173 JingleS5BTransport.Builder tb = JingleS5BTransport.getBuilder(); 174 tb.setCandidateError().setStreamId(streamId); 175 176 Jingle jingle = jb.addJingleContent(cb.setTransport(tb.build()).build()).build(); 177 jingle.setFrom(getConnection().getUser().asFullJidOrThrow()); 178 jingle.setTo(remote); 179 180 return jingle; 181 } 182 183 public Jingle createProxyError(FullJid remote, FullJid initiator, String sessionId, 184 JingleContent.Senders senders, JingleContent.Creator creator, 185 String name, String streamId) { 186 Jingle.Builder jb = Jingle.getBuilder(); 187 jb.setSessionId(sessionId).setAction(JingleAction.transport_info).setInitiator(initiator); 188 189 JingleContent.Builder cb = JingleContent.getBuilder(); 190 cb.setSenders(senders).setCreator(creator).setName(name); 191 192 JingleS5BTransport.Builder tb = JingleS5BTransport.getBuilder(); 193 tb.setStreamId(sessionId).setProxyError().setStreamId(streamId); 194 195 Jingle jingle = jb.addJingleContent(cb.setTransport(tb.build()).build()).build(); 196 jingle.setTo(remote); 197 jingle.setFrom(getConnection().getUser().asFullJidOrThrow()); 198 return jingle; 199 } 200 201 public Jingle createCandidateActivated(FullJid remote, FullJid initiator, String sessionId, 202 JingleContent.Senders senders, JingleContent.Creator creator, 203 String name, String streamId, String candidateId) { 204 Jingle.Builder jb = Jingle.getBuilder(); 205 jb.setInitiator(initiator).setSessionId(sessionId).setAction(JingleAction.transport_info); 206 207 JingleContent.Builder cb = JingleContent.getBuilder(); 208 cb.setName(name).setCreator(creator).setSenders(senders); 209 210 JingleS5BTransport.Builder tb = JingleS5BTransport.getBuilder(); 211 tb.setStreamId(streamId).setCandidateActivated(candidateId); 212 213 Jingle jingle = jb.addJingleContent(cb.setTransport(tb.build()).build()).build(); 214 jingle.setFrom(getConnection().getUser().asFullJidOrThrow()); 215 jingle.setTo(remote); 216 return jingle; 217 } 218 219 public static void setUseLocalCandidates(boolean localCandidates) { 220 JingleS5BTransportManager.useLocalCandidates = localCandidates; 221 } 222 223 public static void setUseExternalCandidates(boolean externalCandidates) { 224 JingleS5BTransportManager.useExternalCandidates = externalCandidates; 225 } 226 227 public static boolean isUseLocalCandidates() { 228 return useLocalCandidates; 229 } 230 231 public static boolean isUseExternalCandidates() { 232 return useExternalCandidates; 233 } 234}