1 11 package org.eclipse.team.internal.ccvs.ssh; 12 13 16 17 import java.io.BufferedInputStream ; 18 import java.io.IOException ; 19 import java.io.InputStream ; 20 import java.io.InterruptedIOException ; 21 import java.io.OutputStream ; 22 import java.math.BigInteger ; 23 import java.net.Socket ; 24 25 import org.eclipse.core.runtime.IProgressMonitor; 26 import org.eclipse.osgi.util.NLS; 27 import org.eclipse.team.internal.ccvs.core.connection.CVSAuthenticationException; 28 import org.eclipse.team.internal.ccvs.core.util.Util; 29 import org.eclipse.team.internal.core.streams.PollingInputStream; 30 import org.eclipse.team.internal.core.streams.PollingOutputStream; 31 import org.eclipse.team.internal.core.streams.TimeoutOutputStream; 32 33 public class Client { 34 private static final String clientId = "SSH-1.5-Java 1.2.2\n"; 37 private static String serverId = null; 39 40 private static final int MAX_CLIENT_PACKET_SIZE = 1024; 42 43 private static final int SSH_MSG_DISCONNECT = 1; 45 private static final int SSH_SMSG_PUBLIC_KEY = 2; 46 private static final int SSH_CMSG_SESSION_KEY = 3; 47 private static final int SSH_CMSG_USER = 4; 48 private static final int SSH_CMSG_AUTH_PASSWORD = 9; 49 private static final int SSH_CMSG_REQUEST_PTY = 10; 50 private static final int SSH_CMSG_EXEC_SHELL = 12; 51 private static final int SSH_CMSG_EXEC_CMD = 13; 52 private static final int SSH_SMSG_SUCCESS = 14; 53 private static final int SSH_SMSG_FAILURE = 15; 54 private static final int SSH_CMSG_STDIN_DATA = 16; 55 private static final int SSH_SMSG_STDOUT_DATA = 17; 56 private static final int SSH_SMSG_STDERR_DATA = 18; 57 private static final int SSH_SMSG_EXITSTATUS = 20; 58 private static final int SSH_CMSG_EXIT_CONFIRMATION = 33; 59 private static final int SSH_MSG_DEBUG = 36; 60 61 private static String [] cipherNames = { "None", "IDEA", "DES", "3DES", "TSS", "RC4", "Blowfish" }; 64 private static int SSH_CIPHER_BLOWFISH = 6; 66 67 private int[] preferredCipherTypes = { SSH_CIPHER_BLOWFISH }; 69 70 private String host; 71 private int port; 72 private String username; 73 private String password; 74 private String command; 75 76 private Socket socket; 77 private InputStream socketIn; 78 private PollingOutputStream socketOut; 79 private InputStream is; 80 private OutputStream os; 81 private boolean connected = false; 82 private int timeout = -1; 83 84 private Cipher cipher = null; 85 86 private class StandardInputStream extends InputStream { 87 private ServerPacket packet = null; 88 private InputStream buffer = null; 89 private boolean atEnd = false; 90 private boolean closed = false; 91 92 public int available() throws IOException { 93 if (closed) { 94 throw new IOException (CVSSSHMessages.closed); 95 } 96 97 int available = buffer == null ? 0 : buffer.available(); 98 99 if (available == 0 && socketIn.available() > 0) { 100 fill(); 101 if (atEnd) { 102 return 0; 103 } 104 available = buffer.available(); 105 } 106 107 return available; 108 } 109 110 public void close() throws IOException { 111 if (!closed) { 112 closed = true; 113 if (packet != null) { 114 packet.close(false); 115 buffer = null; 116 packet = null; 117 } 118 } 119 } 120 121 public int read() throws IOException { 122 if (closed) { 123 throw new IOException (CVSSSHMessages.closed); 124 } 125 126 if (atEnd) { 127 return -1; 128 } 129 130 if (buffer == null || buffer.available() == 0) { 131 fill(); 132 if (atEnd) { 133 return -1; 134 } 135 } 136 137 return buffer.read(); 138 } 139 140 public int read(byte b[], int off, int len) throws IOException { 141 if (closed) { 142 throw new IOException (CVSSSHMessages.closed); 143 } 144 145 if (atEnd) { 146 return -1; 147 } 148 149 if (buffer == null || buffer.available() == 0) { 150 fill(); 151 if (atEnd) { 152 return -1; 153 } 154 } 155 156 return buffer.read(b, off, len); 157 } 158 159 private void fill() throws IOException { 160 if (buffer != null) { 161 buffer.close(); 162 } 163 164 packet = skip_SSH_MSG_DEBUG(); 165 int packetType = packet.getType(); 166 167 switch (packetType) { 168 case SSH_SMSG_STDOUT_DATA : 169 case SSH_SMSG_STDERR_DATA : 170 case SSH_MSG_DEBUG : 171 buffer = packet.getInputStream(); 172 Misc.readInt(buffer); 173 break; 174 case SSH_SMSG_EXITSTATUS : 175 buffer = null; 176 atEnd = true; 177 InputStream pis = packet.getInputStream(); 178 Misc.readInt(pis); 179 pis.close(); 180 send(SSH_CMSG_EXIT_CONFIRMATION, null); 181 break; 182 case SSH_MSG_DISCONNECT : 183 buffer = null; 184 atEnd = true; 185 handleDisconnect(packet.getInputStream()); 186 break; 187 default : 188 throw new IOException (NLS.bind(CVSSSHMessages.Client_packetType, (new Object [] {new Integer (packetType)}))); 189 } 190 } 191 192 private void handleDisconnect(InputStream in) throws IOException { 193 String description = null; 194 try { 195 description = Misc.readString(in); 196 } catch (IOException e) { 197 } finally { 198 in.close(); 199 } 200 201 if (description == null) { 203 description = CVSSSHMessages.Client_noDisconnectDescription; 204 } 205 206 throw new IOException (NLS.bind(CVSSSHMessages.Client_disconnectDescription, (new Object [] {description}))); 208 } 209 } 210 211 private class StandardOutputStream extends OutputStream { 212 private int MAX_BUFFER_SIZE = MAX_CLIENT_PACKET_SIZE; 213 private byte[] buffer = new byte[MAX_BUFFER_SIZE]; 214 private int bufpos = 0; 215 private boolean closed = false; 216 217 public void close() throws IOException { 218 if (!closed) { 219 try { 220 flush(); 221 } finally { 222 closed = true; 223 } 224 } 225 } 226 227 public void flush() throws IOException { 228 if (closed) { 229 throw new IOException (CVSSSHMessages.closed); 230 } 231 232 if (bufpos > 0) { 233 send(SSH_CMSG_STDIN_DATA, buffer, 0, bufpos); 234 bufpos = 0; 235 } 236 } 237 238 public void write(int b) throws IOException { 239 if (closed) { 240 throw new IOException (CVSSSHMessages.closed); 241 } 242 243 buffer[bufpos++] = (byte) b; 244 245 if (bufpos == MAX_BUFFER_SIZE) { 246 flush(); 247 } 248 } 249 250 public void write(byte b[], int off, int len) throws IOException { 251 if (closed) { 252 throw new IOException (CVSSSHMessages.closed); 253 } 254 255 int bytesWritten = 0; 256 int totalBytesWritten = 0; 257 258 if (bufpos > 0) { 259 bytesWritten = Math.min(MAX_BUFFER_SIZE - bufpos, len); 260 System.arraycopy(b, off, buffer, bufpos, bytesWritten); 261 bufpos += bytesWritten; 262 totalBytesWritten += bytesWritten; 263 264 if (bufpos == MAX_BUFFER_SIZE) { 265 flush(); 266 } 267 } 268 269 while (len - totalBytesWritten >= MAX_BUFFER_SIZE) { 270 send(SSH_CMSG_STDIN_DATA, b, off + totalBytesWritten, MAX_BUFFER_SIZE); 271 totalBytesWritten += MAX_BUFFER_SIZE; 272 } 273 274 if (totalBytesWritten < len) { 275 bytesWritten = len - totalBytesWritten; 276 System.arraycopy(b, off + totalBytesWritten, buffer, 0, bytesWritten); 277 bufpos += bytesWritten; 278 } 279 } 280 } 281 public Client(String host, int port, String username, String password) { 282 this.host = host; 283 this.port = port; 284 this.username = username; 285 this.password = password; 286 } 287 public Client(String host, int port, String username, String password, String command) { 288 this(host, port, username, password); 289 this.command = command; 290 } 291 public Client(String host, int port, String username, String password, String command, int timeout) { 292 this(host, port, username, password, command); 293 this.timeout = timeout; 294 } 295 298 private void cleanup() throws IOException { 299 try { 300 if (is != null) 301 is.close(); 302 } finally { 303 try { 304 if (os != null) 305 os.close(); 306 } finally { 307 try { 308 if (socketIn != null) 309 socketIn.close(); 310 } finally { 311 try { 312 if (socketOut != null) 313 socketOut.close(); 314 } finally { 315 try { 316 if (socket != null) 317 socket.close(); 318 } finally { 319 socket = null; 320 } 321 } 322 } 323 } 324 } 325 } 326 330 public void connect(IProgressMonitor monitor) throws IOException , CVSAuthenticationException { 331 if (connected || monitor.isCanceled()) 333 return; 334 335 try { 337 PollingInputStream pollingInputStream = null; 338 if (socket == null) { 340 try { 341 socket = Util.createSocket(host, port, monitor); 342 socket.setTcpNoDelay(true); 344 } catch (InterruptedIOException e) { 345 throw new InterruptedIOException (NLS.bind(CVSSSHMessages.Client_socket, (new Object [] {host}))); 347 348 } 349 if (timeout >= 0) { 350 socket.setSoTimeout(1000); 351 } 352 pollingInputStream = new PollingInputStream(socket.getInputStream(), 353 timeout > 0 ? timeout : 1, monitor); 354 socketIn = new BufferedInputStream (pollingInputStream); 355 356 socketOut = new PollingOutputStream(new TimeoutOutputStream( 357 socket.getOutputStream(), 8192 , 1000 , 1000 ), 358 timeout > 0 ? timeout : 1, monitor); 359 } 360 361 socketOut.setIsCancellable(false ); 368 pollingInputStream.setIsCancellable(false); 369 StringBuffer buf = new StringBuffer (); 370 int c; 371 while ((c = socketIn.read()) != '\n') { 372 if (c == -1) 373 throw new IOException (CVSSSHMessages.Client_socketClosed); 374 buf.append((char) c); 375 } 376 serverId = buf.toString(); 377 378 if (Policy.DEBUG_SSH_PROTOCOL) { 379 System.out.println("SSH > server ID: " + serverId); System.out.println("SSH > client ID: " + clientId); } 382 383 if (!serverId.startsWith("SSH-1.")) { String sshVersion = (serverId.startsWith("SSH-")? serverId:""); throw new IOException (NLS.bind(CVSSSHMessages.Client_sshProtocolVersion, new String [] { sshVersion })); 386 } 387 388 socketOut.write(clientId.getBytes()); 390 socketOut.flush(); 391 392 login(); 393 394 socketOut.setIsCancellable(true ); 395 pollingInputStream.setIsCancellable(true); 396 397 if( command == null ) { 400 startShell(); 401 } else { 402 executeCommand(); 403 } 404 405 is = new StandardInputStream(); 406 os = new StandardOutputStream(); 407 connected = true; 408 } finally { 410 if (! connected) cleanup(); 411 } 412 } 413 416 public void disconnect() throws IOException { 417 if (Policy.DEBUG_SSH_PROTOCOL) { 418 System.out.println("Disconnecting."); } 420 if (connected) { 421 connected = false; 422 try { 423 send(SSH_MSG_DISCONNECT, null); 424 } finally { 425 cleanup(); 426 } 427 } 428 } 429 public InputStream getInputStream() throws IOException { 430 if (!connected) { 431 throw new IOException (CVSSSHMessages.Client_notConnected); 432 } 433 434 return is; 435 } 436 public OutputStream getOutputStream() throws IOException { 437 if (!connected) { 438 throw new IOException (CVSSSHMessages.Client_notConnected); 439 } 440 441 return os; 442 } 443 444 private void startShell() throws IOException { 445 ServerPacket packet = null; 446 int packetType; 447 448 send_SSH_CMSG_REQUEST_PTY(); 449 450 try { 451 packet = skip_SSH_MSG_DEBUG(); 452 packetType = packet.getType(); 453 454 if (packetType != SSH_SMSG_SUCCESS) { 455 throw new IOException (NLS.bind(CVSSSHMessages.Client_packetType, (new Object [] {new Integer (packetType)}))); 456 } 457 } finally { 458 if (packet != null) { 459 packet.close(true ); 460 } 461 } 462 463 send(SSH_CMSG_EXEC_SHELL, null); 464 } 465 466 private void executeCommand() throws IOException { 467 send(SSH_CMSG_EXEC_CMD, command); 468 } 469 470 private void login() throws IOException , CVSAuthenticationException { 471 ServerPacket packet = null; 472 int packetType; 473 474 try { 475 packet = skip_SSH_MSG_DEBUG(); 476 packetType = packet.getType(); 477 478 if (packetType != SSH_SMSG_PUBLIC_KEY) { 479 throw new IOException (NLS.bind(CVSSSHMessages.Client_packetType, (new Object [] {new Integer (packetType)}))); 480 } 481 482 receive_SSH_SMSG_PUBLIC_KEY(packet); 483 } finally { 484 if (packet != null) { 485 packet.close(true); 486 } 487 } 488 489 try { 490 packet = skip_SSH_MSG_DEBUG(); 491 packetType = packet.getType(); 492 493 if (packetType != SSH_SMSG_SUCCESS) { 494 throw new IOException (NLS.bind(CVSSSHMessages.Client_packetType, (new Object [] {new Integer (packetType)}))); 495 } 496 } finally { 497 if (packet != null) { 498 packet.close(true); 499 } 500 } 501 502 send(SSH_CMSG_USER, username); 503 504 try { 505 packet = skip_SSH_MSG_DEBUG(); 506 packetType = packet.getType(); 507 508 if (packetType != SSH_SMSG_FAILURE) { 509 throw new IOException (NLS.bind(CVSSSHMessages.Client_packetType, (new Object [] {new Integer (packetType)}))); 510 } 511 } finally { 512 if (packet != null) { 513 packet.close(true); 514 } 515 } 516 517 send(SSH_CMSG_AUTH_PASSWORD, password); 518 519 try { 520 packet = skip_SSH_MSG_DEBUG(); 521 packetType = packet.getType(); 522 523 if (packetType == SSH_SMSG_FAILURE) { 524 throw new CVSAuthenticationException(CVSSSHMessages.Client_authenticationFailed, CVSAuthenticationException.RETRY); 525 } 526 527 if (packetType != SSH_SMSG_SUCCESS) { 528 throw new IOException (NLS.bind(CVSSSHMessages.Client_packetType, (new Object [] {new Integer (packetType)}))); 529 } 530 } finally { 531 if (packet != null) { 532 packet.close(true); 533 } 534 } 535 } 536 private void receive_SSH_SMSG_PUBLIC_KEY(ServerPacket packet) throws IOException , CVSAuthenticationException { 537 InputStream pis = packet.getInputStream(); 538 539 byte[] anti_spoofing_cookie = new byte[8]; 540 Misc.readFully(pis, anti_spoofing_cookie); 541 542 byte[] server_key_bits = new byte[4]; 543 Misc.readFully(pis, server_key_bits); 544 545 byte[] server_key_public_exponent = Misc.readMpInt(pis); 546 byte[] server_key_public_modulus = Misc.readMpInt(pis); 547 548 byte[] host_key_bits = new byte[4]; 549 Misc.readFully(pis, host_key_bits); 550 551 byte[] host_key_public_exponent = Misc.readMpInt(pis); 552 byte[] host_key_public_modulus = Misc.readMpInt(pis); 553 554 byte[] protocol_flags = new byte[4]; 555 Misc.readFully(pis, protocol_flags); 556 557 byte[] supported_ciphers_mask = new byte[4]; 558 Misc.readFully(pis, supported_ciphers_mask); 559 560 byte[] supported_authentications_mask = new byte[4]; 561 Misc.readFully(pis, supported_authentications_mask); 562 563 pis.close(); 564 565 send_SSH_CMSG_SESSION_KEY(anti_spoofing_cookie, host_key_bits, server_key_public_modulus, host_key_public_modulus, supported_ciphers_mask, server_key_public_exponent, host_key_public_exponent); 566 } 567 private void send(int packetType, String s) throws IOException { 568 byte[] data = s == null ? new byte[0] : s.getBytes("UTF-8"); send(packetType, data, 0, data.length); 570 } 571 private void send(int packetType, byte[] data, int off, int len) throws IOException { 572 data = data == null ? null : Misc.lengthEncode(data, off, len); 573 ClientPacket packet = new ClientPacket(packetType, data, cipher); 574 socketOut.write(packet.getBytes()); 575 socketOut.flush(); 576 } 577 private void send_SSH_CMSG_REQUEST_PTY() throws IOException { 578 byte packet_type = SSH_CMSG_REQUEST_PTY; 579 580 byte[] termType = Misc.lengthEncode("dumb".getBytes(), 0, 4); byte[] row = {0, 0, 0, 0}; 582 byte[] col = {0, 0, 0, 0}; 583 byte[] XPixels = {0, 0, 0, 0}; 584 byte[] YPixels = {0, 0, 0, 0}; 585 byte[] terminalModes = {0}; 586 587 byte[] data = new byte[termType.length + row.length + col.length + XPixels.length + YPixels.length + terminalModes.length]; 588 589 int offset = 0; 590 System.arraycopy(termType, 0, data, offset, termType.length); 591 592 offset += termType.length; 593 System.arraycopy(row, 0, data, offset, row.length); 594 595 offset += row.length; 596 System.arraycopy(col, 0, data, offset, col.length); 597 598 offset += col.length; 599 System.arraycopy(XPixels, 0, data, offset, XPixels.length); 600 601 offset += XPixels.length; 602 System.arraycopy(YPixels, 0, data, offset, YPixels.length); 603 604 offset += YPixels.length; 605 System.arraycopy(terminalModes, 0, data, offset, terminalModes.length); 606 607 ClientPacket packet = new ClientPacket(packet_type, data, cipher); 608 socketOut.write(packet.getBytes()); 609 socketOut.flush(); 610 } 611 private void send_SSH_CMSG_SESSION_KEY(byte[] anti_spoofing_cookie, byte[] host_key_bits, byte[] server_key_public_modulus, byte[] host_key_public_modulus, byte[] supported_ciphers_mask, byte[] server_key_public_exponent, byte[] host_key_public_exponent) throws IOException , CVSAuthenticationException { 612 byte packet_type = SSH_CMSG_SESSION_KEY; 613 614 byte[] session_id = new byte[host_key_public_modulus.length + server_key_public_modulus.length + anti_spoofing_cookie.length]; 616 617 int offset = 0; 618 System.arraycopy(host_key_public_modulus, 0, session_id, offset, host_key_public_modulus.length); 619 620 offset += host_key_public_modulus.length; 621 System.arraycopy(server_key_public_modulus, 0, session_id, offset, server_key_public_modulus.length); 622 623 offset += server_key_public_modulus.length; 624 System.arraycopy(anti_spoofing_cookie, 0, session_id, offset, anti_spoofing_cookie.length); 625 626 session_id = Misc.md5(session_id); 627 628 byte cipher_type = 0; 630 boolean foundSupportedCipher = false; 631 632 for (int i = 0; i < preferredCipherTypes.length && !foundSupportedCipher; ++i) { 633 cipher_type = (byte) preferredCipherTypes[i]; 634 foundSupportedCipher = (supported_ciphers_mask[3] & (byte) (1 << cipher_type)) != 0; 635 } 636 637 if (!foundSupportedCipher) { 638 throw new IOException (CVSSSHMessages.Client_cipher); 639 } 640 641 byte[] session_key = new byte[32]; 643 byte[] session_key_xored = new byte[32]; 644 byte[] session_key_encrypted = null; 645 646 Misc.random(session_key, 0, session_key.length, true); 647 System.arraycopy(session_key, 0, session_key_xored, 0, session_key.length); 648 Misc.xor(session_key_xored, 0, session_id, 0, session_key_xored, 0, session_id.length); 649 650 BigInteger host_e = new BigInteger (1, host_key_public_exponent); 651 BigInteger host_n = new BigInteger (1, host_key_public_modulus); 652 if (!new KnownHosts().verifyKey(host, host_key_bits, host_e, host_n)) { 653 throw new CVSAuthenticationException(CVSSSHMessages.Client_hostIdChanged, CVSAuthenticationException.NO_RETRY); 654 }; 655 byte[] result; 656 if (new BigInteger (1,server_key_public_modulus).compareTo(host_n) == -1) { 657 result = Misc.encryptRSAPkcs1(session_key_xored, server_key_public_exponent, server_key_public_modulus); 658 result = Misc.encryptRSAPkcs1(result, host_key_public_exponent, host_key_public_modulus); 659 } else { 660 result = Misc.encryptRSAPkcs1(session_key_xored, host_key_public_exponent, host_key_public_modulus); 661 result = Misc.encryptRSAPkcs1(result, server_key_public_exponent, server_key_public_modulus); 662 } 663 664 session_key_encrypted = new byte[result.length + 2]; 665 session_key_encrypted[1] = (byte) ((8 * result.length) & 0xff); 666 session_key_encrypted[0] = (byte) (((8 * result.length) >> 8) & 0xff); 667 668 for (int i = 0; i < result.length; i++) { 669 session_key_encrypted[i + 2] = result[i]; 670 } 671 672 byte[] protocol_flags = {0, 0, 0, 0}; 674 675 byte[] data = new byte[1 + anti_spoofing_cookie.length + session_key_encrypted.length + protocol_flags.length]; 677 678 offset = 0; 679 data[offset++] = cipher_type; 680 681 System.arraycopy(anti_spoofing_cookie, 0, data, offset, anti_spoofing_cookie.length); 682 683 offset += anti_spoofing_cookie.length; 684 System.arraycopy(session_key_encrypted, 0, data, offset, session_key_encrypted.length); 685 686 offset += session_key_encrypted.length; 687 System.arraycopy(protocol_flags, 0, data, offset, protocol_flags.length); 688 689 cipher = Cipher.getInstance(cipherNames[cipher_type]); 691 cipher.setKey(session_key); 692 693 ClientPacket packet = new ClientPacket(packet_type, data, null); 695 socketOut.write(packet.getBytes()); 696 socketOut.flush(); 697 } 698 699 private ServerPacket skip_SSH_MSG_DEBUG() throws IOException { 700 ServerPacket packet = new ServerPacket(socketIn, cipher); 701 while (packet.getType() == SSH_MSG_DEBUG) { 702 packet.close(true); 703 packet = new ServerPacket(socketIn, cipher); 704 } 705 706 return packet; 707 } 708 } 709 | Popular Tags |