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