├── lib ├── ssh1.js ├── sftp.js ├── util.js └── ssh2.js ├── README.md ├── .gitmodules └── test └── connect_ssh2.js /lib/ssh1.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/sftp.js: -------------------------------------------------------------------------------- 1 | empty file. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | === node-ssh === 2 | 3 | More words, coming soon. 4 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "support/node-strtok"] 2 | path = support/node-strtok 3 | url = git://github.com/pgriess/node-strtok.git 4 | -------------------------------------------------------------------------------- /test/connect_ssh2.js: -------------------------------------------------------------------------------- 1 | var SSH2 = require("./../lib/ssh2"); 2 | // turn ON logging: 3 | SSH2.NET_SSH2_LOGGING = SSH2.NET_SSH2_LOG_COMPLEX; 4 | 5 | SSH2.createConnection("localhost", 22, function(err, conn) { 6 | console.log("DONE!", err); 7 | }); -------------------------------------------------------------------------------- /lib/util.js: -------------------------------------------------------------------------------- 1 | //https://github.com/sjwalter/Node-Packer.git 2 | function pack(format) { 3 | var argIndex = 1; 4 | var args = arguments; 5 | 6 | if(args[1] instanceof Array) { 7 | // We were passed an array of data to pack 8 | args = arguments[1]; 9 | argIndex = 0; 10 | } 11 | 12 | // Because node Buffers can't be resized, we store the result 13 | // as an array of Buffers and keep track of the total length, 14 | // then return a new buffer from this array at the end. 15 | var result = []; 16 | var totalLength = 0; 17 | 18 | // TODO: Byte order first-char 19 | // For now, we're assuming '!' (Network--big-endian). This is 20 | // different from python's struct.pack assumption that the 21 | // default is @ (native size and byte order). 22 | var formatIndex = 0; 23 | 24 | switch(format[0]) { 25 | case "@": 26 | case "=": 27 | case "<": 28 | case ">": 29 | case "!": 30 | // Don't do anything with these right now, and maybe 31 | // I can't (can I manipulate the byte-orderings?) 32 | // Just keeping this here to ensure compatibility 33 | // with python's struct.pack, which I'm only doing 34 | // so that I have to write less documentation. 35 | formatIndex = 1; 36 | break; 37 | } 38 | 39 | for(; formatIndex < format.length; formatIndex++) { 40 | var curType = format[formatIndex]; 41 | var curAddition; 42 | switch(curType) { 43 | case "x": 44 | // Pad byte, just one empty byte. 45 | var tmp = new Buffer(1); 46 | tmp[0] = 0; 47 | result.push(tmp); 48 | totalLength += 1; 49 | break; 50 | case "c": 51 | case "b": 52 | case "B": 53 | // Character, one byte. 54 | var tmp = new Buffer(1); 55 | var character = args[argIndex]; 56 | if(typeof character != "number") { 57 | character = parseInt(character); 58 | } 59 | if(character > 255) { 60 | throw new Error("Can not pack a B-type from an int > 255"); 61 | } 62 | tmp[0] = character; 63 | result.push(tmp); 64 | totalLength += 1; 65 | break; 66 | case "I": 67 | case "L": 68 | // unsigned integer/long, 4 bytes 69 | var tmp = new Buffer(4); 70 | var num = args[argIndex]; 71 | if(typeof num != "number") { 72 | num = parseInt(num); 73 | } 74 | tmp[0] = num & 255; 75 | tmp[1] = num >> 8 & 255; 76 | tmp[2] = num >> 16 & 255; 77 | tmp[3] = num >> 24 & 255; 78 | result.push(tmp); 79 | totalLength += 4; 80 | break; 81 | default: 82 | throw new Error("Unsupported packing type: " + curType); 83 | break; 84 | } 85 | argIndex += 1; 86 | } 87 | 88 | // Prepare the actual return buffer 89 | var ret = new Buffer(totalLength); 90 | var retIndex = 0; 91 | for(var i = 0; i < result.length; i++) { 92 | var curResult = result[i]; 93 | for(var j = 0; j < curResult.length; j++) { 94 | ret[retIndex] = curResult[j]; 95 | retIndex += 1; 96 | } 97 | } 98 | return ret; 99 | } 100 | 101 | function unpack(format, buffer) { 102 | if(!(buffer instanceof Buffer)) { 103 | throw new Error("unpack expects a second argument of type Buffer"); 104 | } 105 | 106 | var formatIndex = 0; 107 | var bufferIndex = 0; 108 | var result = new Array(); 109 | 110 | switch(format[0]) { 111 | case "@": 112 | case "=": 113 | case ">": 114 | case "<": 115 | case "!": 116 | formatIndex = 1; 117 | break; 118 | } 119 | 120 | for(; formatIndex < format.length; formatIndex++) { 121 | var curType = format[formatIndex]; 122 | 123 | switch(curType) { 124 | case "x": 125 | // Just a pad byte. Next! 126 | bufferIndex += 1; 127 | break; 128 | case "c": 129 | case "b": 130 | case "B": 131 | result.push(parseInt(buffer[bufferIndex])); 132 | bufferIndex += 1; 133 | break; 134 | case "I": 135 | case "L": 136 | var num = 137 | parseInt((buffer[bufferIndex + 3] & 255) << 24) + 138 | parseInt((buffer[bufferIndex + 2] & 255) << 16) + 139 | parseInt((buffer[bufferIndex + 1] & 255) << 8) + 140 | parseInt(buffer[bufferIndex] & 255); 141 | result.push(num); 142 | bufferIndex += 4; 143 | break; 144 | default: 145 | throw new Error("Unsupported format type: " + curType); 146 | break; 147 | } 148 | } 149 | return result; 150 | } 151 | 152 | module.exports.pack = pack; 153 | module.exports.unpack = unpack; 154 | 155 | var Buffy = function() { 156 | this._store = new Array(); 157 | this._length = 0; 158 | }; 159 | Buffy.prototype.append = function(buffer) { 160 | this._length += buffer.length; 161 | this._store.push(buffer); 162 | }; 163 | Buffy.prototype.indexOf = function(bytes, start) { 164 | if (start && (start < 0 || start >= this._length)) 165 | return -1;//throw new Error('OOB'); 166 | if (typeof bytes === 'number') 167 | bytes = [bytes]; 168 | start = start || 0; 169 | var ret = -1, matching = false, foundStart = false, bn = 0, bc = 0, matchedAt, 170 | numbufs = this._store.length, buflen, bytesPos = 0, 171 | lastBytesPos = bytes.length-1, i; 172 | while (bn < numbufs) { 173 | i = 0; 174 | buflen = this._store[bn].length; 175 | if (!foundStart) { 176 | if (start >= buflen) 177 | start -= buflen; 178 | else { 179 | i = start; 180 | foundStart = true; 181 | } 182 | } 183 | if (foundStart) { 184 | for (; i -1) 202 | break; 203 | } 204 | bc += buflen; 205 | ++bn; 206 | } 207 | return ret; 208 | }; 209 | Buffy.prototype.GCBefore = function(index) { 210 | if (index < 0 || index > this._length) 211 | throw new Error('OOB'); 212 | var toRemove = 0, amount = 0; 213 | for (var bn=0,i=0,len=this._store.length; bn 0) { 215 | amount += this._store[bn-1].length; 216 | this._length -= this._store[bn-1].length; 217 | ++toRemove; 218 | } 219 | i += this._store[bn].length; 220 | if (index < i) 221 | break; 222 | } 223 | if (toRemove > 0) 224 | this._store.splice(0, toRemove); 225 | return amount; 226 | }; 227 | Buffy.prototype.copy = function(destBuffer, destStart, srcStart, srcEnd) { 228 | if (typeof srcEnd === 'undefined') 229 | srcEnd = this._length; 230 | destStart = destStart || 0; 231 | srcStart = srcStart || 0; 232 | if (srcStart < 0 || srcStart > this._length || srcEnd > this._length 233 | || srcStart > srcEnd || destStart + (srcEnd-srcStart) > destBuffer.length) 234 | throw new Error('OOB'); 235 | if (srcStart !== srcEnd) { 236 | var foundStart = false, totalBytes = (srcEnd-srcStart), 237 | buflen, destPos = destStart; 238 | for (var bn=0,len=this._store.length; bn= buflen) 242 | srcStart -= buflen; 243 | else 244 | foundStart = true; 245 | } 246 | if (foundStart) { 247 | if ((totalBytes - destPos) <= (buflen - srcStart)) { 248 | this._store[bn].copy(destBuffer, destPos, srcStart, srcStart + (totalBytes - destPos)); 249 | break; 250 | } else { 251 | this._store[bn].copy(destBuffer, destPos, srcStart, buflen); 252 | destPos += (buflen - srcStart); 253 | srcStart = 0; 254 | } 255 | } 256 | } 257 | } 258 | }; 259 | Buffy.prototype.splice = function(index, howmany, el) { 260 | var idxLastDel = index + howmany, idxLastAdd = index, 261 | numNew = 0, newEls, idxRet = 0; 262 | if (index < 0 || index >= this._length || howmany < 0 || idxLastDel >= this._length) 263 | throw new Error('OOB'); 264 | if (el) { 265 | newEls = Array.prototype.slice.call(arguments).slice(2); 266 | numNew = newEls.length; 267 | idxLastAdd = index + numNew; 268 | } 269 | var idxLastMin = Math.min(idxLastAdd, idxLastDel), 270 | idxLastMax = Math.max(idxLastAdd, idxLastDel); 271 | var ret = new Array(howmany); 272 | if (numNew === howmany) { 273 | for (var bn=0,i=0,blen,start=-1,len=this._store.length; bn= 0 && index < this._length) { 300 | for (var bn=0,i=0,blen,len=this._store.length; bn= 0 && index < this._length && typeof value === 'number' 314 | && value >= 0 && value <= 255) { 315 | for (var bn=0,i=0,blen,len=this._store.length; bn this._length || end > this._length || start > end) 333 | throw new Error('OOB'); 334 | if (start !== end) { 335 | if (start === 0 && end === this._length) { 336 | // simple case 337 | for (var i=0,len=this._store.length; i= buflen) 346 | start -= buflen; 347 | else 348 | foundStart = true; 349 | } 350 | if (foundStart) { 351 | if ((totalBytes - destPos) <= (buflen - start)) { 352 | ret.push(this._store[bn].toString(encoding, start, start + (totalBytes - destPos))); 353 | break; 354 | } else { 355 | ret.push(this._store[bn].toString(encoding, start, buflen)); 356 | destPos += (buflen - start); 357 | start = 0; 358 | } 359 | } 360 | } 361 | } 362 | } 363 | return ret.join(''); 364 | }; 365 | Buffy.prototype.inspect = function() { 366 | var len = this._store.length, ret = '"); 519 | if (exports.NET_SSH2_LOGGING == exports.NET_SSH2_LOG_COMPLEX) 520 | _self.message_log.push(temp, _self.identifier + "\r\n"); 521 | } 522 | 523 | _self.server_identifier = temp.replace(/[\r\n]+/, ""); 524 | if (extra) 525 | _self.errors.push(extra); 526 | 527 | if (matches[1] != "1.99" && matches[1] != "2.0") 528 | return cbconnect("Cannot connect to SSH " + $matches[1] +" servers"); 529 | 530 | _self.fsock.write(_self.identifier + "\r\n"); 531 | connected = true; 532 | _self.fsock.removeListener("data", listener); 533 | 534 | _self._get_binary_packet(function(response) { 535 | 536 | }); 537 | _self._listen(); 538 | } 539 | else { 540 | 541 | if (false){ 542 | if (!_self.buffer) 543 | _self.buffer = data; 544 | else 545 | _self.buffer.addChunk(data); 546 | 547 | console.log("buffer expanded", _self.buffer.length); 548 | if (!authenticated) { 549 | var response = _self._get_binary_packet(); 550 | if (response === false) 551 | return cbconnect("Connection closed by server"); 552 | 553 | console.log("RES", typeof response, response); 554 | /*return; 555 | if (response[0].charCodeAt(0) != exports.NET_SSH2_MSG_KEXINIT) 556 | return cbconnect("Expected SSH_MSG_KEXINIT"); 557 | 558 | if (!_self._key_exchange(response)) 559 | return cbconnect("No supported authentication method found");*/ 560 | 561 | _self.bitmap = exports.NET_SSH2_MASK_CONSTRUCTOR; 562 | authenticated = true; 563 | cbconnect(null, _self); 564 | } 565 | } 566 | } 567 | console.log("data: ", data + "--END--"); 568 | }); 569 | }); 570 | this.fsock.addListener("error", function(err) { 571 | cbconnect("Cannot connect to host: " + err); 572 | _self.fsock.destroy(); 573 | }); 574 | this.fsock.addListener("timeout", function(err) { 575 | _self.fsock.end(); 576 | }); 577 | }; 578 | 579 | /** 580 | * Disconnect 581 | */ 582 | this.disconnect = function() { 583 | this._disconnect(exports.NET_SSH2_DISCONNECT_BY_APPLICATION); 584 | }; 585 | 586 | /** 587 | * Disconnect 588 | * 589 | * @param Integer reason 590 | * @return Boolean 591 | */ 592 | this._disconnect = function(reason) { 593 | if (this.bitmap) { 594 | data = pack("CNNa*Na*", exports.NET_SSH2_MSG_DISCONNECT, reason, 0, "", 0, ""); 595 | this._send_binary_packet(data); 596 | this.bitmap = 0; 597 | fclose(this.fsock); 598 | return false; 599 | } 600 | }; 601 | 602 | /** 603 | * Key Exchange 604 | * 605 | * @param String $kexinit_payload_server 606 | * @access private 607 | */ 608 | function _key_exchange(kexinit_payload_server) 609 | { 610 | var kex_algorithms = [ 611 | "diffie-hellman-group1-sha1", // REQUIRED 612 | "diffie-hellman-group14-sha1" // REQUIRED 613 | ]; 614 | 615 | var server_host_key_algorithms = [ 616 | "ssh-rsa", // RECOMMENDED sign Raw RSA Key 617 | "ssh-dss" // REQUIRED sign Raw DSS Key 618 | ]; 619 | 620 | var encryption_algorithms = [ 621 | // from : 622 | "arcfour256", 623 | "arcfour128", 624 | 625 | "arcfour", // OPTIONAL the ARCFOUR stream cipher with a 128-bit key 626 | 627 | "aes128-cbc", // RECOMMENDED AES with a 128-bit key 628 | "aes192-cbc", // OPTIONAL AES with a 192-bit key 629 | "aes256-cbc", // OPTIONAL AES in CBC mode, with a 256-bit key 630 | 631 | // from : 632 | "aes128-ctr", // RECOMMENDED AES (Rijndael) in SDCTR mode, with 128-bit key 633 | "aes192-ctr", // RECOMMENDED AES with 192-bit key 634 | "aes256-ctr", // RECOMMENDED AES with 256-bit key 635 | "3des-ctr", // RECOMMENDED Three-key 3DES in SDCTR mode 636 | 637 | "3des-cbc", // REQUIRED three-key 3DES in CBC mode 638 | "none" // OPTIONAL no encryption; NOT RECOMMENDED 639 | ]; 640 | 641 | var mac_algorithms = [ 642 | "hmac-sha1-96", // RECOMMENDED first 96 bits of HMAC-SHA1 (digest length = 12, key length = 20) 643 | "hmac-sha1", // REQUIRED HMAC-SHA1 (digest length = key length = 20) 644 | "hmac-md5-96", // OPTIONAL first 96 bits of HMAC-MD5 (digest length = 12, key length = 16) 645 | "hmac-md5", // OPTIONAL HMAC-MD5 (digest length = key length = 16) 646 | "none" // OPTIONAL no MAC; NOT RECOMMENDED 647 | ]; 648 | 649 | var compression_algorithms = [ 650 | "none" // REQUIRED no compression 651 | //"zlib" // OPTIONAL ZLIB (LZ77) compression 652 | ]; 653 | 654 | var str_kex_algorithms, str_server_host_key_algorithms, 655 | encryption_algorithms_server_to_client, mac_algorithms_server_to_client, compression_algorithms_server_to_client, 656 | encryption_algorithms_client_to_server, mac_algorithms_client_to_server, compression_algorithms_client_to_server; 657 | 658 | if (empty(str_kex_algorithms)) { 659 | str_kex_algorithms = implode(",", kex_algorithms); 660 | str_server_host_key_algorithms = implode(",", server_host_key_algorithms); 661 | encryption_algorithms_server_to_client = encryption_algorithms_client_to_server = implode(",", encryption_algorithms); 662 | mac_algorithms_server_to_client = mac_algorithms_client_to_server = implode(",", mac_algorithms); 663 | compression_algorithms_server_to_client = compression_algorithms_client_to_server = implode(",", compression_algorithms); 664 | } 665 | 666 | client_cookie = ""; 667 | for (i = 0; i < 16; i++) { 668 | client_cookie += chr(crypt_random(0, 255)); 669 | } 670 | 671 | response = kexinit_payload_server; 672 | this._string_shift(response, 1); // skip past the message number (it should be SSH_MSG_KEXINIT) 673 | server_cookie = this._string_shift(response, 16); 674 | 675 | temp = unpack("Nlength", this._string_shift(response, 4)); 676 | this.kex_algorithms = explode(",", this._string_shift(response, temp["length"])); 677 | 678 | temp = unpack("Nlength", this._string_shift(response, 4)); 679 | this.server_host_key_algorithms = explode(",", this._string_shift(response, temp["length"])); 680 | 681 | temp = unpack("Nlength", this._string_shift(response, 4)); 682 | this.encryption_algorithms_client_to_server = explode(",", this._string_shift(response, temp["length"])); 683 | 684 | temp = unpack("Nlength", this._string_shift(response, 4)); 685 | this.encryption_algorithms_server_to_client = explode(",", this._string_shift(response, temp["length"])); 686 | 687 | temp = unpack("Nlength", this._string_shift(response, 4)); 688 | this.mac_algorithms_client_to_server = explode(",", this._string_shift(response, temp["length"])); 689 | 690 | temp = unpack("Nlength", this._string_shift(response, 4)); 691 | this.mac_algorithms_server_to_client = explode(",", this._string_shift(response, temp["length"])); 692 | 693 | temp = unpack("Nlength", this._string_shift(response, 4)); 694 | this.compression_algorithms_client_to_server = explode(",", this._string_shift(response, temp["length"])); 695 | 696 | temp = unpack("Nlength", this._string_shift(response, 4)); 697 | this.compression_algorithms_server_to_client = explode(",", this._string_shift(response, temp["length"])); 698 | 699 | temp = unpack("Nlength", this._string_shift(response, 4)); 700 | this.languages_client_to_server = explode(",", this._string_shift(response, temp["length"])); 701 | 702 | temp = unpack("Nlength", this._string_shift(response, 4)); 703 | this.languages_server_to_client = explode(",", this._string_shift(response, temp["length"])); 704 | 705 | extract(unpack("Cfirst_kex_packet_follows", this._string_shift(response, 1))); 706 | first_kex_packet_follows = first_kex_packet_follows != 0; 707 | 708 | // the sending of SSH2_MSG_KEXINIT could go in one of two places. this is the second place. 709 | kexinit_payload_client = pack("Ca*Na*Na*Na*Na*Na*Na*Na*Na*Na*Na*CN", 710 | NET_SSH2_MSG_KEXINIT, client_cookie, strlen(str_kex_algorithms), str_kex_algorithms, 711 | strlen(str_server_host_key_algorithms), str_server_host_key_algorithms, strlen(encryption_algorithms_client_to_server), 712 | encryption_algorithms_client_to_server, strlen(encryption_algorithms_server_to_client), encryption_algorithms_server_to_client, 713 | strlen(mac_algorithms_client_to_server), mac_algorithms_client_to_server, strlen(mac_algorithms_server_to_client), 714 | mac_algorithms_server_to_client, strlen(compression_algorithms_client_to_server), compression_algorithms_client_to_server, 715 | strlen(compression_algorithms_server_to_client), compression_algorithms_server_to_client, 0, "", 0, "", 716 | 0, 0 717 | ); 718 | 719 | if (!this._send_binary_packet(kexinit_payload_client)) { 720 | return false; 721 | } 722 | // here ends the second place. 723 | 724 | // we need to decide upon the symmetric encryption algorithms before we do the diffie-hellman key exchange 725 | for (i = 0; i < count(encryption_algorithms) && !in_array(encryption_algorithms[i], this.encryption_algorithms_server_to_client); i++); 726 | if (i == count(encryption_algorithms)) { 727 | user_error("No compatible server to client encryption algorithms found", E_USER_NOTICE); 728 | return this._disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); 729 | } 730 | 731 | // we don"t initialize any crypto-objects, yet - we do that, later. for now, we need the lengths to make the 732 | // diffie-hellman key exchange as fast as possible 733 | decrypt = encryption_algorithms[i]; 734 | switch (decrypt) { 735 | case "3des-cbc": 736 | case "3des-ctr": 737 | decryptKeyLength = 24; // eg. 192 / 8 738 | break; 739 | case "aes256-cbc": 740 | case "aes256-ctr": 741 | decryptKeyLength = 32; // eg. 256 / 8 742 | break; 743 | case "aes192-cbc": 744 | case "aes192-ctr": 745 | decryptKeyLength = 24; // eg. 192 / 8 746 | break; 747 | case "aes128-cbc": 748 | case "aes128-ctr": 749 | decryptKeyLength = 16; // eg. 128 / 8 750 | break; 751 | case "arcfour": 752 | case "arcfour128": 753 | decryptKeyLength = 16; // eg. 128 / 8 754 | break; 755 | case "arcfour256": 756 | decryptKeyLength = 32; // eg. 128 / 8 757 | break; 758 | case "none": 759 | decryptKeyLength = 0; 760 | break; 761 | } 762 | 763 | for (i = 0; i < count(encryption_algorithms) && !in_array(encryption_algorithms[i], this.encryption_algorithms_client_to_server); i++); 764 | if (i == count(encryption_algorithms)) { 765 | user_error("No compatible client to server encryption algorithms found", E_USER_NOTICE); 766 | return this._disconnect(exports.NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); 767 | } 768 | 769 | encrypt = encryption_algorithms[i]; 770 | switch (encrypt) { 771 | case "3des-cbc": 772 | case "3des-ctr": 773 | encryptKeyLength = 24; 774 | break; 775 | case "aes256-cbc": 776 | case "aes256-ctr": 777 | encryptKeyLength = 32; 778 | break; 779 | case "aes192-cbc": 780 | case "aes192-ctr": 781 | encryptKeyLength = 24; 782 | break; 783 | case "aes128-cbc": 784 | case "aes128-ctr": 785 | encryptKeyLength = 16; 786 | break; 787 | case "arcfour": 788 | case "arcfour128": 789 | encryptKeyLength = 16; 790 | break; 791 | case "arcfour256": 792 | encryptKeyLength = 32; 793 | break; 794 | case "none": 795 | encryptKeyLength = 0; 796 | break; 797 | } 798 | 799 | keyLength = decryptKeyLength > encryptKeyLength ? decryptKeyLength : encryptKeyLength; 800 | 801 | // through diffie-hellman key exchange a symmetric key is obtained 802 | for (i = 0; i < count(kex_algorithms) && !in_array(kex_algorithms[i], this.kex_algorithms); i++); 803 | if (i == count(kex_algorithms)) { 804 | user_error("No compatible key exchange algorithms found", E_USER_NOTICE); 805 | return this._disconnect(exports.NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); 806 | } 807 | 808 | switch (kex_algorithms[i]) { 809 | // see http://tools.ietf.org/html/rfc2409#section-6.2 and 810 | // http://tools.ietf.org/html/rfc2412, appendex E 811 | case "diffie-hellman-group1-sha1": 812 | p = pack("H256", "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74" + 813 | "020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F1437" + 814 | "4FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" + 815 | "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381FFFFFFFFFFFFFFFF"); 816 | keyLength = keyLength < 160 ? keyLength : 160; 817 | hash = "sha1"; 818 | break; 819 | // see http://tools.ietf.org/html/rfc3526#section-3 820 | case "diffie-hellman-group14-sha1": 821 | p = pack("H512", "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74" + 822 | "020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F1437" + 823 | "4FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" + 824 | "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF05" + 825 | "98DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB" + 826 | "9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B" + 827 | "E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718" + 828 | "3995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF"); 829 | keyLength = keyLength < 160 ? keyLength : 160; 830 | hash = "sha1"; 831 | } 832 | 833 | p = new Math_BigInteger(p, 256); 834 | //q = p.bitwise_rightShift(1); 835 | 836 | /* To increase the speed of the key exchange, both client and server may 837 | reduce the size of their private exponents. It should be at least 838 | twice as long as the key material that is generated from the shared 839 | secret. For more details, see the paper by van Oorschot and Wiener 840 | [VAN-OORSCHOT]. 841 | 842 | -- http://tools.ietf.org/html/rfc4419#section-6.2 */ 843 | q = new Math_BigInteger(1); 844 | q = q.bitwise_leftShift(2 * keyLength); 845 | q = q.subtract(new Math_BigInteger(1)); 846 | 847 | g = new Math_BigInteger(2); 848 | x = new Math_BigInteger(); 849 | x.setRandomGenerator("crypt_random"); 850 | x = x.random(new Math_BigInteger(1), q); 851 | e = g.modPow(x, p); 852 | 853 | eBytes = e.toBytes(true); 854 | data = pack("CNa*", exports.NET_SSH2_MSG_KEXDH_INIT, strlen(eBytes), eBytes); 855 | 856 | if (!this._send_binary_packet(data)) { 857 | user_error("Connection closed by server", E_USER_NOTICE); 858 | return false; 859 | } 860 | 861 | response = this._get_binary_packet(); 862 | if (response === false) { 863 | user_error("Connection closed by server", E_USER_NOTICE); 864 | return false; 865 | } 866 | extract(unpack("Ctype", this._string_shift(response, 1))); 867 | 868 | if (type != NET_SSH2_MSG_KEXDH_REPLY) { 869 | user_error("Expected SSH_MSG_KEXDH_REPLY", E_USER_NOTICE); 870 | return false; 871 | } 872 | 873 | temp = unpack("Nlength", this._string_shift(response, 4)); 874 | this.server_public_host_key = server_public_host_key = this._string_shift(response, temp["length"]); 875 | 876 | temp = unpack("Nlength", this._string_shift(server_public_host_key, 4)); 877 | public_key_format = this._string_shift(server_public_host_key, temp["length"]); 878 | 879 | temp = unpack("Nlength", this._string_shift(response, 4)); 880 | fBytes = this._string_shift(response, temp["length"]); 881 | f = new Math_BigInteger(fBytes, -256); 882 | 883 | temp = unpack("Nlength", this._string_shift(response, 4)); 884 | this.signature = this._string_shift(response, temp["length"]); 885 | 886 | temp = unpack("Nlength", this._string_shift(this.signature, 4)); 887 | this.signature_format = this._string_shift(this.signature, temp["length"]); 888 | 889 | key = f.modPow(x, p); 890 | keyBytes = key.toBytes(true); 891 | 892 | if (this.session_id === false) { 893 | source = pack("Na*Na*Na*Na*Na*Na*Na*Na*", 894 | strlen(this.identifier), this.identifier, strlen(this.server_identifier), this.server_identifier, 895 | strlen(kexinit_payload_client), kexinit_payload_client, strlen(kexinit_payload_server), 896 | kexinit_payload_server, strlen(this.server_public_host_key), this.server_public_host_key, strlen(eBytes), 897 | eBytes, strlen(fBytes), fBytes, strlen(keyBytes), keyBytes 898 | ); 899 | 900 | source = pack("H*", hash(source)); 901 | 902 | this.session_id = source; 903 | } 904 | 905 | for (i = 0; i < count(server_host_key_algorithms) && !in_array(server_host_key_algorithms[i], this.server_host_key_algorithms); i++); 906 | if (i == count(server_host_key_algorithms)) { 907 | user_error("No compatible server host key algorithms found", E_USER_NOTICE); 908 | return this._disconnect(exports.NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); 909 | } 910 | 911 | if (public_key_format != server_host_key_algorithms[i] || this.signature_format != server_host_key_algorithms[i]) { 912 | user_error("Sever Host Key Algorithm Mismatch", E_USER_NOTICE); 913 | return this._disconnect(exports.NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); 914 | } 915 | 916 | packet = pack("C", 917 | exports.NET_SSH2_MSG_NEWKEYS 918 | ); 919 | 920 | if (!this._send_binary_packet(packet)) { 921 | return false; 922 | } 923 | 924 | response = this._get_binary_packet(); 925 | 926 | if (response === false) { 927 | user_error("Connection closed by server", E_USER_NOTICE); 928 | return false; 929 | } 930 | 931 | extract(unpack("Ctype", this._string_shift(response, 1))); 932 | 933 | if (type != exports.NET_SSH2_MSG_NEWKEYS) { 934 | user_error("Expected SSH_MSG_NEWKEYS", E_USER_NOTICE); 935 | return false; 936 | } 937 | 938 | switch (encrypt) { 939 | case "3des-cbc": 940 | this.encrypt = new Crypt_TripleDES(); 941 | // this.encrypt_block_size = 64 / 8 == the default 942 | break; 943 | case "3des-ctr": 944 | this.encrypt = new Crypt_TripleDES(CRYPT_DES_MODE_CTR); 945 | // this.encrypt_block_size = 64 / 8 == the default 946 | break; 947 | case "aes256-cbc": 948 | case "aes192-cbc": 949 | case "aes128-cbc": 950 | this.encrypt = new Crypt_AES(); 951 | this.encrypt_block_size = 16; // eg. 128 / 8 952 | break; 953 | case "aes256-ctr": 954 | case "aes192-ctr": 955 | case "aes128-ctr": 956 | this.encrypt = new Crypt_AES(CRYPT_AES_MODE_CTR); 957 | this.encrypt_block_size = 16; // eg. 128 / 8 958 | break; 959 | case "arcfour": 960 | case "arcfour128": 961 | case "arcfour256": 962 | this.encrypt = new Crypt_RC4(); 963 | break; 964 | case "none": 965 | //this.encrypt = new Crypt_Null(); 966 | break; 967 | } 968 | 969 | switch (decrypt) { 970 | case "3des-cbc": 971 | this.decrypt = new Crypt_TripleDES(); 972 | break; 973 | case "3des-ctr": 974 | this.decrypt = new Crypt_TripleDES(CRYPT_DES_MODE_CTR); 975 | break; 976 | case "aes256-cbc": 977 | case "aes192-cbc": 978 | case "aes128-cbc": 979 | this.decrypt = new Crypt_AES(); 980 | this.decrypt_block_size = 16; 981 | break; 982 | case "aes256-ctr": 983 | case "aes192-ctr": 984 | case "aes128-ctr": 985 | this.decrypt = new Crypt_AES(CRYPT_AES_MODE_CTR); 986 | this.decrypt_block_size = 16; 987 | break; 988 | case "arcfour": 989 | case "arcfour128": 990 | case "arcfour256": 991 | this.decrypt = new Crypt_RC4(); 992 | break; 993 | case "none": 994 | //this.decrypt = new Crypt_Null(); 995 | break; 996 | } 997 | 998 | keyBytes = pack("Na*", strlen(keyBytes), keyBytes); 999 | 1000 | if (this.encrypt) { 1001 | this.encrypt.enableContinuousBuffer(); 1002 | this.encrypt.disablePadding(); 1003 | 1004 | iv = pack("H*", hash(keyBytes + this.session_id + "A" + this.session_id)); 1005 | while (this.encrypt_block_size > strlen(iv)) { 1006 | iv += pack("H*", hash(keyBytes + this.session_id + iv)); 1007 | } 1008 | this.encrypt.setIV(substr(iv, 0, this.encrypt_block_size)); 1009 | 1010 | key = pack("H*", hash(keyBytes + this.session_id + "C" + this.session_id)); 1011 | while (encryptKeyLength > strlen(key)) { 1012 | key += pack("H*", hash(keyBytes + this.session_id + key)); 1013 | } 1014 | this.encrypt.setKey(substr(key, 0, encryptKeyLength)); 1015 | } 1016 | 1017 | if (this.decrypt) { 1018 | this.decrypt.enableContinuousBuffer(); 1019 | this.decrypt.disablePadding(); 1020 | 1021 | iv = pack("H*", hash(keyBytes + this.session_id + "B" + this.session_id)); 1022 | while (this.decrypt_block_size > strlen(iv)) { 1023 | iv += pack("H*", hash(keyBytes + this.session_id + iv)); 1024 | } 1025 | this.decrypt.setIV(substr(iv, 0, this.decrypt_block_size)); 1026 | 1027 | key = pack("H*", hash(keyBytes + this.session_id + "D" + this.session_id)); 1028 | while (decryptKeyLength > strlen(key)) { 1029 | key += pack("H*", hash(keyBytes + this.session_id + key)); 1030 | } 1031 | this.decrypt.setKey(substr(key, 0, decryptKeyLength)); 1032 | } 1033 | 1034 | /* The "arcfour128" algorithm is the RC4 cipher, as described in 1035 | [SCHNEIER], using a 128-bit key. The first 1536 bytes of keystream 1036 | generated by the cipher MUST be discarded, and the first byte of the 1037 | first encrypted packet MUST be encrypted using the 1537th byte of 1038 | keystream. 1039 | 1040 | -- http://tools.ietf.org/html/rfc4345#section-4 */ 1041 | if (encrypt == "arcfour128" || encrypt == "arcfour256") { 1042 | this.encrypt.encrypt(str_repeat("\0", 1536)); 1043 | } 1044 | if (decrypt == "arcfour128" || decrypt == "arcfour256") { 1045 | this.decrypt.decrypt(str_repeat("\0", 1536)); 1046 | } 1047 | 1048 | for (i = 0; i < count(mac_algorithms) && !in_array(mac_algorithms[i], this.mac_algorithms_client_to_server); i++); 1049 | if (i == count(mac_algorithms)) { 1050 | user_error("No compatible client to server message authentication algorithms found", E_USER_NOTICE); 1051 | return this._disconnect(exports.NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); 1052 | } 1053 | 1054 | createKeyLength = 0; // ie. mac_algorithms[i] == "none" 1055 | switch (mac_algorithms[i]) { 1056 | case "hmac-sha1": 1057 | this.hmac_create = new Crypt_Hash("sha1"); 1058 | createKeyLength = 20; 1059 | break; 1060 | case "hmac-sha1-96": 1061 | this.hmac_create = new Crypt_Hash("sha1-96"); 1062 | createKeyLength = 20; 1063 | break; 1064 | case "hmac-md5": 1065 | this.hmac_create = new Crypt_Hash("md5"); 1066 | createKeyLength = 16; 1067 | break; 1068 | case "hmac-md5-96": 1069 | this.hmac_create = new Crypt_Hash("md5-96"); 1070 | createKeyLength = 16; 1071 | } 1072 | 1073 | for (i = 0; i < count(mac_algorithms) && !in_array(mac_algorithms[i], this.mac_algorithms_server_to_client); i++); 1074 | if (i == count(mac_algorithms)) { 1075 | user_error("No compatible server to client message authentication algorithms found", E_USER_NOTICE); 1076 | return this._disconnect(exports.NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); 1077 | } 1078 | 1079 | checkKeyLength = 0; 1080 | this.hmac_size = 0; 1081 | switch (mac_algorithms[i]) { 1082 | case "hmac-sha1": 1083 | this.hmac_check = new Crypt_Hash("sha1"); 1084 | checkKeyLength = 20; 1085 | this.hmac_size = 20; 1086 | break; 1087 | case "hmac-sha1-96": 1088 | this.hmac_check = new Crypt_Hash("sha1-96"); 1089 | checkKeyLength = 20; 1090 | this.hmac_size = 12; 1091 | break; 1092 | case "hmac-md5": 1093 | this.hmac_check = new Crypt_Hash("md5"); 1094 | checkKeyLength = 16; 1095 | this.hmac_size = 16; 1096 | break; 1097 | case "hmac-md5-96": 1098 | this.hmac_check = new Crypt_Hash("md5-96"); 1099 | checkKeyLength = 16; 1100 | this.hmac_size = 12; 1101 | } 1102 | 1103 | key = pack("H*", hash(keyBytes + this.session_id + "E" + this.session_id)); 1104 | while (createKeyLength > strlen(key)) { 1105 | key += pack("H*", hash(keyBytes + this.session_id + key)); 1106 | } 1107 | this.hmac_create.setKey(substr(key, 0, createKeyLength)); 1108 | 1109 | key = pack("H*", hash(keyBytes + this.session_id + "F" + this.session_id)); 1110 | while (checkKeyLength > strlen(key)) { 1111 | key += pack("H*", hash(keyBytes + this.session_id + key)); 1112 | } 1113 | this.hmac_check.setKey(substr(key, 0, checkKeyLength)); 1114 | 1115 | for (i = 0; i < count(compression_algorithms) && !in_array(compression_algorithms[i], this.compression_algorithms_server_to_client); i++); 1116 | if (i == count(compression_algorithms)) { 1117 | user_error("No compatible server to client compression algorithms found", E_USER_NOTICE); 1118 | return this._disconnect(exports.NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); 1119 | } 1120 | this.decompress = compression_algorithms[i] == "zlib"; 1121 | 1122 | for (i = 0; i < count(compression_algorithms) && !in_array(compression_algorithms[i], this.compression_algorithms_client_to_server); i++); 1123 | if (i == count(compression_algorithms)) { 1124 | user_error("No compatible client to server compression algorithms found", E_USER_NOTICE); 1125 | return this._disconnect(exports.NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); 1126 | } 1127 | this.compress = compression_algorithms[i] == "zlib"; 1128 | 1129 | return true; 1130 | } 1131 | 1132 | /** 1133 | * Login 1134 | * 1135 | * @param String username 1136 | * @param optional String password 1137 | * @return Boolean 1138 | * @internal It might be worthwhile, at some point, to protect against 1139 | * {@link http://tools.ietf.org/html/rfc4251#section-9.3.9 traffic analysis} 1140 | * by sending dummy SSH_MSG_IGNORE messages. 1141 | */ 1142 | this.login = function(username, password) { 1143 | password = password || ""; 1144 | if (!(this.bitmap & exports.NET_SSH2_MASK_CONSTRUCTOR)) { 1145 | return false; 1146 | } 1147 | 1148 | packet = pack("CNa*", 1149 | exports.NET_SSH2_MSG_SERVICE_REQUEST, strlen("ssh-userauth"), "ssh-userauth" 1150 | ); 1151 | 1152 | if (!this._send_binary_packet(packet)) { 1153 | return false; 1154 | } 1155 | 1156 | response = this._get_binary_packet(); 1157 | if (response === false) { 1158 | user_error("Connection closed by server", E_USER_NOTICE); 1159 | return false; 1160 | } 1161 | 1162 | extract(unpack("Ctype", this._string_shift(response, 1))); 1163 | 1164 | if (type != exports.NET_SSH2_MSG_SERVICE_ACCEPT) { 1165 | user_error("Expected SSH_MSG_SERVICE_ACCEPT", E_USER_NOTICE); 1166 | return false; 1167 | } 1168 | 1169 | // although PHP5"s get_class() preserves the case, PHP4"s does not 1170 | if (is_object(password) && strtolower(get_class(password)) == "crypt_rsa") { 1171 | return this._privatekey_login(username, password); 1172 | } 1173 | 1174 | utf8_password = utf8_encode(password); 1175 | packet = pack("CNa*Na*Na*CNa*", 1176 | exports.NET_SSH2_MSG_USERAUTH_REQUEST, strlen(username), username, strlen("ssh-connection"), "ssh-connection", 1177 | strlen("password"), "password", 0, strlen(utf8_password), utf8_password 1178 | ); 1179 | 1180 | if (!this._send_binary_packet(packet)) { 1181 | return false; 1182 | } 1183 | 1184 | // remove the username and password from the last logged packet 1185 | if (defined("NET_SSH2_LOGGING") && exports.NET_SSH2_LOGGING == exports.NET_SSH2_LOG_COMPLEX) { 1186 | packet = pack("CNa*Na*Na*CNa*", 1187 | exports.NET_SSH2_MSG_USERAUTH_REQUEST, strlen("username"), "username", strlen("ssh-connection"), "ssh-connection", 1188 | strlen("password"), "password", 0, strlen("password"), "password" 1189 | ); 1190 | this.message_log[count(this.message_log) - 1] = packet; 1191 | } 1192 | 1193 | response = this._get_binary_packet(); 1194 | if (response === false) { 1195 | user_error("Connection closed by server", E_USER_NOTICE); 1196 | return false; 1197 | } 1198 | 1199 | extract(unpack("Ctype", this._string_shift(response, 1))); 1200 | 1201 | switch (type) { 1202 | case exports.NET_SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ: // in theory, the password can be changed 1203 | if (defined("NET_SSH2_LOGGING")) { 1204 | this.message_number_log[count(this.message_number_log) - 1] = "NET_SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ"; 1205 | } 1206 | extract(unpack("Nlength", this._string_shift(response, 4))); 1207 | this.errors.push("SSH_MSG_USERAUTH_PASSWD_CHANGEREQ: " + utf8_decode(this._string_shift(response, length))); 1208 | return this._disconnect(exports.NET_SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER); 1209 | case exports.NET_SSH2_MSG_USERAUTH_FAILURE: 1210 | // either the login is bad or the server employees multi-factor authentication 1211 | return false; 1212 | case exports.NET_SSH2_MSG_USERAUTH_SUCCESS: 1213 | this.bitmap |= exports.NET_SSH2_MASK_LOGIN; 1214 | return true; 1215 | } 1216 | 1217 | return false; 1218 | }; 1219 | 1220 | /** 1221 | * Login with an RSA private key 1222 | * 1223 | * @param String username 1224 | * @param Crypt_RSA password 1225 | * @return Boolean 1226 | * @internal It might be worthwhile, at some point, to protect against 1227 | * {@link http://tools.ietf.org/html/rfc4251#section-9.3.9 traffic analysis} 1228 | * by sending dummy SSH_MSG_IGNORE messages. 1229 | */ 1230 | this._privatekey_login = function(username, privatekey) { 1231 | // see http://tools.ietf.org/html/rfc4253#page-15 1232 | publickey = privatekey.getPublicKey(CRYPT_RSA_PUBLIC_FORMAT_RAW); 1233 | if (publickey === false) { 1234 | return false; 1235 | } 1236 | 1237 | publickey = { 1238 | "e" : publickey["e"].toBytes(true), 1239 | "n" : publickey["n"].toBytes(true) 1240 | }; 1241 | publickey = pack("Na*Na*Na*", 1242 | strlen("ssh-rsa"), "ssh-rsa", strlen(publickey["e"]), publickey["e"], strlen(publickey["n"]), publickey["n"] 1243 | ); 1244 | 1245 | part1 = pack("CNa*Na*Na*", 1246 | exports.NET_SSH2_MSG_USERAUTH_REQUEST, strlen(username), username, strlen("ssh-connection"), "ssh-connection", 1247 | strlen("publickey"), "publickey" 1248 | ); 1249 | part2 = pack("Na*Na*", strlen("ssh-rsa"), "ssh-rsa", strlen(publickey), publickey); 1250 | 1251 | packet = part1 . chr(0) . part2; 1252 | 1253 | if (!this._send_binary_packet(packet)) { 1254 | return false; 1255 | } 1256 | 1257 | response = this._get_binary_packet(); 1258 | if (response === false) { 1259 | user_error("Connection closed by server", E_USER_NOTICE); 1260 | return false; 1261 | } 1262 | 1263 | extract(unpack("Ctype", this._string_shift(response, 1))); 1264 | 1265 | switch (type) { 1266 | case exports.NET_SSH2_MSG_USERAUTH_FAILURE: 1267 | extract(unpack("Nlength", this._string_shift(response, 4))); 1268 | this.errors.push("SSH_MSG_USERAUTH_FAILURE: " + this._string_shift(response, length)); 1269 | return this._disconnect(exports.NET_SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER); 1270 | case exports.NET_SSH2_MSG_USERAUTH_PK_OK: 1271 | // we"ll just take it on faith that the public key blob and the public key algorithm name are as 1272 | // they should be 1273 | if (defined("NET_SSH2_LOGGING")) { 1274 | this.message_number_log[count(this.message_number_log) - 1] = "NET_SSH2_MSG_USERAUTH_PK_OK"; 1275 | } 1276 | } 1277 | 1278 | packet = part1 . chr(1) . part2; 1279 | privatekey.setSignatureMode(CRYPT_RSA_SIGNATURE_PKCS1); 1280 | signature = privatekey.sign(pack("Na*a*", strlen(this.session_id), this.session_id, packet)); 1281 | signature = pack("Na*Na*", strlen("ssh-rsa"), "ssh-rsa", strlen(signature), signature); 1282 | packet += pack("Na*", strlen(signature), signature); 1283 | 1284 | if (!this._send_binary_packet(packet)) { 1285 | return false; 1286 | } 1287 | 1288 | response = this._get_binary_packet(); 1289 | if (response === false) { 1290 | user_error("Connection closed by server", E_USER_NOTICE); 1291 | return false; 1292 | } 1293 | 1294 | extract(unpack("Ctype", this._string_shift(response, 1))); 1295 | 1296 | switch (type) { 1297 | case exports.NET_SSH2_MSG_USERAUTH_FAILURE: 1298 | // either the login is bad or the server employees multi-factor authentication 1299 | return false; 1300 | case exports.NET_SSH2_MSG_USERAUTH_SUCCESS: 1301 | this.bitmap |= exports.NET_SSH2_MASK_LOGIN; 1302 | return true; 1303 | } 1304 | 1305 | return false; 1306 | }; 1307 | 1308 | /** 1309 | * Execute Command 1310 | * 1311 | * @param String command 1312 | * @return String 1313 | */ 1314 | this.exec = function(command) { 1315 | if (!(this.bitmap & exports.NET_SSH2_MASK_LOGIN)) { 1316 | return false; 1317 | } 1318 | 1319 | // RFC4254 defines the (client) window size as "bytes the other party can send before it must wait for the window to 1320 | // be adjusted". 0x7FFFFFFF is, at 4GB, the max size. technically, it should probably be decremented, but, 1321 | // honestly, if you"re transfering more than 4GB, you probably shouldn"t be using phpseclib, anyway. 1322 | // see http://tools.ietf.org/html/rfc4254#section-5.2 for more info 1323 | this.window_size_client_to_server[exports.NET_SSH2_CHANNEL_EXEC] = 0x7FFFFFFF; 1324 | // 0x8000 is the maximum max packet size, per http://tools.ietf.org/html/rfc4253#section-6.1, although since PuTTy 1325 | // uses 0x4000, that"s what will be used here, as well. 1326 | packet_size = 0x4000; 1327 | 1328 | packet = pack("CNa*N3", 1329 | NET_SSH2_MSG_CHANNEL_OPEN, strlen("session"), "session", exports.NET_SSH2_CHANNEL_EXEC, 1330 | this.window_size_client_to_server[exports.NET_SSH2_CHANNEL_EXEC], packet_size); 1331 | 1332 | if (!this._send_binary_packet(packet)) { 1333 | return false; 1334 | } 1335 | 1336 | this.channel_status[exports.NET_SSH2_CHANNEL_EXEC] = exports.NET_SSH2_MSG_CHANNEL_OPEN; 1337 | 1338 | response = this._get_channel_packet(exports.NET_SSH2_CHANNEL_EXEC); 1339 | if (response === false) { 1340 | return false; 1341 | } 1342 | 1343 | // sending a pty-req SSH_MSG_CHANNEL_REQUEST message is unnecessary and, in fact, in most cases, slows things 1344 | // down. the one place where it might be desirable is if you"re doing something like Net_SSH2::exec("ping localhost &"). 1345 | // with a pty-req SSH_MSG_cHANNEL_REQUEST, exec() will return immediately and the ping process will then 1346 | // then immediately terminate. without such a request exec() will loop indefinitely. the ping process won"t end but 1347 | // neither will your script. 1348 | 1349 | // although, in theory, the size of SSH_MSG_CHANNEL_REQUEST could exceed the maximum packet size established by 1350 | // SSH_MSG_CHANNEL_OPEN_CONFIRMATION, RFC4254#section-5.1 states that the "maximum packet size" refers to the 1351 | // "maximum size of an individual data packet". ie. SSH_MSG_CHANNEL_DATA. RFC4254#section-5.2 corroborates. 1352 | packet = pack("CNNa*CNa*", 1353 | exports.NET_SSH2_MSG_CHANNEL_REQUEST, this.server_channels[exports.NET_SSH2_CHANNEL_EXEC], 1354 | strlen("exec"), "exec", 1, strlen(command), command); 1355 | if (!this._send_binary_packet(packet)) { 1356 | return false; 1357 | } 1358 | 1359 | this.channel_status[exports.NET_SSH2_CHANNEL_EXEC] = exports.NET_SSH2_MSG_CHANNEL_REQUEST; 1360 | 1361 | response = this._get_channel_packet(exports.NET_SSH2_CHANNEL_EXEC); 1362 | if (response === false) { 1363 | return false; 1364 | } 1365 | 1366 | this.channel_status[exports.NET_SSH2_CHANNEL_EXEC] = exports.NET_SSH2_MSG_CHANNEL_DATA; 1367 | 1368 | output = ""; 1369 | while (true) { 1370 | temp = this._get_channel_packet(exports.NET_SSH2_CHANNEL_EXEC); 1371 | switch (true) { 1372 | case temp === true: 1373 | return output; 1374 | case temp === false: 1375 | return false; 1376 | default: 1377 | output += temp; 1378 | } 1379 | } 1380 | }; 1381 | 1382 | this._listen = function() { 1383 | this.packets = []; 1384 | 1385 | var remaining_length, buffer, message, 1386 | _self = this; 1387 | 1388 | function reset() { 1389 | remaining_length = -1; 1390 | message = {}; 1391 | return Strtok.UINT32_BE; 1392 | } 1393 | reset(); 1394 | 1395 | Strtok.parse(this.fsock, function(v, callback) { 1396 | //console.log("callback: ", v); 1397 | if (typeof v == "undefined") 1398 | return Strtok.UINT32_BE; 1399 | 1400 | if (!message.packet_length) { 1401 | message.packet_length = v; 1402 | //return Strtok.UINT8; 1403 | return new Strtok.BufferType(_self.decrypt_block_size); 1404 | } 1405 | else if (!message.padding_length) { 1406 | message.raw = v; 1407 | if (_self.decrypt) 1408 | raw = _self.decrypt.decrypt(message.raw); 1409 | message.padding_length = Strtok.UINT8.get(message.raw, 0); 1410 | message.type = (new Strtok.StringType(1, "binary")).get(message.raw, Strtok.UINT8.len).charCodeAt(0); 1411 | 1412 | remaining_length = message.packet_length - _self.decrypt_block_size - 1;// + 4 - _self.decrypt_block_size; 1413 | //console.log("how many bytes remaining? ", message.packet_length, remaining_length, _self.decrypt_block_size); 1414 | buffer = new Buffer(0); 1415 | return new Strtok.BufferType(remaining_length); 1416 | } 1417 | else if (remaining_length > 0) { 1418 | remaining_length -= v.length; 1419 | buffer = Buffer.concat(buffer, v); 1420 | //console.log("MORE BYTES? ", remaining_length); 1421 | return new Strtok.BufferType(remaining_length); 1422 | } 1423 | else if (!message.payload) { 1424 | //console.log("DO WE GET HERE??", buffer.length); 1425 | if (buffer.length > 0) 1426 | message.raw = Buffer.concat(message.raw, _self.decrypt ? _self.decrypt.decrypt(buffer) : buffer); 1427 | 1428 | var pay_len = message.packet_length - message.padding_length - 1; 1429 | //console.log("message length: ", message.raw.length, pay_len); 1430 | message.payload = message.raw.slice(0, pay_len); 1431 | message.padding = message.raw.slice(pay_len, pay_len + message.padding_length); 1432 | 1433 | /*if (_self.hmac_check) { 1434 | message.hmac = fread($this->fsock, $this->hmac_size); 1435 | if ($hmac != $this->hmac_check->hash(pack('NNCa*', $this->get_seq_no, $packet_length, $padding_length, $payload . $padding))) { 1436 | user_error('Invalid HMAC', E_USER_NOTICE); 1437 | return false; 1438 | } 1439 | } 1440 | else { 1441 | 1442 | }*/ 1443 | _self.get_seq_no++; 1444 | 1445 | if (exports.NET_SSH2_LOGGING) { 1446 | temp = _self.message_numbers[message.payload[0]] 1447 | ? _self.message_numbers[message.payload[0]] 1448 | : "UNKNOWN"; 1449 | if (exports.NET_SSH2_LOGGING == exports.NET_SSH2_LOG_COMPLEX) 1450 | _self.message_log.push(message.payload.slice(1)); 1451 | } 1452 | 1453 | message = _self._filter(message); 1454 | if (message !== null) { 1455 | _self.packets[_self.get_seq_no] = message; 1456 | var cb = _self.listen_callbacks.shift(); 1457 | if (cb) 1458 | cb(message); 1459 | } 1460 | 1461 | // start over from the start (wait for the next packet) 1462 | return reset(); 1463 | } 1464 | }); 1465 | }; 1466 | 1467 | /** 1468 | * Gets Binary Packets 1469 | * See "6. Binary Packet Protocol" of rfc4253 for more info. 1470 | * 1471 | * @see Net_SSH2::_send_binary_packet() 1472 | * @return String 1473 | */ 1474 | this._get_binary_packet = function(callback) { 1475 | this.listen_callbacks.push(callback); 1476 | }; 1477 | 1478 | /** 1479 | * Filter Binary Packets 1480 | * Because some binary packets need to be ignored... 1481 | * 1482 | * @see Net_SSH2::_get_binary_packet() 1483 | * @return String 1484 | */ 1485 | this._filter = function(message) { 1486 | console.log("payload: ", message); 1487 | switch (message.type) { 1488 | case exports.NET_SSH2_MSG_DISCONNECT: 1489 | var reason_code = Strtok.UINT8.get(message.payload, 1); 1490 | var length = Strtok.UINT8.get(message.payload, 1 + Strtok.UINT8.len); 1491 | this.errors.push("SSH_MSG_DISCONNECT: " + this.disconnect_reasons[reason_code] + "\r\n" 1492 | + message.payload.slice(1 + (Strtok.UINT8.len * 2), length).toString("utf8")); 1493 | this.bitmask = 0; 1494 | // return immediately, message will not be pushed on the stack 1495 | return false; 1496 | case exports.NET_SSH2_MSG_IGNORE: 1497 | // return immediately, message will not be pushed on the stack 1498 | return null; 1499 | case exports.NET_SSH2_MSG_DEBUG: 1500 | var length = Strtok.UINT8.get(message.payload, 2); 1501 | this.errors.push("SSH_MSG_DEBUG: " + message.payload.slice(2 + Strtok.UINT8.len, length)); 1502 | // return immediately, message will not be pushed on the stack 1503 | return null; 1504 | case exports.NET_SSH2_MSG_UNIMPLEMENTED: 1505 | // return immediately, message will not be pushed on the stack 1506 | return false; 1507 | case exports.NET_SSH2_MSG_KEXINIT: 1508 | console.log("KEXINIT: ", this.session_id); 1509 | if (this.session_id !== false) { 1510 | if (!this._key_exchange(message.payload)) { 1511 | this.bitmask = 0; 1512 | return false; 1513 | } 1514 | // return immediately, message will not be pushed on the stack 1515 | return null; 1516 | } 1517 | break; 1518 | } 1519 | 1520 | // see http://tools.ietf.org/html/rfc4252#section-5.4; only called when the 1521 | // encryption has been activated and when we haven"t already logged in 1522 | if ((this.bitmap & exports.NET_SSH2_MASK_CONSTRUCTOR) && !(this.bitmap & exports.NET_SSH2_MASK_LOGIN) 1523 | && message.type == exports.NET_SSH2_MSG_USERAUTH_BANNER) { 1524 | var length = Strtok.INT32_BE.get(message.payload, 1); 1525 | this.errors.push("SSH_MSG_USERAUTH_BANNER: " + payload.slice(1 + Strtok.INT32_BE, length).toString("utf8")); 1526 | // return immediately, message will not be pushed on the stack 1527 | return null; 1528 | } 1529 | 1530 | // only called when we've already logged in 1531 | if ((this.bitmap & exports.NET_SSH2_MASK_CONSTRUCTOR) && (this.bitmap & exports.NET_SSH2_MASK_LOGIN)) { 1532 | switch (message.type) { 1533 | case exports.NET_SSH2_MSG_GLOBAL_REQUEST: // see http://tools.ietf.org/html/rfc4254#section-4 1534 | var length = Strtok.UINT8.get(message.payload, 1); 1535 | this.errors.push("SSH_MSG_GLOBAL_REQUEST: " + message.payload.slice(1 + Strtok.UINT8.len, length).toString("utf8")); 1536 | if (!this._send_binary_packet(pack("C", exports.NET_SSH2_MSG_REQUEST_FAILURE))) 1537 | return this._disconnect(exports.NET_SSH2_DISCONNECT_BY_APPLICATION); 1538 | 1539 | // return immediately, message will not be pushed on the stack 1540 | return null; 1541 | break; 1542 | case exports.NET_SSH2_MSG_CHANNEL_OPEN: // see http://tools.ietf.org/html/rfc4254#section-5.1 1543 | var length = Strtok.UINT8.get(message.payload, 1); 1544 | this.errors.push("SSH_MSG_CHANNEL_OPEN: " + message.payload.slice(1, length).toString("utf8")); 1545 | 1546 | var server_channel = Strtok.UINT8.get(message.payload, 1 + 4); // skip over client channel 1547 | 1548 | packet = pack("CN3a*Na*", 1549 | exports.NET_SSH2_MSG_REQUEST_FAILURE, server_channel, NET_SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED, 0, "", 0, ""); 1550 | 1551 | if (!this._send_binary_packet(packet)) { 1552 | return this._disconnect(exports.NET_SSH2_DISCONNECT_BY_APPLICATION); 1553 | } 1554 | 1555 | // return immediately, message will not be pushed on the stack 1556 | return null; 1557 | case exports.NET_SSH2_MSG_CHANNEL_WINDOW_ADJUST: 1558 | // return immediately, message will not be pushed on the stack 1559 | return null; 1560 | } 1561 | } 1562 | 1563 | return message; 1564 | }; 1565 | 1566 | /** 1567 | * Gets channel data 1568 | * Returns the data as a string if it"s available and false if not. 1569 | * 1570 | * @param client_channel 1571 | * @return Mixed 1572 | */ 1573 | this._get_channel_packet = function(client_channel) { 1574 | if (!empty(this.channel_buffers[client_channel])) { 1575 | return array_shift(this.channel_buffers[client_channel]); 1576 | } 1577 | 1578 | while (true) { 1579 | response = this._get_binary_packet(); 1580 | if (response === false) { 1581 | user_error("Connection closed by server", E_USER_NOTICE); 1582 | return false; 1583 | } 1584 | 1585 | extract(unpack("Ctype/Nchannel", this._string_shift(response, 5))); 1586 | 1587 | switch (this.channel_status[channel]) { 1588 | case NET_SSH2_MSG_CHANNEL_OPEN: 1589 | switch (type) { 1590 | case NET_SSH2_MSG_CHANNEL_OPEN_CONFIRMATION: 1591 | extract(unpack("Nserver_channel", this._string_shift(response, 4))); 1592 | this.server_channels[client_channel] = server_channel; 1593 | this._string_shift(response, 4); // skip over (server) window size 1594 | temp = unpack("Npacket_size_client_to_server", this._string_shift(response, 4)); 1595 | this.packet_size_client_to_server[client_channel] = temp["packet_size_client_to_server"]; 1596 | return true; 1597 | //case NET_SSH2_MSG_CHANNEL_OPEN_FAILURE: 1598 | default: 1599 | user_error("Unable to open channel", E_USER_NOTICE); 1600 | return this._disconnect(NET_SSH2_DISCONNECT_BY_APPLICATION); 1601 | } 1602 | break; 1603 | case NET_SSH2_MSG_CHANNEL_REQUEST: 1604 | switch (type) { 1605 | case NET_SSH2_MSG_CHANNEL_SUCCESS: 1606 | return true; 1607 | //case NET_SSH2_MSG_CHANNEL_FAILURE: 1608 | default: 1609 | user_error("Unable to request pseudo-terminal", E_USER_NOTICE); 1610 | return this._disconnect(NET_SSH2_DISCONNECT_BY_APPLICATION); 1611 | } 1612 | 1613 | } 1614 | 1615 | switch (type) { 1616 | case NET_SSH2_MSG_CHANNEL_DATA: 1617 | if (client_channel == NET_SSH2_CHANNEL_EXEC) { 1618 | // SCP requires null packets, such as this, be sent. further, in the case of the ssh.com SSH server 1619 | // this actually seems to make things twice as fast. more to the point, the message right after 1620 | // SSH_MSG_CHANNEL_DATA (usually SSH_MSG_IGNORE) won"t block for as long as it would have otherwise. 1621 | // in OpenSSH it slows things down but only by a couple thousandths of a second. 1622 | this._send_channel_packet(client_channel, chr(0)); 1623 | } 1624 | extract(unpack("Nlength", this._string_shift(response, 4))); 1625 | data = this._string_shift(response, length); 1626 | if (client_channel == channel) { 1627 | return data; 1628 | } 1629 | if (!isset(this.channel_buffers[client_channel])) { 1630 | this.channel_buffers[client_channel] = [] 1631 | } 1632 | this.channel_buffers[client_channel].push(data); 1633 | break; 1634 | case NET_SSH2_MSG_CHANNEL_EXTENDED_DATA: 1635 | if (client_channel == NET_SSH2_CHANNEL_EXEC) { 1636 | this._send_channel_packet(client_channel, chr(0)); 1637 | } 1638 | // currently, there"s only one possible value for data_type_code: NET_SSH2_EXTENDED_DATA_STDERR 1639 | extract(unpack("Ndata_type_code/Nlength", this._string_shift(response, 8))); 1640 | data = this._string_shift(response, length); 1641 | if (client_channel == channel) { 1642 | return data; 1643 | } 1644 | if (!isset(this.channel_buffers[client_channel])) { 1645 | this.channel_buffers[client_channel] = [] 1646 | } 1647 | this.channel_buffers[client_channel].push(data); 1648 | break; 1649 | case NET_SSH2_MSG_CHANNEL_REQUEST: 1650 | extract(unpack("Nlength", this._string_shift(response, 4))); 1651 | value = this._string_shift(response, length); 1652 | switch (value) { 1653 | case "exit-signal": 1654 | this._string_shift(response, 1); 1655 | extract(unpack("Nlength", this._string_shift(response, 4))); 1656 | this.errors.push("SSH_MSG_CHANNEL_REQUEST (exit-signal): " + this._string_shift(response, length)); 1657 | this._string_shift(response, 1); 1658 | extract(unpack("Nlength", this._string_shift(response, 4))); 1659 | if (length) { 1660 | this.errors.push(this.errors.pop() + "\r\n" + this._string_shift(response, length)); 1661 | } 1662 | //case "exit-status": 1663 | default: 1664 | // "Some systems may not implement signals, in which case they SHOULD ignore this message." 1665 | // -- http://tools.ietf.org/html/rfc4254#section-6.9 1666 | break; 1667 | } 1668 | break; 1669 | case NET_SSH2_MSG_CHANNEL_CLOSE: 1670 | this._send_binary_packet(pack("CN", NET_SSH2_MSG_CHANNEL_CLOSE, this.server_channels[channel])); 1671 | return true; 1672 | case NET_SSH2_MSG_CHANNEL_EOF: 1673 | break; 1674 | default: 1675 | user_error("Error reading channel data", E_USER_NOTICE); 1676 | return this._disconnect(NET_SSH2_DISCONNECT_BY_APPLICATION); 1677 | } 1678 | } 1679 | }; 1680 | 1681 | /** 1682 | * Sends Binary Packets 1683 | * See "6. Binary Packet Protocol" of rfc4253 for more info. 1684 | * 1685 | * @param String data 1686 | * @see Net_SSH2::_get_binary_packet() 1687 | * @return Boolean 1688 | */ 1689 | this._send_binary_packet = function(data) { 1690 | if (feof(this.fsock)) { 1691 | user_error("Connection closed prematurely", E_USER_NOTICE); 1692 | return false; 1693 | } 1694 | 1695 | //if (this.compress) { 1696 | // // the -4 removes the checksum: 1697 | // // http://php.net/function.gzcompress#57710 1698 | // data = substr(gzcompress(data), 0, -4); 1699 | //} 1700 | 1701 | // 4 (packet length) + 1 (padding length) + 4 (minimal padding amount) == 9 1702 | packet_length = strlen(data) + 9; 1703 | // round up to the nearest this.encrypt_block_size 1704 | packet_length+= ((this.encrypt_block_size - 1) * packet_length) % this.encrypt_block_size; 1705 | // subtracting strlen(data) is obvious - subtracting 5 is necessary because of packet_length and padding_length 1706 | padding_length = packet_length - strlen(data) - 5; 1707 | 1708 | padding = ""; 1709 | for (i = 0; i < padding_length; i++) { 1710 | padding += chr(crypt_random(0, 255)); 1711 | } 1712 | 1713 | // we subtract 4 from packet_length because the packet_length field isn"t supposed to include itself 1714 | packet = pack("NCa*", packet_length - 4, padding_length, data . padding); 1715 | 1716 | hmac = this.hmac_create !== false ? this.hmac_create.hash(pack("Na*", this.send_seq_no, packet)) : ""; 1717 | this.send_seq_no++; 1718 | 1719 | if (this.encrypt !== false) { 1720 | packet = this.encrypt.encrypt(packet); 1721 | } 1722 | 1723 | packet += hmac; 1724 | 1725 | start = strtok(microtime(), " ") + strtok(""); // http://php.net/microtime#61838 1726 | result = strlen(packet) == fputs(this.fsock, packet); 1727 | stop = strtok(microtime(), " ") + strtok(""); 1728 | 1729 | if (defined("NET_SSH2_LOGGING")) { 1730 | temp = isset(this.message_numbers[ord(data[0])]) ? this.message_numbers[ord(data[0])] : "UNKNOWN"; 1731 | this.message_number_log.push(". " + temp + 1732 | " (" + round(stop - start, 4) + "s)"); 1733 | if (NET_SSH2_LOGGING == NET_SSH2_LOG_COMPLEX) { 1734 | this.message_log.push(substr(data, 1)); 1735 | } 1736 | } 1737 | 1738 | return result; 1739 | }; 1740 | 1741 | /** 1742 | * Sends channel data 1743 | * Spans multiple SSH_MSG_CHANNEL_DATAs if appropriate 1744 | * 1745 | * @param Integer client_channel 1746 | * @param String data 1747 | * @return Boolean 1748 | */ 1749 | this._send_channel_packet = function(client_channel, data) { 1750 | while (strlen(data) > this.packet_size_client_to_server[client_channel]) { 1751 | // resize the window, if appropriate 1752 | this.window_size_client_to_server[client_channel]-= this.packet_size_client_to_server[client_channel]; 1753 | if (this.window_size_client_to_server[client_channel] < 0) { 1754 | packet = pack("CNN", NET_SSH2_MSG_CHANNEL_WINDOW_ADJUST, this.server_channels[client_channel], this.window_size); 1755 | if (!this._send_binary_packet(packet)) { 1756 | return false; 1757 | } 1758 | this.window_size_client_to_server[client_channel]+= this.window_size; 1759 | } 1760 | 1761 | packet = pack("CN2a*", 1762 | NET_SSH2_MSG_CHANNEL_DATA, 1763 | this.server_channels[client_channel], 1764 | this.packet_size_client_to_server[client_channel], 1765 | this._string_shift(data, this.packet_size_client_to_server[client_channel]) 1766 | ); 1767 | 1768 | if (!this._send_binary_packet(packet)) { 1769 | return false; 1770 | } 1771 | } 1772 | 1773 | // resize the window, if appropriate 1774 | this.window_size_client_to_server[client_channel]-= strlen(data); 1775 | if (this.window_size_client_to_server[client_channel] < 0) { 1776 | packet = pack("CNN", NET_SSH2_MSG_CHANNEL_WINDOW_ADJUST, this.server_channels[client_channel], this.window_size); 1777 | if (!this._send_binary_packet(packet)) { 1778 | return false; 1779 | } 1780 | this.window_size_client_to_server[client_channel]+= this.window_size; 1781 | } 1782 | 1783 | return this._send_binary_packet(pack("CN2a*", 1784 | NET_SSH2_MSG_CHANNEL_DATA, 1785 | this.server_channels[client_channel], 1786 | strlen(data), 1787 | data)); 1788 | }; 1789 | 1790 | /** 1791 | * String Shift 1792 | * Inspired by array_shift 1793 | * 1794 | * @param String string 1795 | * @param optional Integer index 1796 | * @return String 1797 | */ 1798 | this._string_shift = function(string, index) { 1799 | if (typeof index == "undefined") 1800 | index = 1; 1801 | substr = substr(string, 0, index); 1802 | string = substr(string, index); 1803 | return substr; 1804 | }; 1805 | 1806 | /** 1807 | * Returns a log of the packets that have been sent and received. 1808 | * Returns a string if NET_SSH2_LOGGING == NET_SSH2_LOG_COMPLEX, an array if 1809 | * NET_SSH2_LOGGING == NET_SSH2_LOG_SIMPLE and false if !defined("NET_SSH2_LOGGING") 1810 | * 1811 | * @return String or Array 1812 | */ 1813 | this.getLog = function() { 1814 | if (!defined("NET_SSH2_LOGGING")) { 1815 | return false; 1816 | } 1817 | 1818 | switch (NET_SSH2_LOGGING) { 1819 | case NET_SSH2_LOG_SIMPLE: 1820 | return this.message_number_log; 1821 | break; 1822 | case NET_SSH2_LOG_COMPLEX: 1823 | return this._format_log(this.message_log, this.message_number_log); 1824 | break; 1825 | default: 1826 | return false; 1827 | } 1828 | }; 1829 | 1830 | /** 1831 | * Formats a log for printing 1832 | * 1833 | * @param Array message_log 1834 | * @param Array message_number_log 1835 | * @return String 1836 | */ 1837 | this._format_log = function(message_log, message_number_log) { 1838 | var boundary = ":", long_width = 65, short_width = 16; 1839 | 1840 | output = ""; 1841 | for (i = 0; i < count(message_log); i++) { 1842 | output += message_number_log[i] + "\r\n"; 1843 | current_log = message_log[i]; 1844 | j = 0; 1845 | do { 1846 | if (!empty(current_log)) { 1847 | output += str_pad(dechex(j), 7, "0", STR_PAD_LEFT) + "0 "; 1848 | } 1849 | fragment = this._string_shift(current_log, short_width); 1850 | hex = substr( 1851 | preg_replace( 1852 | "#(.)#es", 1853 | "\"" + boundary + "\"" . str_pad(dechex(ord(substr("\\1", -1))), 2, "0", STR_PAD_LEFT), 1854 | fragment), 1855 | strlen(boundary) 1856 | ); 1857 | // replace non ASCII printable characters with dots 1858 | // http://en.wikipedia.org/wiki/ASCII#ASCII_printable_characters 1859 | // also replace < with a . since < messes up the output on web browsers 1860 | raw = preg_replace("#[^\x20-\x7E]|<#", ".", fragment); 1861 | output += str_pad(hex, long_width - short_width, " ") + raw + "\r\n"; 1862 | j++; 1863 | } while (!empty(current_log)); 1864 | output += "\r\n"; 1865 | } 1866 | 1867 | return output; 1868 | }; 1869 | 1870 | /** 1871 | * Returns all errors 1872 | * 1873 | * @return String 1874 | */ 1875 | this.getErrors = function() { 1876 | return this.errors; 1877 | }; 1878 | 1879 | /** 1880 | * Returns the last error 1881 | * 1882 | * @return String 1883 | */ 1884 | this.getLastError = function() { 1885 | return this.errors[count(this.errors) - 1]; 1886 | }; 1887 | 1888 | /** 1889 | * Return the server identification. 1890 | * 1891 | * @return String 1892 | */ 1893 | this.getServerIdentification = function() { 1894 | return this.server_identifier; 1895 | }; 1896 | 1897 | /** 1898 | * Return a list of the key exchange algorithms the server supports. 1899 | * 1900 | * @return Array 1901 | */ 1902 | this.getKexAlgorithms = function() { 1903 | return this.kex_algorithms; 1904 | }; 1905 | 1906 | /** 1907 | * Return a list of the host key (public key) algorithms the server supports. 1908 | * 1909 | * @return Array 1910 | */ 1911 | this.getServerHostKeyAlgorithms = function() { 1912 | return this.server_host_key_algorithms; 1913 | }; 1914 | 1915 | /** 1916 | * Return a list of the (symmetric key) encryption algorithms the server supports, when receiving stuff from the client. 1917 | * 1918 | * @return Array 1919 | */ 1920 | this.getEncryptionAlgorithmsClient2Server = function() { 1921 | return this.encryption_algorithms_client_to_server; 1922 | }; 1923 | 1924 | /** 1925 | * Return a list of the (symmetric key) encryption algorithms the server supports, when sending stuff to the client. 1926 | * 1927 | * @return Array 1928 | */ 1929 | this.getEncryptionAlgorithmsServer2Client = function() { 1930 | return this.encryption_algorithms_server_to_client; 1931 | }; 1932 | 1933 | /** 1934 | * Return a list of the MAC algorithms the server supports, when receiving stuff from the client. 1935 | * 1936 | * @return Array 1937 | */ 1938 | this.getMACAlgorithmsClient2Server = function() { 1939 | return this.mac_algorithms_client_to_server; 1940 | }; 1941 | 1942 | /** 1943 | * Return a list of the MAC algorithms the server supports, when sending stuff to the client. 1944 | * 1945 | * @return Array 1946 | */ 1947 | this.getMACAlgorithmsServer2Client = function() { 1948 | return this.mac_algorithms_server_to_client; 1949 | }; 1950 | 1951 | /** 1952 | * Return a list of the compression algorithms the server supports, when receiving stuff from the client. 1953 | * 1954 | * @return Array 1955 | */ 1956 | this.getCompressionAlgorithmsClient2Server = function() { 1957 | return this.compression_algorithms_client_to_server; 1958 | }; 1959 | 1960 | /** 1961 | * Return a list of the compression algorithms the server supports, when sending stuff to the client. 1962 | * 1963 | * @return Array 1964 | */ 1965 | this.getCompressionAlgorithmsServer2Client = function() { 1966 | return this.compression_algorithms_server_to_client; 1967 | }; 1968 | 1969 | /** 1970 | * Return a list of the languages the server supports, when sending stuff to the client. 1971 | * 1972 | * @return Array 1973 | */ 1974 | this.getLanguagesServer2Client = function() { 1975 | return this.languages_server_to_client; 1976 | }; 1977 | 1978 | /** 1979 | * Return a list of the languages the server supports, when receiving stuff from the client. 1980 | * 1981 | * @return Array 1982 | */ 1983 | this.getLanguagesClient2Server = function() { 1984 | return this.languages_client_to_server; 1985 | }; 1986 | 1987 | /** 1988 | * Returns the server public host key. 1989 | * 1990 | * Caching this the first time you connect to a server and checking the result on subsequent connections 1991 | * is recommended. Returns false if the server signature is not signed correctly with the public host key. 1992 | * 1993 | * @return Mixed 1994 | */ 1995 | this.getServerPublicHostKey = function(){ 1996 | signature = this.signature; 1997 | server_public_host_key = this.server_public_host_key; 1998 | 1999 | extract(unpack("Nlength", this._string_shift(server_public_host_key, 4))); 2000 | this._string_shift(server_public_host_key, length); 2001 | 2002 | switch (this.signature_format) { 2003 | case "ssh-dss": 2004 | temp = unpack("Nlength", this._string_shift(server_public_host_key, 4)); 2005 | p = new Math_BigInteger(this._string_shift(server_public_host_key, temp["length"]), -256); 2006 | 2007 | temp = unpack("Nlength", this._string_shift(server_public_host_key, 4)); 2008 | q = new Math_BigInteger(this._string_shift(server_public_host_key, temp["length"]), -256); 2009 | 2010 | temp = unpack("Nlength", this._string_shift(server_public_host_key, 4)); 2011 | g = new Math_BigInteger(this._string_shift(server_public_host_key, temp["length"]), -256); 2012 | 2013 | temp = unpack("Nlength", this._string_shift(server_public_host_key, 4)); 2014 | y = new Math_BigInteger(this._string_shift(server_public_host_key, temp["length"]), -256); 2015 | 2016 | /* The value for "dss_signature_blob" is encoded as a string containing 2017 | r, followed by s (which are 160-bit integers, without lengths or 2018 | padding, unsigned, and in network byte order). */ 2019 | temp = unpack("Nlength", this._string_shift(signature, 4)); 2020 | if (temp["length"] != 40) { 2021 | user_error("Invalid signature", E_USER_NOTICE); 2022 | return this._disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); 2023 | } 2024 | 2025 | r = new Math_BigInteger(this._string_shift(signature, 20), 256); 2026 | s = new Math_BigInteger(this._string_shift(signature, 20), 256); 2027 | 2028 | if (r.compare(q) >= 0 || s.compare(q) >= 0) { 2029 | user_error("Invalid signature", E_USER_NOTICE); 2030 | return this._disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); 2031 | } 2032 | 2033 | w = s.modInverse(q); 2034 | 2035 | u1 = w.multiply(new Math_BigInteger(sha1(this.session_id), 16)); 2036 | u1 = u1.divide(q)[1]; 2037 | 2038 | u2 = w.multiply(r); 2039 | u2 = u2.divide(q)[1]; 2040 | 2041 | g = g.modPow(u1, p); 2042 | y = y.modPow(u2, p); 2043 | 2044 | v = g.multiply(y); 2045 | v = v.divide(p)[1]; 2046 | v = v.divide(q)[1]; 2047 | 2048 | if (!v.equals(r)) { 2049 | user_error("Bad server signature", E_USER_NOTICE); 2050 | return this._disconnect(NET_SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE); 2051 | } 2052 | 2053 | break; 2054 | case "ssh-rsa": 2055 | temp = unpack("Nlength", this._string_shift(server_public_host_key, 4)); 2056 | e = new Math_BigInteger(this._string_shift(server_public_host_key, temp["length"]), -256); 2057 | 2058 | temp = unpack("Nlength", this._string_shift(server_public_host_key, 4)); 2059 | n = new Math_BigInteger(this._string_shift(server_public_host_key, temp["length"]), -256); 2060 | nLength = temp["length"]; 2061 | 2062 | /* 2063 | temp = unpack("Nlength", this._string_shift(signature, 4)); 2064 | signature = this._string_shift(signature, temp["length"]); 2065 | 2066 | if (!class_exists("Crypt_RSA")) { 2067 | require_once("Crypt/RSA.php"); 2068 | } 2069 | 2070 | rsa = new Crypt_RSA(); 2071 | rsa.setSignatureMode(CRYPT_RSA_SIGNATURE_PKCS1); 2072 | rsa.loadKey(array("e" => e, "n" => n), CRYPT_RSA_PUBLIC_FORMAT_RAW); 2073 | if (!rsa.verify(this.session_id, signature)) { 2074 | user_error("Bad server signature", E_USER_NOTICE); 2075 | return this._disconnect(NET_SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE); 2076 | } 2077 | */ 2078 | 2079 | temp = unpack("Nlength", this._string_shift(signature, 4)); 2080 | s = new Math_BigInteger(this._string_shift(signature, temp["length"]), 256); 2081 | 2082 | // validate an RSA signature per "8.2 RSASSA-PKCS1-v1_5", "5.2.2 RSAVP1", and "9.1 EMSA-PSS" in the 2083 | // following URL: 2084 | // ftp://ftp.rsasecurity.com/pub/pkcs/pkcs-1/pkcs-1v2-1.pdf 2085 | 2086 | // also, see SSHRSA.c (rsa2_verifysig) in PuTTy"s source. 2087 | 2088 | if (s.compare(new Math_BigInteger()) < 0 || s.compare(n.subtract(new Math_BigInteger(1))) > 0) { 2089 | user_error("Invalid signature", E_USER_NOTICE); 2090 | return this._disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); 2091 | } 2092 | 2093 | s = s.modPow(e, n); 2094 | s = s.toBytes(); 2095 | 2096 | h = pack("N4H*", 0x00302130, 0x0906052B, 0x0E03021A, 0x05000414, sha1(this.session_id)); 2097 | h = chr(0x01) . str_repeat(chr(0xFF), nLength - 3 - strlen(h)) . h; 2098 | 2099 | if (s != h) { 2100 | user_error("Bad server signature", E_USER_NOTICE); 2101 | return this._disconnect(NET_SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE); 2102 | } 2103 | } 2104 | 2105 | return this.server_public_host_key; 2106 | }; 2107 | }).call(ssh2.prototype); 2108 | 2109 | exports.createConnection = function(port, host, callback) { 2110 | var socket = new ssh2(port, host); 2111 | socket.connect(callback); 2112 | return socket; 2113 | }; 2114 | --------------------------------------------------------------------------------