Initial commit
This commit is contained in:
238
server/node_modules/ssh2/examples/server-chat.js
generated
vendored
Normal file
238
server/node_modules/ssh2/examples/server-chat.js
generated
vendored
Normal file
@@ -0,0 +1,238 @@
|
||||
// **BEFORE RUNNING THIS SCRIPT:**
|
||||
// 1. The server portion is best run on non-Windows systems because they have
|
||||
// terminfo databases which are needed to properly work with different
|
||||
// terminal types of client connections
|
||||
// 2. Install `blessed`: `npm install blessed`
|
||||
// 3. Create a server host key in this same directory and name it `host.key`
|
||||
'use strict';
|
||||
|
||||
const { readFileSync } = require('fs');
|
||||
|
||||
const blessed = require('blessed');
|
||||
const { Server } = require('ssh2');
|
||||
|
||||
const RE_SPECIAL =
|
||||
// eslint-disable-next-line no-control-regex
|
||||
/[\x00-\x1F\x7F]+|(?:\x1B\[([0-9]{1,2}(;[0-9]{1,2})?)?[m|K])/g;
|
||||
const MAX_MSG_LEN = 128;
|
||||
const MAX_NAME_LEN = 10;
|
||||
const PROMPT_NAME = `Enter a nickname to use (max ${MAX_NAME_LEN} chars): `;
|
||||
|
||||
const users = [];
|
||||
|
||||
function formatMessage(msg, output) {
|
||||
output.parseTags = true;
|
||||
msg = output._parseTags(msg);
|
||||
output.parseTags = false;
|
||||
return msg;
|
||||
}
|
||||
|
||||
function userBroadcast(msg, source) {
|
||||
const sourceMsg = `> ${msg}`;
|
||||
const name = `{cyan-fg}{bold}${source.name}{/}`;
|
||||
msg = `: ${msg}`;
|
||||
for (const user of users) {
|
||||
const output = user.output;
|
||||
if (source === user)
|
||||
output.add(sourceMsg);
|
||||
else
|
||||
output.add(formatMessage(name, output) + msg);
|
||||
}
|
||||
}
|
||||
|
||||
function localMessage(msg, source) {
|
||||
const output = source.output;
|
||||
output.add(formatMessage(msg, output));
|
||||
}
|
||||
|
||||
function noop(v) {}
|
||||
|
||||
new Server({
|
||||
hostKeys: [readFileSync('host.key')],
|
||||
}, (client) => {
|
||||
let stream;
|
||||
let name;
|
||||
|
||||
client.on('authentication', (ctx) => {
|
||||
let nick = ctx.username;
|
||||
let prompt = PROMPT_NAME;
|
||||
let lowered;
|
||||
|
||||
// Try to use username as nickname
|
||||
if (nick.length > 0 && nick.length <= MAX_NAME_LEN) {
|
||||
lowered = nick.toLowerCase();
|
||||
let ok = true;
|
||||
for (const user of users) {
|
||||
if (user.name.toLowerCase() === lowered) {
|
||||
ok = false;
|
||||
prompt = `That nickname is already in use.\n${PROMPT_NAME}`;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (ok) {
|
||||
name = nick;
|
||||
return ctx.accept();
|
||||
}
|
||||
} else if (nick.length === 0) {
|
||||
prompt = 'A nickname is required.\n' + PROMPT_NAME;
|
||||
} else {
|
||||
prompt = 'That nickname is too long.\n' + PROMPT_NAME;
|
||||
}
|
||||
|
||||
if (ctx.method !== 'keyboard-interactive')
|
||||
return ctx.reject(['keyboard-interactive']);
|
||||
|
||||
ctx.prompt(prompt, function retryPrompt(answers) {
|
||||
if (answers.length === 0)
|
||||
return ctx.reject(['keyboard-interactive']);
|
||||
nick = answers[0];
|
||||
if (nick.length > MAX_NAME_LEN) {
|
||||
return ctx.prompt(`That nickname is too long.\n${PROMPT_NAME}`,
|
||||
retryPrompt);
|
||||
} else if (nick.length === 0) {
|
||||
return ctx.prompt(`A nickname is required.\n${PROMPT_NAME}`,
|
||||
retryPrompt);
|
||||
}
|
||||
lowered = nick.toLowerCase();
|
||||
for (const user of users) {
|
||||
if (user.name.toLowerCase() === lowered) {
|
||||
return ctx.prompt(`That nickname is already in use.\n${PROMPT_NAME}`,
|
||||
retryPrompt);
|
||||
}
|
||||
}
|
||||
name = nick;
|
||||
ctx.accept();
|
||||
});
|
||||
}).on('ready', () => {
|
||||
let rows;
|
||||
let cols;
|
||||
let term;
|
||||
client.once('session', (accept, reject) => {
|
||||
accept().once('pty', (accept, reject, info) => {
|
||||
rows = info.rows;
|
||||
cols = info.cols;
|
||||
term = info.term;
|
||||
accept && accept();
|
||||
}).on('window-change', (accept, reject, info) => {
|
||||
rows = info.rows;
|
||||
cols = info.cols;
|
||||
if (stream) {
|
||||
stream.rows = rows;
|
||||
stream.columns = cols;
|
||||
stream.emit('resize');
|
||||
}
|
||||
accept && accept();
|
||||
}).once('shell', (accept, reject) => {
|
||||
stream = accept();
|
||||
users.push(stream);
|
||||
|
||||
stream.name = name;
|
||||
stream.rows = rows || 24;
|
||||
stream.columns = cols || 80;
|
||||
stream.isTTY = true;
|
||||
stream.setRawMode = noop;
|
||||
stream.on('error', noop);
|
||||
|
||||
const screen = new blessed.screen({
|
||||
autoPadding: true,
|
||||
smartCSR: true,
|
||||
program: new blessed.program({
|
||||
input: stream,
|
||||
output: stream
|
||||
}),
|
||||
terminal: term || 'ansi'
|
||||
});
|
||||
|
||||
screen.title = 'SSH Chatting as ' + name;
|
||||
// Disable local echo
|
||||
screen.program.attr('invisible', true);
|
||||
|
||||
const output = stream.output = new blessed.log({
|
||||
screen: screen,
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: '100%',
|
||||
bottom: 2,
|
||||
scrollOnInput: true
|
||||
});
|
||||
screen.append(output);
|
||||
|
||||
screen.append(new blessed.box({
|
||||
screen: screen,
|
||||
height: 1,
|
||||
bottom: 1,
|
||||
left: 0,
|
||||
width: '100%',
|
||||
type: 'line',
|
||||
ch: '='
|
||||
}));
|
||||
|
||||
const input = new blessed.textbox({
|
||||
screen: screen,
|
||||
bottom: 0,
|
||||
height: 1,
|
||||
width: '100%',
|
||||
inputOnFocus: true
|
||||
});
|
||||
screen.append(input);
|
||||
|
||||
input.focus();
|
||||
|
||||
// Local greetings
|
||||
localMessage('{blue-bg}{white-fg}{bold}Welcome to SSH Chat!{/}\n'
|
||||
+ 'There are {bold}'
|
||||
+ (users.length - 1)
|
||||
+ '{/} other user(s) connected.\n'
|
||||
+ 'Type /quit or /exit to exit the chat.',
|
||||
stream);
|
||||
|
||||
// Let everyone else know that this user just joined
|
||||
for (const user of users) {
|
||||
const output = user.output;
|
||||
if (user === stream)
|
||||
continue;
|
||||
output.add(formatMessage('{green-fg}*** {bold}', output)
|
||||
+ name
|
||||
+ formatMessage('{/bold} has joined the chat{/}', output));
|
||||
}
|
||||
|
||||
screen.render();
|
||||
// XXX This fake resize event is needed for some terminals in order to
|
||||
// have everything display correctly
|
||||
screen.program.emit('resize');
|
||||
|
||||
// Read a line of input from the user
|
||||
input.on('submit', (line) => {
|
||||
input.clearValue();
|
||||
screen.render();
|
||||
if (!input.focused)
|
||||
input.focus();
|
||||
line = line.replace(RE_SPECIAL, '').trim();
|
||||
if (line.length > MAX_MSG_LEN)
|
||||
line = line.substring(0, MAX_MSG_LEN);
|
||||
if (line.length > 0) {
|
||||
if (line === '/quit' || line === '/exit')
|
||||
stream.end();
|
||||
else
|
||||
userBroadcast(line, stream);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}).on('close', () => {
|
||||
if (stream !== undefined) {
|
||||
users.splice(users.indexOf(stream), 1);
|
||||
// Let everyone else know that this user just left
|
||||
for (const user of users) {
|
||||
const output = user.output;
|
||||
output.add(formatMessage('{magenta-fg}*** {bold}', output)
|
||||
+ name
|
||||
+ formatMessage('{/bold} has left the chat{/}', output));
|
||||
}
|
||||
}
|
||||
}).on('error', (err) => {
|
||||
// Ignore errors
|
||||
});
|
||||
}).listen(0, function() {
|
||||
console.log('Listening on port ' + this.address().port);
|
||||
});
|
||||
134
server/node_modules/ssh2/examples/sftp-server-download-only.js
generated
vendored
Normal file
134
server/node_modules/ssh2/examples/sftp-server-download-only.js
generated
vendored
Normal file
@@ -0,0 +1,134 @@
|
||||
'use strict';
|
||||
|
||||
const { timingSafeEqual } = require('crypto');
|
||||
const { constants, readFileSync } = require('fs');
|
||||
|
||||
const { Server, sftp: { OPEN_MODE, STATUS_CODE } } = require('ssh2');
|
||||
|
||||
const allowedUser = Buffer.from('foo');
|
||||
const allowedPassword = Buffer.from('bar');
|
||||
|
||||
function checkValue(input, allowed) {
|
||||
const autoReject = (input.length !== allowed.length);
|
||||
if (autoReject) {
|
||||
// Prevent leaking length information by always making a comparison with the
|
||||
// same input when lengths don't match what we expect ...
|
||||
allowed = input;
|
||||
}
|
||||
const isMatch = timingSafeEqual(input, allowed);
|
||||
return (!autoReject && isMatch);
|
||||
}
|
||||
|
||||
new Server({
|
||||
hostKeys: [readFileSync('host.key')]
|
||||
}, (client) => {
|
||||
console.log('Client connected!');
|
||||
|
||||
client.on('authentication', (ctx) => {
|
||||
let allowed = true;
|
||||
if (!checkValue(Buffer.from(ctx.username), allowedUser))
|
||||
allowed = false;
|
||||
|
||||
switch (ctx.method) {
|
||||
case 'password':
|
||||
if (!checkValue(Buffer.from(ctx.password), allowedPassword))
|
||||
return ctx.reject();
|
||||
break;
|
||||
default:
|
||||
return ctx.reject();
|
||||
}
|
||||
|
||||
if (allowed)
|
||||
ctx.accept();
|
||||
else
|
||||
ctx.reject();
|
||||
}).on('ready', () => {
|
||||
console.log('Client authenticated!');
|
||||
|
||||
client.on('session', (accept, reject) => {
|
||||
const session = accept();
|
||||
session.on('sftp', (accept, reject) => {
|
||||
console.log('Client SFTP session');
|
||||
|
||||
const openFiles = new Map();
|
||||
let handleCount = 0;
|
||||
const sftp = accept();
|
||||
sftp.on('OPEN', (reqid, filename, flags, attrs) => {
|
||||
// Only allow opening /tmp/foo.txt for writing
|
||||
if (filename !== '/tmp/foo.txt' || !(flags & OPEN_MODE.READ))
|
||||
return sftp.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
|
||||
const handle = Buffer.alloc(4);
|
||||
openFiles.set(handleCount, { read: false });
|
||||
handle.writeUInt32BE(handleCount++, 0, true);
|
||||
|
||||
console.log('Opening file for read');
|
||||
sftp.handle(reqid, handle);
|
||||
}).on('READ', (reqid, handle, offset, length) => {
|
||||
let fnum;
|
||||
if (handle.length !== 4
|
||||
|| !openFiles.has(fnum = handle.readUInt32BE(0, true))) {
|
||||
return sftp.status(reqid, STATUS_CODE.FAILURE);
|
||||
}
|
||||
|
||||
// Fake the read
|
||||
const state = openFiles.get(fnum);
|
||||
if (state.read) {
|
||||
sftp.status(reqid, STATUS_CODE.EOF);
|
||||
} else {
|
||||
state.read = true;
|
||||
|
||||
console.log(
|
||||
'Read from file at offset %d, length %d', offset, length
|
||||
);
|
||||
sftp.data(reqid, 'bar');
|
||||
}
|
||||
}).on('CLOSE', (reqid, handle) => {
|
||||
let fnum;
|
||||
if (handle.length !== 4
|
||||
|| !openFiles.has(fnum = handle.readUInt32BE(0))) {
|
||||
return sftp.status(reqid, STATUS_CODE.FAILURE);
|
||||
}
|
||||
|
||||
openFiles.delete(fnum);
|
||||
|
||||
console.log('Closing file');
|
||||
sftp.status(reqid, STATUS_CODE.OK);
|
||||
}).on('REALPATH', function(reqid, path) {
|
||||
const name = [{
|
||||
filename: '/tmp/foo.txt',
|
||||
longname: '-rwxrwxrwx 1 foo foo 3 Dec 8 2009 foo.txt',
|
||||
attrs: {}
|
||||
}];
|
||||
sftp.name(reqid, name);
|
||||
}).on('STAT', onSTAT)
|
||||
.on('LSTAT', onSTAT);
|
||||
|
||||
function onSTAT(reqid, path) {
|
||||
if (path !== '/tmp/foo.txt')
|
||||
return sftp.status(reqid, STATUS_CODE.FAILURE);
|
||||
|
||||
let mode = constants.S_IFREG; // Regular file
|
||||
mode |= constants.S_IRWXU; // Read, write, execute for user
|
||||
mode |= constants.S_IRWXG; // Read, write, execute for group
|
||||
mode |= constants.S_IRWXO; // Read, write, execute for other
|
||||
sftp.attrs(reqid, {
|
||||
mode: mode,
|
||||
uid: 0,
|
||||
gid: 0,
|
||||
size: 3,
|
||||
atime: Date.now(),
|
||||
mtime: Date.now(),
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}).on('close', () => {
|
||||
console.log('Client disconnected');
|
||||
});
|
||||
}).listen(0, '127.0.0.1', function() {
|
||||
console.log(`Listening on port ${this.address().port}`);
|
||||
});
|
||||
Reference in New Issue
Block a user