This commit is contained in:
tkQubo
2015-08-30 01:30:56 +09:00
parent 0a51ae619b
commit 2442ad1ae6
2 changed files with 774 additions and 0 deletions
+428
View File
@@ -0,0 +1,428 @@
/// <reference path="ssh2.d.ts" />
import * as ssh2 from 'ssh2';
declare var inspect: any;
//
// # Client Examples
//
// Authenticate using keys and execute uptime on a server:
var Client = require('ssh2').Client;
var conn = new Client();
conn.on('ready', () => {
console.log('Client :: ready');
conn.exec('uptime', (err: Error, stream: ssh2.Channel) => {
if (err) throw err;
stream
.on('close', (code: any, signal: any) => {
console.log('Stream :: close :: code: ' + code + ', signal: ' + signal);
conn.end();
}).on('data', (data: any) => {
console.log('STDOUT: ' + data);
}).stderr.on('data', (data: any) => {
console.log('STDERR: ' + data);
});
});
}).connect({
host: '192.168.100.100',
port: 22,
username: 'frylock',
privateKey: require('fs').readFileSync('/here/is/my/key')
});
// Authenticate using keys and start an interactive shell session:
var Client = require('ssh2').Client;
var conn = new Client();
conn.on('ready', () => {
console.log('Client :: ready');
conn.shell( (err: Error, stream: ssh2.Channel) => {
if (err) throw err;
stream.on('close', () => {
console.log('Stream :: close');
conn.end();
}).on('data', (data: any) => {
console.log('STDOUT: ' + data);
}).stderr.on('data', (data: any) => {
console.log('STDERR: ' + data);
});
stream.end('ls -l\nexit\n');
});
}).connect({
host: '192.168.100.100',
port: 22,
username: 'frylock',
privateKey: require('fs').readFileSync('/here/is/my/key')
});
// Authenticate using password and send an HTTP request to port 80 on the server:
var Client = require('ssh2').Client;
var conn = new Client();
conn.on('ready', () => {
console.log('Client :: ready');
conn.forwardOut('192.168.100.102', 8000, '127.0.0.1', 80, (err: Error, stream: ssh2.Channel) => {
if (err) throw err;
stream.on('close', () => {
console.log('TCP :: CLOSED');
conn.end();
}).on('data', (data: any) => {
console.log('TCP :: DATA: ' + data);
}).end([
'HEAD / HTTP/1.1',
'User-Agent: curl/7.27.0',
'Host: 127.0.0.1',
'Accept: */*',
'Connection: close',
'',
''
].join('\r\n'));
});
}).connect({
host: '192.168.100.100',
port: 22,
username: 'frylock',
password: 'nodejsrules'
});
// Authenticate using password and forward remote connections on port 8000 to us:
var Client = require('ssh2').Client;
var conn = new Client();
conn.on('ready', () => {
console.log('Client :: ready');
conn.forwardIn('127.0.0.1', 8000, (err: Error) => {
if (err) throw err;
console.log('Listening for connections on server on port 8000!');
});
}).on('tcp connection', (info: any, accept: Function, reject: Function) => {
console.log('TCP :: INCOMING CONNECTION:');
console.dir(info);
accept().on('close', () => {
console.log('TCP :: CLOSED');
}).on('data', (data: any) => {
console.log('TCP :: DATA: ' + data);
}).end([
'HTTP/1.1 404 Not Found',
'Date: Thu, 15 Nov 2012 02:07:58 GMT',
'Server: ForwardedConnection',
'Content-Length: 0',
'Connection: close',
'',
''
].join('\r\n'));
}).connect({
host: '192.168.100.100',
port: 22,
username: 'frylock',
password: 'nodejsrules'
});
// Authenticate using password and get a directory listing via SFTP:
var Client = require('ssh2').Client;
var conn = new Client();
conn.on('ready', () => {
console.log('Client :: ready');
conn.sftp( (err: Error, sftp: any) => {
if (err) throw err;
sftp.readdir('foo', (err: Error, list: any) => {
if (err) throw err;
console.dir(list);
conn.end();
});
});
}).connect({
host: '192.168.100.100',
port: 22,
username: 'frylock',
password: 'nodejsrules'
});
// Connection hopping:
var Client = require('ssh2').Client;
var conn1 = new Client(),
conn2 = new Client();
conn1.on('ready', () => {
console.log('FIRST :: connection ready');
conn1.exec('nc 192.168.1.2 22', (err: Error, stream: ssh2.Channel) => {
if (err) {
console.log('FIRST :: exec error: ' + err);
return conn1.end();
}
conn2.connect({
sock: stream,
username: 'user2',
password: 'password2',
});
});
}).connect({
host: '192.168.1.1',
username: 'user1',
password: 'password1',
});
conn2.on('ready', () => {
console.log('SECOND :: connection ready');
conn2.exec('uptime', (err: Error, stream: ssh2.Channel) => {
if (err) {
console.log('SECOND :: exec error: ' + err);
return conn1.end();
}
stream.on('end', () => {
conn1.end(); // close parent (and this) connection
}).on('data', (data: any) => {
console.log(data.toString());
});
});
});
// Forward X11 connections (xeyes):
var net = require('net'),
Client = require('ssh2').Client;
var conn = new Client();
conn.on('x11', (info: any, accept: any, reject: any) => {
var xserversock = new net.Socket();
xserversock.on('connect', () => {
var xclientsock = accept();
xclientsock.pipe(xserversock).pipe(xclientsock);
});
// connects to localhost:0.0
xserversock.connect(6000, 'localhost');
});
conn.on('ready', () => {
conn.exec('xeyes', { x11: true }, (err: Error, stream: ssh2.Channel) => {
if (err) throw err;
var code = 0;
stream.on('end', () => {
if (code !== 0)
console.log('Do you have X11 forwarding enabled on your SSH server?');
conn.end();
}).on('exit', (exitcode: number) => {
code = exitcode;
});
});
}).connect({
host: '192.168.1.1',
username: 'foo',
password: 'bar'
});
// Dynamic (1:1) port forwarding using a SOCKSv5 proxy (using socksv5):
var socks = require('socksv5'),
Client = require('ssh2').Client;
var ssh_config = {
host: '192.168.100.1',
port: 22,
username: 'nodejs',
password: 'rules'
};
socks.createServer( (info: any, accept: any, deny: any) => {
// NOTE: you could just use one ssh2 client connection for all forwards, but
// you could run into server-imposed limits if you have too many forwards open
// at any given time
var conn = new Client();
conn.on('ready', () => {
conn.forwardOut(info.srcAddr,
info.srcPort,
info.dstAddr,
info.dstPort,
(err: Error, stream: ssh2.Channel) => {
if (err) {
conn.end();
return deny();
}
var clientSocket: any;
if (clientSocket = accept(true)) {
stream.pipe(clientSocket).pipe(stream).on('close', () => {
conn.end();
});
} else
conn.end();
});
}).on('error', (err: Error) => {
deny();
}).connect(ssh_config);
}).listen(1080, 'localhost', () => {
console.log('SOCKSv5 proxy server started on port 1080');
}).useAuth(socks.auth.None());
// Invoke an arbitrary subsystem (netconf in this example):
var Client = require('ssh2').Client,
xmlhello = '<?xml version="1.0" encoding="UTF-8"?>'+
'<hello xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">'+
' <capabilities>'+
' <capability>urn:ietf:params:netconf:base:1.0</capability>'+
' </capabilities>'+
'</hello>]]>]]>';
var conn = new Client();
conn.on('ready', () => {
console.log('Client :: ready');
conn.subsys('netconf', (err: Error, stream: ssh2.Channel) => {
if (err) throw err;
stream.on('data', (data: any) => {
console.log(data);
}).write(xmlhello);
});
}).connect({
host: '1.2.3.4',
port: 22,
username: 'blargh',
password: 'honk'
});
//
// # Server Examples
//
// Only allow password and public key authentication and command execution:
var fs = require('fs'),
crypto = require('crypto');
var buffersEqual = require('buffer-equal-constant-time'),
//ssh2 = require('ssh2'),
utils = ssh2.utils;
var pubKey = utils.genPublicKey(utils.parseKey(fs.readFileSync('user.pub')));
new ssh2.Server({
privateKey: fs.readFileSync('host.key')
}, (client: any) => {
console.log('Client connected!');
client.on('authentication', (ctx: any) => {
if (ctx.method === 'password'
&& ctx.username === 'foo'
&& ctx.password === 'bar')
ctx.accept();
else if (ctx.method === 'publickey'
&& ctx.key.algo === pubKey.fulltype
&& buffersEqual(ctx.key.data, pubKey.public)) {
if (ctx.signature) {
var verifier = crypto.createVerify(ctx.sigAlgo);
verifier.update(ctx.blob);
if (verifier.verify(pubKey.publicOrig, ctx.signature, 'binary'))
ctx.accept();
else
ctx.reject();
} else {
// if no signature present, that means the client is just checking
// the validity of the given public key
ctx.accept();
}
} else
ctx.reject();
}).on('ready', () => {
console.log('Client authenticated!');
client.on('session', (accept: any, reject: any) => {
var session = accept();
session.once('exec', (accept: any, reject: any, info: any) => {
console.log('Client wants to execute: ' + inspect(info.command));
var stream = accept();
stream.stderr.write('Oh no, the dreaded errors!\n');
stream.write('Just kidding about the errors!\n');
stream.exit(0);
stream.end();
});
});
}).on('end', () => {
console.log('Client disconnected');
});
}).listen(0, '127.0.0.1', () => {
console.log('Listening on port ' + this.address().port);
});
// SFTP only server:
var fs = require('fs');
//var ssh2 = require('ssh2');
var OPEN_MODE = ssh2.SFTP_OPEN_MODE,
STATUS_CODE = ssh2.SFTP_STATUS_CODE;
new ssh2.Server({
privateKey: fs.readFileSync('host.key')
}, (client: any) => {
console.log('Client connected!');
client.on('authentication', (ctx: any) => {
if (ctx.method === 'password'
&& ctx.username === 'foo'
&& ctx.password === 'bar')
ctx.accept();
else
ctx.reject();
}).on('ready', () => {
console.log('Client authenticated!');
client.on('session', (accept: any, reject: any) => {
var session = accept();
session.on('sftp', (accept: any, reject: any) => {
console.log('Client SFTP session');
var openFiles: any = {};
var handleCount = 0;
// `sftpStream` is an `SFTPStream` instance in server mode
// see: https://github.com/mscdex/ssh2-streams/blob/master/SFTPStream.md
var sftpStream = accept();
sftpStream.on('OPEN', (reqid: any, filename: any, flags: any, attrs: any) => {
// only allow opening /tmp/foo.txt for writing
if (filename !== '/tmp/foo.txt' || !(flags & OPEN_MODE.WRITE))
return sftpStream.status(reqid, STATUS_CODE.FAILURE);
// create a fake handle to return to the client, this could easily
// be a real file descriptor number for example if actually opening
// the file on the disk
var handle = new Buffer(4);
openFiles[handleCount] = true;
handle.writeUInt32BE(handleCount++, 0, true);
sftpStream.handle(reqid, handle);
console.log('Opening file for write')
}).on('WRITE', (reqid: any, handle: any, offset: any, data: any) => {
if (handle.length !== 4 || !openFiles[handle.readUInt32BE(0, true)])
return sftpStream.status(reqid, STATUS_CODE.FAILURE);
// fake the write
sftpStream.status(reqid, STATUS_CODE.OK);
var inspected = require('util').inspect(data);
console.log('Write to file at offset %d: %s', offset, inspected);
}).on('CLOSE', (reqid: any, handle: any) => {
var fnum: any;
if (handle.length !== 4 || !openFiles[(fnum = handle.readUInt32BE(0, true))])
return sftpStream.status(reqid, STATUS_CODE.FAILURE);
delete openFiles[fnum];
sftpStream.status(reqid, STATUS_CODE.OK);
console.log('Closing file');
});
});
});
}).on('end', () => {
console.log('Client disconnected');
});
}).listen(0, '127.0.0.1', () => {
console.log('Listening on port ' + this.address().port);
});
+346
View File
@@ -0,0 +1,346 @@
// Type definitions for ssh2
// Project: https://github.com/mscdex/ssh2
// Definitions by: Qubo <https://github.com/tkQubo>
// Definitions: https://github.com/borisyankov/DefinitelyTyped
/// <reference path="../node/node.d.ts" />
declare module "ssh2" {
import stream = require('stream');
namespace ssh2 {
interface Client {
Server: ServerStatic;
new(): Client;
/**
* SFTPStream.STATUS_CODE from ssh2-streams.
*/
SFTP_STATUS_CODE: Sftp.StatusCode;
/**
* SFTPStream.OPEN_MODE from ssh2-streams.
*/
SFTP_OPEN_MODE: Sftp.OpenMode;
//TODO: Type ssh2-streams
/**
* utility methods from ssh2-streams.
*/
utils: Utils;
connect(config: ConnectConfig): void;
end(): void;
destroy(): void;
exec(command: string, options: ExecOption, callback: ChannelCallback): boolean;
exec(command: string, callback: ChannelCallback): boolean;
shell(window: boolean|PseudoTtySettings, options: boolean|number|X11Settings, callback: ChannelCallback): boolean;
shell(window: boolean|PseudoTtySettings, callback: ChannelCallback): boolean;
shell(callback: ChannelCallback): boolean;
subsys(subsystem: string, callback: ChannelCallback): boolean;
sftp(callback: SftpCallback): boolean;
forwardIn(bindAddr: string, bindPort: number, callback?: ForwardInCallback): boolean;
unforwardIn(bindAddr: string, bindPort: number, callback?: ErrorCallback): boolean;
forwardOut(srcIP: string, srcPort: number, dstIP: string, dstPort: number, callback: ChannelCallback): boolean;
openssh_noMoreSessions(callback?: ErrorCallback): boolean;
openssh_forwardInStreamLocal(socketPath: string, callback?: ErrorCallback): boolean;
openssh_unforwardInStreamLocal(socketPath: string, callback?: ErrorCallback): boolean;
openssh_forwardOutStreamLocal(socketPath: string, callback?: ChannelCallback): boolean;
}
interface ConnectConfig {
/**
* @description Hostname or IP address of the server.
* @default 'localhost'
*/
host?: string;
/**
* @description Port number of the server.
* @default 22
*/
port?: number;
/**
* @description Only connect via resolved IPv4 address for `host`.
* @default false
*/
forceIPv4?: boolean;
/**
* @description Only connect via resolved IPv6 address for `host`.
* @default false
*/
forceIPv6?: boolean;
/**
* @description 'md5' or 'sha1'. The host's key is hashed using this method and passed to the **hostVerifier** function.
* @default (none)
*/
hostHash?: string;
/**
* @description Function that is passed a string hex hash of the host's key for verification purposes.
* Return `true` to continue with the handshake or `false` to reject and disconnect.
*/
hostVerifier?: (keyHash: string) => boolean;
/**
* @description Username for authentication.
*/
username?: string;
/**
* @description Password for password-based user authentication.
*/
password?: string;
/**
* @description Path to ssh-agent's UNIX socket for ssh-agent-based user authentication.
* Windows users: set to 'pageant' for authenticating with Pageant or (actual) path to a cygwin "UNIX socket."
*/
agent?: string;
/**
* @description Buffer or string that contains a private key for either key-based or hostbased user authentication (OpenSSH format).
*/
privateKey?: Buffer|string;
/**
* @description For an encrypted private key, this is the passphrase used to decrypt it.
*/
passphrase?: string;
/**
* @description Along with **localUsername** and **privateKey**, set this to a non-empty string for hostbased user authentication.
*/
localHostname?: string;
/**
* @description Along with **localHostname** and **privateKey**, set this to a non-empty string for hostbased user authentication.
*/
localUsername?: string;
/**
* @description Try keyboard-interactive user authentication if primary user authentication method fails.
* If you set this to `true`, you need to handle the `keyboard-interactive` event.
*/
tryKeyboard: boolean;
/**
* @description How often (in milliseconds) to send SSH-level keepalive packets to the server
* (in a similar way as OpenSSH's ServerAliveInterval config option). Set to 0 to disable.
* @default 0
*/
keepaliveInterval?: number;
/**
* @description How many consecutive, unanswered SSH-level keepalive packets that can be sent to the server
* before disconnection (similar to OpenSSH's ServerAliveCountMax config option).
* @default 3
*/
keepaliveCountMax?: number;
/**
* @description How long (in milliseconds) to wait for the SSH handshake to complete.
* @default 20000
*/
readyTimeout?: number;
/**
* @description Performs a strict server vendor check before sending vendor-specific requests,
* etc. (e.g. check for OpenSSH server when using `openssh_noMoreSessions()`)
* @default true
*/
strictVendor?: boolean;
/**
* A ReadableStream to use for communicating with the server instead of creating and using a new TCP connection (useful for connection hopping).
*/
sock?: NodeJS.ReadableStream;
/**
* @description Set to `true` to use OpenSSH agent forwarding (`auth-agent@openssh.com`) for the life of the connection. `agent` must also be set to use this feature.
* @default false
*/
agentForward?: boolean;
/**
* @description Set this to a function that receives a single string argument to get detailed (local) debug information.
*/
debug: (information: string) => any;
}
interface ExecOption {
/**
* @description An environment to use for the execution of the command.
*/
env?: any;
/**
* @description Set to true to allocate a pseudo-tty with defaults, or an object containing specific pseudo-tty settings
* (see 'Pseudo-TTY settings'). Setting up a pseudo-tty can be useful when working with remote processes
* that expect input from an actual terminal (e.g. sudo's password prompt).
*/
pty?: boolean|PseudoTtySettings;
/**
* @description Set to true to use defaults below, set to a number to specify a specific screen number,
* or an object.
*/
x11?: boolean|number|X11Settings;
}
interface X11Settings {
/**
* Allow just a single connection?
* @default false
*/
single?: boolean;
/**
* Screen number to use
* @default 0
*/
screen?: number;
}
interface PseudoTtySettings {
/**
* @description * Number of rows
* @default 24
*/
rows?: number;
/**
* @description * Number of columns
* @default 80
*/
cols?: number;
/**
* @description * Height in pixels
* @default 480
*/
height?: number;
/**
* @description * Width in pixels
* @default 640
*/
width?: number;
/**
* @description The value to use for $TERM
* @default 'vt100'
*/
term?: string;
}
interface ForwardInCallback {
(err?: Error, bindPort?: number): void;
}
interface ChannelCallback {
(err?: Error, channel?: Channel): void;
}
interface SftpCallback {
(err?: Error, sftp?: Sftp.Wrapper): void;
}
interface ErrorCallback {
(err?: Error): void;
}
interface Channel extends stream.Duplex {
new(info?: any, client?: any, options?: any): Channel;
eof(): boolean;
close(): boolean;
destroy(): void;
setWindow(rows: number, cols: number, height: number, width: number): boolean;
signal(signalName: string): boolean;
exit(name: string, coreDumped: boolean, msg: string): boolean;
exit(status: number): boolean;
stderr?: ServerStderr;
// EventEmitter overrides
addListener(event: string, listener: Function): Channel;
on(event: string, listener: Function): Channel;
once(event: string, listener: Function): Channel;
removeListener(event: string, listener: Function): Channel;
removeAllListeners(event?: string): Channel;
}
interface ServerStderr extends NodeJS.WritableStream {
new(channel: Channel): ServerStderr;
}
interface ServerStatic {
new(config: ServerConfig, listener?: any): Server;
createServer(config: ServerConfig, listener?: any): Server;
KEEPALIVE_INTERVAL: number;
KEEPALIVE_CLIENT_INTERVAL: number;
KEEPALIVE_CLIENT_COUNT_MAX: number;
}
interface Server extends NodeJS.EventEmitter {
listen(port: number, hostname?: string, backlog?: number, callback?: Function): Server;
listen(port: number, hostname?: string, callback?: Function): Server;
listen(path: string, callback?: Function): Server;
listen(handle: any, listeningListener?: Function): Server;
address(): { port: number; family: string; address: string; };
getConnections(callback: any): any; //TODO: No type
close(callback?: Function): Server;
ref(): void;
unref(): void;
}
interface ServerConfig {
/**
* @description Buffer or string that contains the host private key (OpenSSH format).
*/
privateKey: Buffer|string;
/**
* @description For an encrypted host private key, this is the passphrase used to decrypt it.
*/
passphrase?: string;
/**
* @description A message that is sent to clients immediately upon connection, before handshaking begins.
*/
banner?: string;
/**
* @description A custom server software name/version identifier.
* @default 'ssh2js' + moduleVersion + 'srv'
*/
indent?: string;
/**
* @description This is the highWaterMark to use for the parser stream.
* @default 32 * 1024
*/
highWaterMark?: number;
/**
* @description Set this to a function that receives a single string argument to get detailed (local) debug information.
*/
debug?: (information: string) => any;
}
// utility methods from ssh2-streams.
interface Utils {
iv_inc: Function;
isStreamCipher: Function;
isGCM: Function;
readInt: Function;
readString: Function;
parseKey: Function;
genPublicKey: Function;
convertPPKPrivate: Function;
verifyPPKMAC: Function;
decryptKey: Function;
}
namespace Sftp {
// SFTPStream.STATUS_CODE from ssh2-streams.
interface StatusCode {
OK: number;
EOF: number;
NO_SUCH_FILE: number;
PERMISSION_DENIED: number;
FAILURE: number;
BAD_MESSAGE: number;
NO_CONNECTION: number;
CONNECTION_LOST: number;
OP_UNSUPPORTED: number;
}
// SFTPStream.OPEN_MODE from ssh2-streams.
interface OpenMode {
READ: number;
WRITE: number;
APPEND: number;
CREAT: number;
TRUNC: number;
EXCL: number;
}
interface Wrapper extends NodeJS.EventEmitter {
//TODO: extends `ssh2-streams.SFTPStream`
}
}
}
var ssh2: ssh2.Client;
export = ssh2;
}