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