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 file.getParentFile().mkdirs();
141 file.createNewFile();
142 OutputStream fileOutputStream = getOutputStream(file);
143 if (fileOutputStream==null) {
144 callback.onFileTransferAborted();
145 return;
146 }
147 long remainingSize = file.getExpectedSize();
148 byte[] buffer = new byte[8192];
149 int count = buffer.length;
150 while(remainingSize > 0) {
151 count = inputStream.read(buffer);
152 if (count==-1) {
153 callback.onFileTransferAborted();
154 return;
155 } else {
156 fileOutputStream.write(buffer, 0, count);
157 digest.update(buffer, 0, count);
158 remainingSize-=count;
159 }
160 }
161 fileOutputStream.flush();
162 fileOutputStream.close();
163 file.setSha1Sum(CryptoHelper.bytesToHex(digest.digest()));
164 callback.onFileTransmitted(file);
165 } catch (FileNotFoundException e) {
166 callback.onFileTransferAborted();
167 } catch (IOException e) {
168 callback.onFileTransferAborted();
169 } catch (NoSuchAlgorithmException e) {
170 callback.onFileTransferAborted();
171 }
172 }
173 }).start();
174 }
175
176 public boolean isProxy() {
177 return this.candidate.getType() == JingleCandidate.TYPE_PROXY;
178 }
179
180 public boolean needsActivation() {
181 return (this.isProxy() && !this.activated);
182 }
183
184 public void disconnect() {
185 if (this.socket!=null) {
186 try {
187 this.socket.close();
188 } catch (IOException e) {
189
190 }
191 }
192 }
193
194 public boolean isEstablished() {
195 return this.isEstablished;
196 }
197
198 public JingleCandidate getCandidate() {
199 return this.candidate;
200 }
201
202 public void setActivated(boolean activated) {
203 this.activated = activated;
204 }
205}