1package eu.siacs.conversations.xmpp.jingle;
2
3import java.io.FileNotFoundException;
4import java.io.IOException;
5import java.io.InputStream;
6import java.io.OutputStream;
7import java.net.Socket;
8import java.net.UnknownHostException;
9import java.security.MessageDigest;
10import java.security.NoSuchAlgorithmException;
11import java.util.Arrays;
12
13import eu.siacs.conversations.utils.CryptoHelper;
14
15public class JingleSocks5Transport extends JingleTransport {
16 private JingleCandidate candidate;
17 private String destination;
18 private OutputStream outputStream;
19 private InputStream inputStream;
20 private boolean isEstablished = false;
21 private boolean activated = false;
22 protected Socket socket;
23
24 public JingleSocks5Transport(JingleConnection jingleConnection,
25 JingleCandidate candidate) {
26 this.candidate = candidate;
27 try {
28 MessageDigest mDigest = MessageDigest.getInstance("SHA-1");
29 StringBuilder destBuilder = new StringBuilder();
30 destBuilder.append(jingleConnection.getSessionId());
31 if (candidate.isOurs()) {
32 destBuilder.append(jingleConnection.getAccountJid());
33 destBuilder.append(jingleConnection.getCounterPart());
34 } else {
35 destBuilder.append(jingleConnection.getCounterPart());
36 destBuilder.append(jingleConnection.getAccountJid());
37 }
38 mDigest.reset();
39 this.destination = CryptoHelper.bytesToHex(mDigest
40 .digest(destBuilder.toString().getBytes()));
41 } catch (NoSuchAlgorithmException e) {
42
43 }
44 }
45
46 public void connect(final OnTransportConnected callback) {
47 new Thread(new Runnable() {
48
49 @Override
50 public void run() {
51 try {
52 socket = new Socket(candidate.getHost(),
53 candidate.getPort());
54 inputStream = socket.getInputStream();
55 outputStream = socket.getOutputStream();
56 byte[] login = { 0x05, 0x01, 0x00 };
57 byte[] expectedReply = { 0x05, 0x00 };
58 byte[] reply = new byte[2];
59 outputStream.write(login);
60 inputStream.read(reply);
61 final String connect = Character.toString('\u0005')
62 + '\u0001' + '\u0000' + '\u0003' + '\u0028'
63 + destination + '\u0000' + '\u0000';
64 if (Arrays.equals(reply, expectedReply)) {
65 outputStream.write(connect.getBytes());
66 byte[] result = new byte[2];
67 inputStream.read(result);
68 int status = result[1];
69 if (status == 0) {
70 isEstablished = true;
71 callback.established();
72 } else {
73 callback.failed();
74 }
75 } else {
76 socket.close();
77 callback.failed();
78 }
79 } catch (UnknownHostException e) {
80 callback.failed();
81 } catch (IOException e) {
82 callback.failed();
83 }
84 }
85 }).start();
86
87 }
88
89 public void send(final JingleFile file,
90 final OnFileTransmissionStatusChanged callback) {
91 new Thread(new Runnable() {
92
93 @Override
94 public void run() {
95 InputStream fileInputStream = null;
96 try {
97 MessageDigest digest = MessageDigest.getInstance("SHA-1");
98 digest.reset();
99 fileInputStream = getInputStream(file);
100 if (fileInputStream == null) {
101 callback.onFileTransferAborted();
102 return;
103 }
104 int count;
105 byte[] buffer = new byte[8192];
106 while ((count = fileInputStream.read(buffer)) > 0) {
107 outputStream.write(buffer, 0, count);
108 digest.update(buffer, 0, count);
109 }
110 outputStream.flush();
111 file.setSha1Sum(CryptoHelper.bytesToHex(digest.digest()));
112 if (callback != null) {
113 callback.onFileTransmitted(file);
114 }
115 } catch (FileNotFoundException e) {
116 callback.onFileTransferAborted();
117 } catch (IOException e) {
118 callback.onFileTransferAborted();
119 } catch (NoSuchAlgorithmException e) {
120 callback.onFileTransferAborted();
121 } finally {
122 try {
123 if (fileInputStream != null) {
124 fileInputStream.close();
125 }
126 } catch (IOException e) {
127 callback.onFileTransferAborted();
128 }
129 }
130 }
131 }).start();
132
133 }
134
135 public void receive(final JingleFile file,
136 final OnFileTransmissionStatusChanged callback) {
137 new Thread(new Runnable() {
138
139 @Override
140 public void run() {
141 try {
142 MessageDigest digest = MessageDigest.getInstance("SHA-1");
143 digest.reset();
144 inputStream.skip(45);
145 socket.setSoTimeout(30000);
146 file.getParentFile().mkdirs();
147 file.createNewFile();
148 OutputStream fileOutputStream = getOutputStream(file);
149 if (fileOutputStream == null) {
150 callback.onFileTransferAborted();
151 return;
152 }
153 long remainingSize = file.getExpectedSize();
154 byte[] buffer = new byte[8192];
155 int count = buffer.length;
156 while (remainingSize > 0) {
157 count = inputStream.read(buffer);
158 if (count == -1) {
159 callback.onFileTransferAborted();
160 return;
161 } else {
162 fileOutputStream.write(buffer, 0, count);
163 digest.update(buffer, 0, count);
164 remainingSize -= count;
165 }
166 }
167 fileOutputStream.flush();
168 fileOutputStream.close();
169 file.setSha1Sum(CryptoHelper.bytesToHex(digest.digest()));
170 callback.onFileTransmitted(file);
171 } catch (FileNotFoundException e) {
172 callback.onFileTransferAborted();
173 } catch (IOException e) {
174 callback.onFileTransferAborted();
175 } catch (NoSuchAlgorithmException e) {
176 callback.onFileTransferAborted();
177 }
178 }
179 }).start();
180 }
181
182 public boolean isProxy() {
183 return this.candidate.getType() == JingleCandidate.TYPE_PROXY;
184 }
185
186 public boolean needsActivation() {
187 return (this.isProxy() && !this.activated);
188 }
189
190 public void disconnect() {
191 if (this.socket != null) {
192 try {
193 this.socket.close();
194 } catch (IOException e) {
195
196 }
197 }
198 }
199
200 public boolean isEstablished() {
201 return this.isEstablished;
202 }
203
204 public JingleCandidate getCandidate() {
205 return this.candidate;
206 }
207
208 public void setActivated(boolean activated) {
209 this.activated = activated;
210 }
211}