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