chore: initial bifurcation of client and server code
21
.bithoundrc
|
@ -1,21 +0,0 @@
|
|||
{
|
||||
"critics": {
|
||||
"lint": {
|
||||
"engine": "standard"
|
||||
},
|
||||
"wc": {
|
||||
"limit": 5000
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"mute": [
|
||||
"read-config",
|
||||
"socket.io",
|
||||
"standard",
|
||||
"bithound"
|
||||
]
|
||||
},
|
||||
"ignore": [
|
||||
"public/webssh2.bundle.js",
|
||||
]
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
---
|
||||
engines:
|
||||
csslint:
|
||||
enabled: true
|
||||
exclude_paths:
|
||||
- "client/public/*"
|
||||
duplication:
|
||||
exclude_paths:
|
||||
- "client/public/*"
|
||||
- "workspace/*"
|
||||
enabled: true
|
||||
config:
|
||||
languages:
|
||||
- ruby
|
||||
- javascript
|
||||
- python
|
||||
- php
|
||||
eslint:
|
||||
enabled: true
|
||||
fixme:
|
||||
enabled: true
|
||||
|
||||
ratings:
|
||||
paths:
|
||||
- "**.css"
|
||||
- "**.inc"
|
||||
- "**.js"
|
||||
- "**.jsx"
|
||||
- "**.module"
|
||||
- "**.php"
|
||||
- "**.py"
|
||||
- "**.rb"
|
||||
exclude_paths:
|
||||
- "node_modules/"
|
||||
- "client/public/*"
|
||||
- "workspace/*"
|
|
@ -1,2 +0,0 @@
|
|||
--exclude-exts=.min.css
|
||||
--ignore=adjoining-classes,box-model,ids,order-alphabetical,unqualified-attributes
|
4
.snyk
|
@ -1,4 +0,0 @@
|
|||
# Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities.
|
||||
version: v1.13.1
|
||||
ignore: {}
|
||||
patch: {}
|
|
@ -1,4 +0,0 @@
|
|||
language: node_js
|
||||
node_js:
|
||||
- 6
|
||||
- 10
|
|
@ -1 +0,0 @@
|
|||
e2e70f7d2949b6c8fe0299f888a3725763a62c01a1faea1fb729babc2ed51c92 Build/Release/BIG-IP-ILX-WebSSH2-0.2.8.tgz
|
|
@ -1,46 +0,0 @@
|
|||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at wmchurch@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
|
||||
|
||||
[homepage]: http://contributor-covenant.org
|
||||
[version]: http://contributor-covenant.org/version/1/4/
|
|
@ -1,94 +0,0 @@
|
|||
Guidelines for contributing code:
|
||||
|
||||
Make sure code passes linting from (StandardJS)[https://standardjs.com/]
|
||||
|
||||
|
||||
# Contributing
|
||||
|
||||
When contributing to this repository, please first discuss the change you wish to make via issue,
|
||||
email, or any other method with the owners of this repository before making a change.
|
||||
|
||||
Please note we have a code of conduct, please follow it in all your interactions with the project.
|
||||
|
||||
## Pull Request Process
|
||||
|
||||
1. Ensure any install or build dependencies are removed before the end of the layer when doing a
|
||||
build.
|
||||
2. Make sure code passes linting from (StandardJS)[https://standardjs.com/]
|
||||
3. Explain what you're trying to accomplish in your commits
|
||||
4. Update changelog and Readme if needed.
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
### Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as
|
||||
contributors and maintainers pledge to making participation in our project and
|
||||
our community a harassment-free experience for everyone, regardless of age, body
|
||||
size, disability, ethnicity, gender identity and expression, level of experience,
|
||||
nationality, personal appearance, race, religion, or sexual identity and
|
||||
orientation.
|
||||
|
||||
### Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment
|
||||
include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||
advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic
|
||||
address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
### Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable
|
||||
behavior and are expected to take appropriate and fair corrective action in
|
||||
response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or
|
||||
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||
permanently any contributor for other behaviors that they deem inappropriate,
|
||||
threatening, offensive, or harmful.
|
||||
|
||||
### Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces
|
||||
when an individual is representing the project or its community. Examples of
|
||||
representing a project or community include using an official project e-mail
|
||||
address, posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event. Representation of a project may be
|
||||
further defined and clarified by project maintainers.
|
||||
|
||||
### Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by contacting the project team at [INSERT EMAIL ADDRESS]. All
|
||||
complaints will be reviewed and investigated and will result in a response that
|
||||
is deemed necessary and appropriate to the circumstances. The project team is
|
||||
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||
Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||
faith may face temporary or permanent repercussions as determined by other
|
||||
members of the project's leadership.
|
||||
|
||||
### Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||
available at [http://contributor-covenant.org/version/1/4][version]
|
||||
|
||||
[homepage]: http://contributor-covenant.org
|
||||
[version]: http://contributor-covenant.org/version/1/4/
|
|
@ -1,7 +0,0 @@
|
|||
FROM node:8.6
|
||||
|
||||
WORKDIR /usr/src
|
||||
COPY app/ /usr/src/
|
||||
RUN npm install --production
|
||||
EXPOSE 2222
|
||||
CMD npm run start
|
|
@ -1,15 +0,0 @@
|
|||
Depending on the type of issue, please include the follwing information:
|
||||
- Node and NPM Version
|
||||
- `node -v`
|
||||
- `npm -v`
|
||||
- Server OS Version / Distribution / Processor Architecture
|
||||
- `uname -a`
|
||||
- `cat /etc/os-release`
|
||||
- WebSSH2 release version
|
||||
- `grep version app/package.json`
|
||||
- OS and Version of SSH server connecting to
|
||||
- `uname -a`
|
||||
- `sshd -v`
|
||||
- Browser Version and OS
|
||||
- Information from brwoser's About... or a screenshot of the about screen.
|
||||
- Any log or messages from the WebSSH2 output
|
15
Jenkinsfile
vendored
|
@ -1,15 +0,0 @@
|
|||
pipeline {
|
||||
agent {
|
||||
docker {
|
||||
image 'node:6-alpine'
|
||||
args '-p 3000:3000'
|
||||
}
|
||||
}
|
||||
stages {
|
||||
stage('Build') {
|
||||
steps {
|
||||
sh 'npm install'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
90
app/app.js
Normal file
|
@ -0,0 +1,90 @@
|
|||
// app/app.js
|
||||
"use strict";
|
||||
/* jshint esversion: 6, asi: true, node: true */
|
||||
|
||||
const path = require("path");
|
||||
const express = require("express");
|
||||
const compression = require("compression");
|
||||
const session = require("express-session");
|
||||
const logger = require("morgan");
|
||||
const socketIo = require("socket.io");
|
||||
const myutil = require("./util");
|
||||
const config = require("./config");
|
||||
const socketHandler = require("./socket");
|
||||
|
||||
const app = express();
|
||||
const server = require("http").Server(app);
|
||||
|
||||
// Session middleware
|
||||
const sessionMiddleware = session({
|
||||
secret: config.session.secret,
|
||||
name: config.session.name,
|
||||
resave: false,
|
||||
saveUninitialized: true,
|
||||
cookie: {
|
||||
secure: process.env.NODE_ENV === "production",
|
||||
maxAge: 24 * 60 * 60 * 1000,
|
||||
},
|
||||
});
|
||||
|
||||
// Express middleware
|
||||
app.use(compression({ level: 9 }));
|
||||
app.use(sessionMiddleware);
|
||||
if (config.accesslog) app.use(logger("common"));
|
||||
app.disable("x-powered-by");
|
||||
|
||||
// Socket.IO setup
|
||||
const io = socketIo(server, {
|
||||
path: "/ssh/socket.io",
|
||||
cors: {
|
||||
origin: "http://localhost:8080",
|
||||
methods: ["GET", "POST"],
|
||||
credentials: true,
|
||||
},
|
||||
});
|
||||
|
||||
// Socket.io middleware
|
||||
io.use((socket, next) => {
|
||||
sessionMiddleware(socket.request, socket.request.res || {}, next);
|
||||
});
|
||||
|
||||
// WebSocket handling
|
||||
io.on("connection", (socket) => {
|
||||
console.log(
|
||||
"New connection:",
|
||||
socket.id,
|
||||
"Transport:",
|
||||
socket.conn.transport.name
|
||||
);
|
||||
|
||||
// Call the imported socket handler function only once per connection
|
||||
if (!socket.handled) {
|
||||
socketHandler(io, socket);
|
||||
socket.handled = true;
|
||||
}
|
||||
|
||||
socket.on("disconnect", (reason) => {
|
||||
console.log("Client disconnected:", socket.id, reason);
|
||||
});
|
||||
});
|
||||
|
||||
// Error handling
|
||||
app.use((req, res, next) => {
|
||||
res.status(404).send("Sorry can't find that!");
|
||||
});
|
||||
|
||||
app.use((err, req, res, next) => {
|
||||
console.error(err.stack);
|
||||
res.status(500).send("Something broke!");
|
||||
});
|
||||
|
||||
// Graceful shutdown
|
||||
process.on("SIGINT", () => {
|
||||
console.log("SIGINT signal received: closing HTTP server");
|
||||
server.close(() => {
|
||||
console.log("HTTP server closed");
|
||||
process.exit(0);
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = { server, config, io };
|
|
@ -1,32 +0,0 @@
|
|||
<!-- Version Version 0.2.12 - 2024-07-10T13:49:24.751Z - 533f719 -->
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>WebSSH2</title>
|
||||
<style>
|
||||
html,
|
||||
body {
|
||||
background-color: #000;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
.dropup-content {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
<link href="/ssh/webssh2.css" rel="stylesheet"></head>
|
||||
<body>
|
||||
<div class="box">
|
||||
<div id="header"></div>
|
||||
<div id="terminal-container" class="terminal"></div>
|
||||
<div id="bottomdiv">
|
||||
<div class="dropup" id="menu">
|
||||
<i class="fas fa-bars fa-fw"></i> Menu
|
||||
<div id="dropupContent" class="dropup-content"></div>
|
||||
</div>
|
||||
<div id="footer"></div>
|
||||
<div id="status"></div>
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript" src="/ssh/webssh2.bundle.js"></script></body>
|
||||
</html>
|
Before Width: | Height: | Size: 15 KiB |
|
@ -1,295 +0,0 @@
|
|||
/*! Version 0.2.12 - 2024-07-10T13:49:24.747Z - 533f719 */
|
||||
/**
|
||||
* Copyright (c) 2014 The xterm.js authors. All rights reserved.
|
||||
* Copyright (c) 2012-2013, Christopher Jeffrey (MIT License)
|
||||
* https://github.com/chjj/term.js
|
||||
* @license MIT
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* Originally forked from (with the author's permission):
|
||||
* Fabrice Bellard's javascript vt100 for jslinux:
|
||||
* http://bellard.org/jslinux/
|
||||
* Copyright (c) 2011 Fabrice Bellard
|
||||
* The original design remains. The terminal itself
|
||||
* has been extended to include xterm CSI codes, among
|
||||
* other features.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Default styles for xterm.js
|
||||
*/
|
||||
|
||||
.xterm {
|
||||
font-feature-settings: "liga" 0;
|
||||
position: relative;
|
||||
user-select: none;
|
||||
-ms-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
}
|
||||
|
||||
.xterm.focus,
|
||||
.xterm:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.xterm .xterm-helpers {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
/**
|
||||
* The z-index of the helpers must be higher than the canvases in order for
|
||||
* IMEs to appear on top.
|
||||
*/
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.xterm .xterm-helper-textarea {
|
||||
/*
|
||||
* HACK: to fix IE's blinking cursor
|
||||
* Move textarea out of the screen to the far left, so that the cursor is not visible.
|
||||
*/
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
left: -9999em;
|
||||
top: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
z-index: -10;
|
||||
/** Prevent wrapping so the IME appears against the textarea at the correct position */
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
resize: none;
|
||||
}
|
||||
|
||||
.xterm .composition-view {
|
||||
/* TODO: Composition position got messed up somewhere */
|
||||
background: #000;
|
||||
color: #FFF;
|
||||
display: none;
|
||||
position: absolute;
|
||||
white-space: nowrap;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.xterm .composition-view.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.xterm .xterm-viewport {
|
||||
/* On OS X this is required in order for the scroll bar to appear fully opaque */
|
||||
background-color: #000;
|
||||
overflow-y: scroll;
|
||||
cursor: default;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.xterm .xterm-screen {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.xterm .xterm-screen canvas {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.xterm .xterm-scroll-area {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.xterm-char-measure-element {
|
||||
display: inline-block;
|
||||
visibility: hidden;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -9999em;
|
||||
line-height: normal;
|
||||
}
|
||||
|
||||
.xterm {
|
||||
cursor: text;
|
||||
}
|
||||
|
||||
.xterm.enable-mouse-events {
|
||||
/* When mouse events are enabled (eg. tmux), revert to the standard pointer cursor */
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.xterm.xterm-cursor-pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.xterm.column-select.focus {
|
||||
/* Column selection mode */
|
||||
cursor: crosshair;
|
||||
}
|
||||
|
||||
.xterm .xterm-accessibility,
|
||||
.xterm .xterm-message {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
z-index: 100;
|
||||
color: transparent;
|
||||
}
|
||||
|
||||
.xterm .live-region {
|
||||
position: absolute;
|
||||
left: -9999px;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.xterm-dim {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.xterm-underline {
|
||||
text-decoration: underline;
|
||||
}
|
||||
body, html {
|
||||
font-family: helvetica, sans-serif, arial;
|
||||
font-size: 1em;
|
||||
color: #111;
|
||||
background-color: rgb(0, 0, 0);
|
||||
color: rgb(240, 240, 240);
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
#header {
|
||||
color: rgb(240, 240, 240);
|
||||
background-color: rgb(0, 128, 0);
|
||||
width: 100%;
|
||||
border-color: white;
|
||||
border-style: none none solid none;
|
||||
border-width: 1px;
|
||||
text-align: center;
|
||||
flex: 0 1 auto;
|
||||
z-index: 99;
|
||||
height:19px;
|
||||
display: none;
|
||||
}
|
||||
.box {
|
||||
display: block;
|
||||
height: 100%;
|
||||
}
|
||||
#terminal-container {
|
||||
display: block;
|
||||
width: calc(100% - 1 px);
|
||||
margin: 0 auto;
|
||||
padding: 2px;
|
||||
height: calc(100% - 19px);
|
||||
}
|
||||
#terminal-container .terminal {
|
||||
background-color: #000000;
|
||||
color: #fafafa;
|
||||
padding: 2px;
|
||||
height: calc(100% - 19px);
|
||||
}
|
||||
#terminal-container .terminal:focus .terminal-cursor {
|
||||
background-color: #fafafa;
|
||||
}
|
||||
#bottomdiv {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
background-color: rgb(50, 50, 50);
|
||||
border-color: white;
|
||||
border-style: solid none none none;
|
||||
border-width: 1px;
|
||||
z-index: 99;
|
||||
height: 19px;
|
||||
}
|
||||
#footer {
|
||||
display: inline-block;
|
||||
color: rgb(240, 240, 240);
|
||||
background-color: rgb(50, 50, 50);
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
border-color: white;
|
||||
border-style: none none none solid;
|
||||
border-width: 1px;
|
||||
text-align: left;
|
||||
}
|
||||
#status {
|
||||
display: inline-block;
|
||||
color: rgb(240, 240, 240);
|
||||
background-color: rgb(50, 50, 50);
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
border-color: white;
|
||||
border-style: none solid none solid;
|
||||
border-width: 1px;
|
||||
text-align: left;
|
||||
z-index: 100;
|
||||
}
|
||||
#menu {
|
||||
display: inline-block;
|
||||
font-size: 16px;
|
||||
color: rgb(255, 255, 255);
|
||||
padding-left: 10px;
|
||||
z-index: 100;
|
||||
}
|
||||
#menu:hover .dropup-content {
|
||||
display: block;
|
||||
}
|
||||
#logBtn, #credentialsBtn, #reauthBtn {
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.dropup {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
}
|
||||
.dropup-content {
|
||||
display: none;
|
||||
position: absolute;
|
||||
background-color: #f1f1f1;
|
||||
font-size: 16px;
|
||||
min-width: 160px;
|
||||
bottom: 18px;
|
||||
z-index: 101;
|
||||
}
|
||||
.dropup-content a {
|
||||
color: #777;
|
||||
padding: 12px 16px;
|
||||
text-decoration: none;
|
||||
display: block;
|
||||
}
|
||||
.dropup-content a:hover {
|
||||
background-color: #ccc
|
||||
}
|
||||
.dropup:hover .dropup-content {
|
||||
display: block;
|
||||
}
|
||||
.dropup:click .dropup-content {
|
||||
display: block;
|
||||
}
|
||||
.dropup:hover .dropbtn {
|
||||
background-color: #3e8e41;
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
Customizations and modifications to the client (browser) go here. Then run "npm run build" to integrate into ../public (where client files are served from). Note that ../public is a flat directory structure. ../public directory is deleted and refreshed eatch thime "npm run build" is run.
|
|
@ -1,32 +0,0 @@
|
|||
<!-- <%= htmlWebpackPlugin.options.version %> -->
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>WebSSH2</title>
|
||||
<style>
|
||||
html,
|
||||
body {
|
||||
background-color: #000;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
.dropup-content {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="box">
|
||||
<div id="header"></div>
|
||||
<div id="terminal-container" class="terminal"></div>
|
||||
<div id="bottomdiv">
|
||||
<div class="dropup" id="menu">
|
||||
<i class="fas fa-bars fa-fw"></i> Menu
|
||||
<div id="dropupContent" class="dropup-content"></div>
|
||||
</div>
|
||||
<div id="footer"></div>
|
||||
<div id="status"></div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -1,123 +0,0 @@
|
|||
body, html {
|
||||
font-family: helvetica, sans-serif, arial;
|
||||
font-size: 1em;
|
||||
color: #111;
|
||||
background-color: rgb(0, 0, 0);
|
||||
color: rgb(240, 240, 240);
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
#header {
|
||||
color: rgb(240, 240, 240);
|
||||
background-color: rgb(0, 128, 0);
|
||||
width: 100%;
|
||||
border-color: white;
|
||||
border-style: none none solid none;
|
||||
border-width: 1px;
|
||||
text-align: center;
|
||||
flex: 0 1 auto;
|
||||
z-index: 99;
|
||||
height:19px;
|
||||
display: none;
|
||||
}
|
||||
.box {
|
||||
display: block;
|
||||
height: 100%;
|
||||
}
|
||||
#terminal-container {
|
||||
display: block;
|
||||
width: calc(100% - 1 px);
|
||||
margin: 0 auto;
|
||||
padding: 2px;
|
||||
height: calc(100% - 19px);
|
||||
}
|
||||
#terminal-container .terminal {
|
||||
background-color: #000000;
|
||||
color: #fafafa;
|
||||
padding: 2px;
|
||||
height: calc(100% - 19px);
|
||||
}
|
||||
#terminal-container .terminal:focus .terminal-cursor {
|
||||
background-color: #fafafa;
|
||||
}
|
||||
#bottomdiv {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
background-color: rgb(50, 50, 50);
|
||||
border-color: white;
|
||||
border-style: solid none none none;
|
||||
border-width: 1px;
|
||||
z-index: 99;
|
||||
height: 19px;
|
||||
}
|
||||
#footer {
|
||||
display: inline-block;
|
||||
color: rgb(240, 240, 240);
|
||||
background-color: rgb(50, 50, 50);
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
border-color: white;
|
||||
border-style: none none none solid;
|
||||
border-width: 1px;
|
||||
text-align: left;
|
||||
}
|
||||
#status {
|
||||
display: inline-block;
|
||||
color: rgb(240, 240, 240);
|
||||
background-color: rgb(50, 50, 50);
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
border-color: white;
|
||||
border-style: none solid none solid;
|
||||
border-width: 1px;
|
||||
text-align: left;
|
||||
z-index: 100;
|
||||
}
|
||||
#menu {
|
||||
display: inline-block;
|
||||
font-size: 16px;
|
||||
color: rgb(255, 255, 255);
|
||||
padding-left: 10px;
|
||||
z-index: 100;
|
||||
}
|
||||
#menu:hover .dropup-content {
|
||||
display: block;
|
||||
}
|
||||
#logBtn, #credentialsBtn, #reauthBtn {
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.dropup {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
}
|
||||
.dropup-content {
|
||||
display: none;
|
||||
position: absolute;
|
||||
background-color: #f1f1f1;
|
||||
font-size: 16px;
|
||||
min-width: 160px;
|
||||
bottom: 18px;
|
||||
z-index: 101;
|
||||
}
|
||||
.dropup-content a {
|
||||
color: #777;
|
||||
padding: 12px 16px;
|
||||
text-decoration: none;
|
||||
display: block;
|
||||
}
|
||||
.dropup-content a:hover {
|
||||
background-color: #ccc
|
||||
}
|
||||
.dropup:hover .dropup-content {
|
||||
display: block;
|
||||
}
|
||||
.dropup:click .dropup-content {
|
||||
display: block;
|
||||
}
|
||||
.dropup:hover .dropbtn {
|
||||
background-color: #3e8e41;
|
||||
}
|
Before Width: | Height: | Size: 15 KiB |
|
@ -1,246 +0,0 @@
|
|||
'use strict'
|
||||
|
||||
import * as io from 'socket.io-client'
|
||||
import * as Terminal from 'xterm/dist/xterm'
|
||||
import * as fit from 'xterm/dist/addons/fit/fit'
|
||||
import { library, dom } from '@fortawesome/fontawesome-svg-core'
|
||||
import { faBars, faClipboard, faDownload, faKey, faCog } from '@fortawesome/free-solid-svg-icons'
|
||||
library.add(faBars, faClipboard, faDownload, faKey, faCog)
|
||||
dom.watch()
|
||||
|
||||
require('xterm/dist/xterm.css')
|
||||
require('../css/style.css')
|
||||
|
||||
Terminal.applyAddon(fit)
|
||||
|
||||
/* global Blob, logBtn, credentialsBtn, reauthBtn, downloadLogBtn */
|
||||
var sessionLogEnable = false
|
||||
var loggedData = false
|
||||
var allowreplay = false
|
||||
var allowreauth = false
|
||||
var sessionLog, sessionFooter, logDate, currentDate, myFile, errorExists
|
||||
var socket, termid // eslint-disable-line
|
||||
var term = new Terminal()
|
||||
// DOM properties
|
||||
var status = document.getElementById('status')
|
||||
var header = document.getElementById('header')
|
||||
var dropupContent = document.getElementById('dropupContent')
|
||||
var footer = document.getElementById('footer')
|
||||
var terminalContainer = document.getElementById('terminal-container')
|
||||
term.open(terminalContainer)
|
||||
term.focus()
|
||||
term.fit()
|
||||
|
||||
window.addEventListener('resize', resizeScreen, false)
|
||||
|
||||
function resizeScreen () {
|
||||
term.fit()
|
||||
socket.emit('resize', { cols: term.cols, rows: term.rows })
|
||||
}
|
||||
|
||||
socket = io.connect({
|
||||
path: '/ssh/socket.io'
|
||||
})
|
||||
|
||||
term.on('data', function (data) {
|
||||
socket.emit('data', data)
|
||||
})
|
||||
|
||||
socket.on('data', function (data) {
|
||||
term.write(data)
|
||||
if (sessionLogEnable) {
|
||||
sessionLog = sessionLog + data
|
||||
}
|
||||
})
|
||||
|
||||
socket.on('connect', function () {
|
||||
socket.emit('geometry', term.cols, term.rows)
|
||||
})
|
||||
|
||||
socket.on('setTerminalOpts', function (data) {
|
||||
term.setOption('cursorBlink', data.cursorBlink)
|
||||
term.setOption('scrollback', data.scrollback)
|
||||
term.setOption('tabStopWidth', data.tabStopWidth)
|
||||
term.setOption('bellStyle', data.bellStyle)
|
||||
})
|
||||
|
||||
socket.on('title', function (data) {
|
||||
document.title = data
|
||||
})
|
||||
|
||||
socket.on('menu', function (data) {
|
||||
drawMenu(data)
|
||||
})
|
||||
|
||||
socket.on('status', function (data) {
|
||||
status.innerHTML = data
|
||||
})
|
||||
|
||||
socket.on('ssherror', function (data) {
|
||||
status.innerHTML = data
|
||||
status.style.backgroundColor = 'red'
|
||||
errorExists = true
|
||||
})
|
||||
|
||||
socket.on('headerBackground', function (data) {
|
||||
header.style.backgroundColor = data
|
||||
})
|
||||
|
||||
socket.on('header', function (data) {
|
||||
if (data) {
|
||||
header.innerHTML = data
|
||||
header.style.display = 'block'
|
||||
// header is 19px and footer is 19px, recaculate new terminal-container and resize
|
||||
terminalContainer.style.height = 'calc(100% - 38px)'
|
||||
resizeScreen()
|
||||
}
|
||||
})
|
||||
|
||||
socket.on('footer', function (data) {
|
||||
sessionFooter = data
|
||||
footer.innerHTML = data
|
||||
})
|
||||
|
||||
socket.on('statusBackground', function (data) {
|
||||
status.style.backgroundColor = data
|
||||
})
|
||||
|
||||
socket.on('allowreplay', function (data) {
|
||||
if (data === true) {
|
||||
console.log('allowreplay: ' + data)
|
||||
allowreplay = true
|
||||
drawMenu(dropupContent.innerHTML + '<a id="credentialsBtn"><i class="fas fa-key fa-fw"></i> Credentials</a>')
|
||||
} else {
|
||||
allowreplay = false
|
||||
console.log('allowreplay: ' + data)
|
||||
}
|
||||
})
|
||||
|
||||
socket.on('allowreauth', function (data) {
|
||||
if (data === true) {
|
||||
console.log('allowreauth: ' + data)
|
||||
allowreauth = true
|
||||
drawMenu(dropupContent.innerHTML + '<a id="reauthBtn"><i class="fas fa-key fa-fw"></i> Switch User</a>')
|
||||
} else {
|
||||
allowreauth = false
|
||||
console.log('allowreauth: ' + data)
|
||||
}
|
||||
})
|
||||
|
||||
socket.on('disconnect', function (err) {
|
||||
if (!errorExists) {
|
||||
status.style.backgroundColor = 'red'
|
||||
status.innerHTML =
|
||||
'WEBSOCKET SERVER DISCONNECTED: ' + err
|
||||
}
|
||||
socket.io.reconnection(false)
|
||||
})
|
||||
|
||||
socket.on('error', function (err) {
|
||||
if (!errorExists) {
|
||||
status.style.backgroundColor = 'red'
|
||||
status.innerHTML = 'ERROR: ' + err
|
||||
}
|
||||
})
|
||||
|
||||
socket.on('reauth', function () {
|
||||
(allowreauth) && reauthSession()
|
||||
})
|
||||
|
||||
term.on('title', function (title) {
|
||||
document.title = title
|
||||
})
|
||||
|
||||
// draw/re-draw menu and reattach listeners
|
||||
// when dom is changed, listeners are abandonded
|
||||
function drawMenu (data) {
|
||||
dropupContent.innerHTML = data
|
||||
logBtn.addEventListener('click', toggleLog)
|
||||
allowreauth && reauthBtn.addEventListener('click', reauthSession)
|
||||
allowreplay && credentialsBtn.addEventListener('click', replayCredentials)
|
||||
loggedData && downloadLogBtn.addEventListener('click', downloadLog)
|
||||
}
|
||||
|
||||
// reauthenticate
|
||||
function reauthSession () { // eslint-disable-line
|
||||
console.log('re-authenticating')
|
||||
window.location.href = '/ssh/reauth'
|
||||
return false
|
||||
}
|
||||
|
||||
// replay password to server, requires
|
||||
function replayCredentials () { // eslint-disable-line
|
||||
socket.emit('control', 'replayCredentials')
|
||||
console.log('replaying credentials')
|
||||
term.focus()
|
||||
return false
|
||||
}
|
||||
|
||||
// Set variable to toggle log data from client/server to a varialble
|
||||
// for later download
|
||||
function toggleLog () { // eslint-disable-line
|
||||
if (sessionLogEnable === true) {
|
||||
sessionLogEnable = false
|
||||
loggedData = true
|
||||
logBtn.innerHTML = '<i class="fas fa-clipboard fa-fw"></i> Start Log'
|
||||
console.log('stopping log, ' + sessionLogEnable)
|
||||
currentDate = new Date()
|
||||
sessionLog = sessionLog + '\r\n\r\nLog End for ' + sessionFooter + ': ' +
|
||||
currentDate.getFullYear() + '/' + (currentDate.getMonth() + 1) + '/' +
|
||||
currentDate.getDate() + ' @ ' + currentDate.getHours() + ':' +
|
||||
currentDate.getMinutes() + ':' + currentDate.getSeconds() + '\r\n'
|
||||
logDate = currentDate
|
||||
term.focus()
|
||||
return false
|
||||
} else {
|
||||
sessionLogEnable = true
|
||||
loggedData = true
|
||||
logBtn.innerHTML = '<i class="fas fa-cog fa-spin fa-fw"></i> Stop Log'
|
||||
downloadLogBtn.style.color = '#000'
|
||||
downloadLogBtn.addEventListener('click', downloadLog)
|
||||
console.log('starting log, ' + sessionLogEnable)
|
||||
currentDate = new Date()
|
||||
sessionLog = 'Log Start for ' + sessionFooter + ': ' +
|
||||
currentDate.getFullYear() + '/' + (currentDate.getMonth() + 1) + '/' +
|
||||
currentDate.getDate() + ' @ ' + currentDate.getHours() + ':' +
|
||||
currentDate.getMinutes() + ':' + currentDate.getSeconds() + '\r\n\r\n'
|
||||
logDate = currentDate
|
||||
term.focus()
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// cross browser method to "download" an element to the local system
|
||||
// used for our client-side logging feature
|
||||
function downloadLog () { // eslint-disable-line
|
||||
if (loggedData === true) {
|
||||
myFile = 'WebSSH2-' + logDate.getFullYear() + (logDate.getMonth() + 1) +
|
||||
logDate.getDate() + '_' + logDate.getHours() + logDate.getMinutes() +
|
||||
logDate.getSeconds() + '.log'
|
||||
// regex should eliminate escape sequences from being logged.
|
||||
var blob = new Blob([sessionLog.replace(/[\u001b\u009b][[\]()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><;]/g, '')], { // eslint-disable-line no-control-regex
|
||||
type: 'text/plain'
|
||||
})
|
||||
if (window.navigator.msSaveOrOpenBlob) {
|
||||
window.navigator.msSaveBlob(blob, myFile)
|
||||
} else {
|
||||
var elem = window.document.createElement('a')
|
||||
elem.href = window.URL.createObjectURL(blob)
|
||||
elem.download = myFile
|
||||
document.body.appendChild(elem)
|
||||
elem.click()
|
||||
document.body.removeChild(elem)
|
||||
}
|
||||
}
|
||||
term.focus()
|
||||
}
|
||||
|
||||
// Add an event listener for capturing Ctrl + Shift + 6
|
||||
document.addEventListener('keydown', function(event) {
|
||||
if (event.ctrlKey && event.shiftKey && event.code === 'Digit6') {
|
||||
// Prevent the default action
|
||||
event.preventDefault();
|
||||
// Emit the desired key sequence to the server
|
||||
socket.emit('data', '\x1E'); // 0x1E is the RS control character
|
||||
}
|
||||
});
|
95
app/config.js
Normal file
|
@ -0,0 +1,95 @@
|
|||
// config.js
|
||||
const path = require("path");
|
||||
const fs = require("fs");
|
||||
const nodeRoot = path.dirname(require.main.filename);
|
||||
const configPath = path.join(nodeRoot, "config.json");
|
||||
|
||||
// Default configuration
|
||||
let config = {
|
||||
listen: {
|
||||
ip: "0.0.0.0",
|
||||
port: 2222,
|
||||
},
|
||||
http: {
|
||||
origins: ["*:*"],
|
||||
},
|
||||
user: {
|
||||
name: null,
|
||||
password: null,
|
||||
},
|
||||
ssh: {
|
||||
host: null,
|
||||
port: 22,
|
||||
term: "xterm-color",
|
||||
readyTimeout: 20000,
|
||||
keepaliveInterval: 120000,
|
||||
keepaliveCountMax: 10,
|
||||
},
|
||||
terminal: {
|
||||
cursorBlink: true,
|
||||
scrollback: 10000,
|
||||
tabStopWidth: 8,
|
||||
bellStyle: "sound",
|
||||
},
|
||||
header: {
|
||||
text: null,
|
||||
background: "green",
|
||||
},
|
||||
session: {
|
||||
name: "WebSSH2",
|
||||
secret: "mysecret",
|
||||
},
|
||||
options: {
|
||||
challengeButton: true,
|
||||
allowreauth: true,
|
||||
},
|
||||
algorithms: {
|
||||
kex: [
|
||||
"ecdh-sha2-nistp256",
|
||||
"ecdh-sha2-nistp384",
|
||||
"ecdh-sha2-nistp521",
|
||||
"diffie-hellman-group-exchange-sha256",
|
||||
"diffie-hellman-group14-sha1",
|
||||
],
|
||||
cipher: [
|
||||
"aes128-ctr",
|
||||
"aes192-ctr",
|
||||
"aes256-ctr",
|
||||
"aes128-gcm",
|
||||
"aes128-gcm@openssh.com",
|
||||
"aes256-gcm",
|
||||
"aes256-gcm@openssh.com",
|
||||
"aes256-cbc",
|
||||
],
|
||||
hmac: ["hmac-sha2-256", "hmac-sha2-512", "hmac-sha1"],
|
||||
compress: ["none", "zlib@openssh.com", "zlib"],
|
||||
},
|
||||
serverlog: {
|
||||
client: false,
|
||||
server: false,
|
||||
},
|
||||
accesslog: false,
|
||||
verify: false,
|
||||
};
|
||||
|
||||
try {
|
||||
if (fs.existsSync(configPath)) {
|
||||
console.log("WebSSH2 service reading config from: " + configPath);
|
||||
config = require("read-config-ng")(configPath);
|
||||
} else {
|
||||
console.error(
|
||||
"\n\nERROR: Missing config.json for webssh. Current config: " +
|
||||
JSON.stringify(config)
|
||||
);
|
||||
console.error("\n See config.json.sample for details\n\n");
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(
|
||||
"\n\nERROR: Missing config.json for webssh. Current config: " +
|
||||
JSON.stringify(config)
|
||||
);
|
||||
console.error("\n See config.json.sample for details\n\n");
|
||||
console.error("ERROR:\n\n " + err);
|
||||
}
|
||||
|
||||
module.exports = config;
|
12
app/expressOptions.js
Normal file
|
@ -0,0 +1,12 @@
|
|||
// app/expressOptions.js
|
||||
module.exports = {
|
||||
dotfiles: "ignore",
|
||||
etag: false,
|
||||
extensions: ["htm", "html"],
|
||||
index: false,
|
||||
maxAge: "1s",
|
||||
redirect: false,
|
||||
setHeaders: function (res, path, stat) {
|
||||
res.set("x-timestamp", Date.now());
|
||||
},
|
||||
};
|
29
app/index.js
|
@ -1,29 +0,0 @@
|
|||
'use strict'
|
||||
/* jshint esversion: 6, asi: true, node: true */
|
||||
/*
|
||||
* index.js
|
||||
*
|
||||
* WebSSH2 - Web to SSH2 gateway
|
||||
* Bill Church - https://github.com/billchurch/WebSSH2 - May 2017
|
||||
*
|
||||
*/
|
||||
|
||||
var config = require('./server/app').config
|
||||
var server = require('./server/app').server
|
||||
|
||||
server.listen({ host: config.listen.ip, port: config.listen.port
|
||||
})
|
||||
|
||||
console.log('WebSSH2 service listening on ' + config.listen.ip + ':' + config.listen.port)
|
||||
|
||||
server.on('error', function (err) {
|
||||
if (err.code === 'EADDRINUSE') {
|
||||
config.listen.port++
|
||||
console.warn('WebSSH2 Address in use, retrying on port ' + config.listen.port)
|
||||
setTimeout(function () {
|
||||
server.listen(config.listen.port)
|
||||
}, 250)
|
||||
} else {
|
||||
console.log('WebSSH2 server.listen ERROR: ' + err.code)
|
||||
}
|
||||
})
|
|
@ -1,61 +0,0 @@
|
|||
const webpack = require("webpack");
|
||||
const { BannerPlugin } = require("webpack");
|
||||
const HtmlWebpackPlugin = require("html-webpack-plugin");
|
||||
const path = require("path");
|
||||
const CopyWebpackPlugin = require("copy-webpack-plugin");
|
||||
const CleanWebpackPlugin = require("clean-webpack-plugin");
|
||||
const ExtractTextPlugin = require("extract-text-webpack-plugin");
|
||||
const packageJson = require("../package.json"); // Load package.json
|
||||
const commitHash = require("child_process")
|
||||
.execSync("git rev-parse --short HEAD")
|
||||
.toString()
|
||||
.trim();
|
||||
|
||||
module.exports = {
|
||||
context: path.resolve(__dirname, "../"),
|
||||
entry: {
|
||||
webssh2: "./client/src/js/index.js",
|
||||
},
|
||||
plugins: [
|
||||
new BannerPlugin({
|
||||
banner: `Version ${
|
||||
packageJson.version
|
||||
} - ${new Date().toISOString()} - ${commitHash}`,
|
||||
include: /\.(js|css|html|htm)$/,
|
||||
}),
|
||||
new CleanWebpackPlugin(["client/public"], {
|
||||
root: path.resolve("__dirname", "../"),
|
||||
verbose: true,
|
||||
}),
|
||||
new HtmlWebpackPlugin({
|
||||
template: "./client/src/client.htm", // Path to your source template
|
||||
filename: "client.htm", // Optional: output file name, defaults to index.html
|
||||
minify: false,
|
||||
scriptLoading: "defer",
|
||||
version: `Version ${
|
||||
packageJson.version
|
||||
} - ${new Date().toISOString()} - ${commitHash}`,
|
||||
publicPath: "/ssh/", // Prepend /ssh/ to the script tags
|
||||
}),
|
||||
new CopyWebpackPlugin([
|
||||
{ from: "./client/src/favicon.ico", to: "favicon.ico" },
|
||||
]),
|
||||
new ExtractTextPlugin("[name].css"),
|
||||
],
|
||||
output: {
|
||||
filename: "[name].bundle.js",
|
||||
path: path.resolve(__dirname, "../client/public"),
|
||||
publicPath: "/ssh/", // Prepend /ssh/ to the script tags
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.css$/,
|
||||
use: ExtractTextPlugin.extract({
|
||||
fallback: "style-loader",
|
||||
use: [{ loader: "css-loader" }],
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
|
@ -1,9 +0,0 @@
|
|||
const merge = require('webpack-merge')
|
||||
const common = require('./webpack.common.js')
|
||||
|
||||
module.exports = merge(common, {
|
||||
devtool: 'inline-source-map',
|
||||
devServer: {
|
||||
contentBase: '../client/public'
|
||||
}
|
||||
})
|
|
@ -1,21 +0,0 @@
|
|||
const merge = require("webpack-merge");
|
||||
const UglifyJSPlugin = require("uglifyjs-webpack-plugin");
|
||||
const common = require("./webpack.common.js");
|
||||
|
||||
module.exports = merge(
|
||||
{
|
||||
plugins: [
|
||||
new UglifyJSPlugin({
|
||||
uglifyOptions: {
|
||||
ie8: false,
|
||||
dead_code: true,
|
||||
output: {
|
||||
comments: false,
|
||||
beautify: false,
|
||||
},
|
||||
},
|
||||
}),
|
||||
],
|
||||
},
|
||||
common
|
||||
);
|
|
@ -1,202 +0,0 @@
|
|||
'use strict'
|
||||
/* jshint esversion: 6, asi: true, node: true */
|
||||
// app.js
|
||||
|
||||
var path = require('path')
|
||||
var fs = require('fs')
|
||||
var nodeRoot = path.dirname(require.main.filename)
|
||||
var configPath = path.join(nodeRoot, 'config.json')
|
||||
var publicPath = path.join(nodeRoot, 'client', 'public')
|
||||
console.log('WebSSH2 service reading config from: ' + configPath)
|
||||
var express = require('express')
|
||||
var logger = require('morgan')
|
||||
|
||||
// sane defaults if config.json or parts are missing
|
||||
let config = {
|
||||
listen: {
|
||||
ip: '0.0.0.0',
|
||||
port: 2222
|
||||
},
|
||||
http: {
|
||||
origins: ['*:*']
|
||||
},
|
||||
user: {
|
||||
name: null,
|
||||
password: null
|
||||
},
|
||||
ssh: {
|
||||
host: null,
|
||||
port: 22,
|
||||
term: 'xterm-color',
|
||||
readyTimeout: 20000,
|
||||
keepaliveInterval: 120000,
|
||||
keepaliveCountMax: 10
|
||||
},
|
||||
terminal: {
|
||||
cursorBlink: true,
|
||||
scrollback: 10000,
|
||||
tabStopWidth: 8,
|
||||
bellStyle: 'sound'
|
||||
},
|
||||
header: {
|
||||
text: null,
|
||||
background: 'green'
|
||||
},
|
||||
session: {
|
||||
name: 'WebSSH2',
|
||||
secret: 'mysecret'
|
||||
},
|
||||
options: {
|
||||
challengeButton: true,
|
||||
allowreauth: true
|
||||
},
|
||||
algorithms: {
|
||||
kex: [
|
||||
'ecdh-sha2-nistp256',
|
||||
'ecdh-sha2-nistp384',
|
||||
'ecdh-sha2-nistp521',
|
||||
'diffie-hellman-group-exchange-sha256',
|
||||
'diffie-hellman-group14-sha1'
|
||||
],
|
||||
cipher: [
|
||||
'aes128-ctr',
|
||||
'aes192-ctr',
|
||||
'aes256-ctr',
|
||||
'aes128-gcm',
|
||||
'aes128-gcm@openssh.com',
|
||||
'aes256-gcm',
|
||||
'aes256-gcm@openssh.com',
|
||||
'aes256-cbc'
|
||||
],
|
||||
hmac: [
|
||||
'hmac-sha2-256',
|
||||
'hmac-sha2-512',
|
||||
'hmac-sha1'
|
||||
],
|
||||
compress: [
|
||||
'none',
|
||||
'zlib@openssh.com',
|
||||
'zlib'
|
||||
]
|
||||
},
|
||||
serverlog: {
|
||||
client: false,
|
||||
server: false
|
||||
},
|
||||
accesslog: false,
|
||||
verify: false
|
||||
}
|
||||
|
||||
// test if config.json exists, if not provide error message but try to run
|
||||
// anyway
|
||||
try {
|
||||
if (fs.existsSync(configPath)) {
|
||||
console.log('ephemeral_auth service reading config from: ' + configPath)
|
||||
config = require('read-config')(configPath)
|
||||
} else {
|
||||
console.error('\n\nERROR: Missing config.json for webssh. Current config: ' + JSON.stringify(config))
|
||||
console.error('\n See config.json.sample for details\n\n')
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('\n\nERROR: Missing config.json for webssh. Current config: ' + JSON.stringify(config))
|
||||
console.error('\n See config.json.sample for details\n\n')
|
||||
console.error('ERROR:\n\n ' + err)
|
||||
}
|
||||
|
||||
var session = require('express-session')({
|
||||
secret: config.session.secret,
|
||||
name: config.session.name,
|
||||
resave: true,
|
||||
saveUninitialized: false,
|
||||
unset: 'destroy'
|
||||
})
|
||||
var app = express()
|
||||
var compression = require('compression')
|
||||
var server = require('http').Server(app)
|
||||
var myutil = require('./util')
|
||||
var validator = require('validator')
|
||||
var io = require('socket.io')(server, { serveClient: false, path: '/ssh/socket.io', origins: config.http.origins })
|
||||
var socket = require('./socket')
|
||||
var expressOptions = require('./expressOptions')
|
||||
var favicon = require('serve-favicon')
|
||||
|
||||
// express
|
||||
app.use(compression({ level: 9 }))
|
||||
app.use(session)
|
||||
app.use(myutil.basicAuth)
|
||||
if (config.accesslog) app.use(logger('common'))
|
||||
app.disable('x-powered-by')
|
||||
|
||||
// static files
|
||||
app.use('/ssh', express.static(publicPath, expressOptions))
|
||||
// app.use(express.static(publicPath, expressOptions))
|
||||
|
||||
// favicon from root if being pre-fetched by browser to prevent a 404
|
||||
app.use(favicon(path.join(publicPath,'favicon.ico')))
|
||||
|
||||
app.get('/ssh/reauth', function (req, res, next) {
|
||||
var r = req.headers.referer || '/'
|
||||
res.status(401).send('<!DOCTYPE html><html><head><meta http-equiv="refresh" content="0; url=' + r + '"></head><body bgcolor="#000"></body></html>')
|
||||
})
|
||||
|
||||
// eslint-disable-next-line complexity
|
||||
app.get('/ssh/host/:host?', function (req, res, next) {
|
||||
res.sendFile(path.join(path.join(publicPath, 'client.htm')))
|
||||
// capture, assign, and validated variables
|
||||
req.session.ssh = {
|
||||
host: (validator.isIP(req.params.host + '') && req.params.host) ||
|
||||
(validator.isFQDN(req.params.host) && req.params.host) ||
|
||||
(/^(([a-z]|[A-Z]|[0-9]|[!^(){}\-_~])+)?\w$/.test(req.params.host) &&
|
||||
req.params.host) || config.ssh.host,
|
||||
port: (validator.isInt(req.query.port + '', { min: 1, max: 65535 }) &&
|
||||
req.query.port) || config.ssh.port,
|
||||
header: {
|
||||
name: req.query.header || config.header.text,
|
||||
background: req.query.headerBackground || config.header.background
|
||||
},
|
||||
algorithms: config.algorithms,
|
||||
keepaliveInterval: config.ssh.keepaliveInterval,
|
||||
keepaliveCountMax: config.ssh.keepaliveCountMax,
|
||||
term: (/^(([a-z]|[A-Z]|[0-9]|[!^(){}\-_~])+)?\w$/.test(req.query.sshterm) &&
|
||||
req.query.sshterm) || config.ssh.term,
|
||||
terminal: {
|
||||
cursorBlink: (validator.isBoolean(req.query.cursorBlink + '') ? myutil.parseBool(req.query.cursorBlink) : config.terminal.cursorBlink),
|
||||
scrollback: (validator.isInt(req.query.scrollback + '', { min: 1, max: 200000 }) && req.query.scrollback) ? req.query.scrollback : config.terminal.scrollback,
|
||||
tabStopWidth: (validator.isInt(req.query.tabStopWidth + '', { min: 1, max: 100 }) && req.query.tabStopWidth) ? req.query.tabStopWidth : config.terminal.tabStopWidth,
|
||||
bellStyle: ((req.query.bellStyle) && (['sound', 'none'].indexOf(req.query.bellStyle) > -1)) ? req.query.bellStyle : config.terminal.bellStyle
|
||||
},
|
||||
allowreplay: config.options.challengeButton || (validator.isBoolean(req.headers.allowreplay + '') ? myutil.parseBool(req.headers.allowreplay) : false),
|
||||
allowreauth: config.options.allowreauth || false,
|
||||
mrhsession: ((validator.isAlphanumeric(req.headers.mrhsession + '') && req.headers.mrhsession) ? req.headers.mrhsession : 'none'),
|
||||
serverlog: {
|
||||
client: config.serverlog.client || false,
|
||||
server: config.serverlog.server || false
|
||||
},
|
||||
readyTimeout: (validator.isInt(req.query.readyTimeout + '', { min: 1, max: 300000 }) &&
|
||||
req.query.readyTimeout) || config.ssh.readyTimeout
|
||||
}
|
||||
if (req.session.ssh.header.name) validator.escape(req.session.ssh.header.name)
|
||||
if (req.session.ssh.header.background) validator.escape(req.session.ssh.header.background)
|
||||
})
|
||||
|
||||
// express error handling
|
||||
app.use(function (req, res, next) {
|
||||
res.status(404).send("Sorry can't find that!")
|
||||
})
|
||||
|
||||
app.use(function (err, req, res, next) {
|
||||
console.error(err.stack)
|
||||
res.status(500).send('Something broke!')
|
||||
})
|
||||
|
||||
// socket.io
|
||||
// expose express session with socket.request.session
|
||||
io.use(function (socket, next) {
|
||||
(socket.request.res) ? session(socket.request, socket.request.res, next)
|
||||
: next(next)
|
||||
})
|
||||
|
||||
// bring up socket
|
||||
io.on('connection', socket)
|
||||
|
||||
module.exports = { server: server, config: config }
|
|
@ -1,11 +0,0 @@
|
|||
module.exports = {
|
||||
dotfiles: 'ignore',
|
||||
etag: false,
|
||||
extensions: ['htm', 'html'],
|
||||
index: false,
|
||||
maxAge: '1s',
|
||||
redirect: false,
|
||||
setHeaders: function (res, path, stat) {
|
||||
res.set('x-timestamp', Date.now())
|
||||
}
|
||||
}
|
|
@ -1,173 +0,0 @@
|
|||
'use strict'
|
||||
/* jshint esversion: 6, asi: true, node: true */
|
||||
// socket.js
|
||||
|
||||
// private
|
||||
var debug = require('debug')
|
||||
var debugWebSSH2 = require('debug')('WebSSH2')
|
||||
var SSH = require('ssh2').Client
|
||||
// var fs = require('fs')
|
||||
// var hostkeys = JSON.parse(fs.readFileSync('./hostkeyhashes.json', 'utf8'))
|
||||
var termCols, termRows
|
||||
var menuData = '<a id="logBtn"><i class="fas fa-clipboard fa-fw"></i> Start Log</a>' +
|
||||
'<a id="downloadLogBtn"><i class="fas fa-download fa-fw"></i> Download Log</a>'
|
||||
|
||||
// public
|
||||
module.exports = function socket (socket) {
|
||||
// if websocket connection arrives without an express session, kill it
|
||||
if (!socket.request.session) {
|
||||
socket.emit('401 UNAUTHORIZED')
|
||||
debugWebSSH2('SOCKET: No Express Session / REJECTED')
|
||||
socket.disconnect(true)
|
||||
return
|
||||
}
|
||||
var conn = new SSH()
|
||||
socket.on('geometry', function socketOnGeometry (cols, rows) {
|
||||
termCols = cols
|
||||
termRows = rows
|
||||
})
|
||||
conn.on('banner', function connOnBanner (data) {
|
||||
// need to convert to cr/lf for proper formatting
|
||||
data = data.replace(/\r?\n/g, '\r\n')
|
||||
socket.emit('data', data.toString('utf-8'))
|
||||
})
|
||||
|
||||
conn.on('ready', function connOnReady () {
|
||||
console.log('WebSSH2 Login: user=' + socket.request.session.username + ' from=' + socket.handshake.address + ' host=' + socket.request.session.ssh.host + ' port=' + socket.request.session.ssh.port + ' sessionID=' + socket.request.sessionID + '/' + socket.id + ' mrhsession=' + socket.request.session.ssh.mrhsession + ' allowreplay=' + socket.request.session.ssh.allowreplay + ' term=' + socket.request.session.ssh.term)
|
||||
socket.emit('menu', menuData)
|
||||
socket.emit('allowreauth', socket.request.session.ssh.allowreauth)
|
||||
socket.emit('setTerminalOpts', socket.request.session.ssh.terminal)
|
||||
socket.emit('title', 'ssh://' + socket.request.session.ssh.host)
|
||||
if (socket.request.session.ssh.header.background) socket.emit('headerBackground', socket.request.session.ssh.header.background)
|
||||
if (socket.request.session.ssh.header.name) socket.emit('header', socket.request.session.ssh.header.name)
|
||||
socket.emit('footer', 'ssh://' + socket.request.session.username + '@' + socket.request.session.ssh.host + ':' + socket.request.session.ssh.port)
|
||||
socket.emit('status', 'SSH CONNECTION ESTABLISHED')
|
||||
socket.emit('statusBackground', 'green')
|
||||
socket.emit('allowreplay', socket.request.session.ssh.allowreplay)
|
||||
conn.shell({
|
||||
term: socket.request.session.ssh.term,
|
||||
cols: termCols,
|
||||
rows: termRows
|
||||
}, function connShell (err, stream) {
|
||||
if (err) {
|
||||
SSHerror('EXEC ERROR' + err)
|
||||
conn.end()
|
||||
return
|
||||
}
|
||||
// poc to log commands from client
|
||||
if (socket.request.session.ssh.serverlog.client) var dataBuffer
|
||||
socket.on('data', function socketOnData (data) {
|
||||
stream.write(data)
|
||||
// poc to log commands from client
|
||||
if (socket.request.session.ssh.serverlog.client) {
|
||||
if (data === '\r') {
|
||||
console.log('serverlog.client: ' + socket.request.session.id + '/' + socket.id + ' host: ' + socket.request.session.ssh.host + ' command: ' + dataBuffer)
|
||||
dataBuffer = undefined
|
||||
} else {
|
||||
dataBuffer = (dataBuffer) ? dataBuffer + data : data
|
||||
}
|
||||
}
|
||||
})
|
||||
socket.on('control', function socketOnControl (controlData) {
|
||||
switch (controlData) {
|
||||
case 'replayCredentials':
|
||||
if (socket.request.session.ssh.allowreplay) {
|
||||
stream.write(socket.request.session.userpassword + '\n')
|
||||
}
|
||||
/* falls through */
|
||||
default:
|
||||
console.log('controlData: ' + controlData)
|
||||
}
|
||||
})
|
||||
socket.on('resize', function socketOnResize (data) {
|
||||
stream.setWindow(data.rows, data.cols)
|
||||
})
|
||||
socket.on('disconnecting', function socketOnDisconnecting (reason) { debugWebSSH2('SOCKET DISCONNECTING: ' + reason) })
|
||||
socket.on('disconnect', function socketOnDisconnect (reason) {
|
||||
debugWebSSH2('SOCKET DISCONNECT: ' + reason)
|
||||
err = { message: reason }
|
||||
SSHerror('CLIENT SOCKET DISCONNECT', err)
|
||||
conn.end()
|
||||
// socket.request.session.destroy()
|
||||
})
|
||||
socket.on('error', function socketOnError (err) {
|
||||
SSHerror('SOCKET ERROR', err)
|
||||
conn.end()
|
||||
})
|
||||
|
||||
stream.on('data', function streamOnData (data) { socket.emit('data', data.toString('utf-8')) })
|
||||
stream.on('close', function streamOnClose (code, signal) {
|
||||
err = { message: ((code || signal) ? (((code) ? 'CODE: ' + code : '') + ((code && signal) ? ' ' : '') + ((signal) ? 'SIGNAL: ' + signal : '')) : undefined) }
|
||||
SSHerror('STREAM CLOSE', err)
|
||||
conn.end()
|
||||
})
|
||||
stream.stderr.on('data', function streamStderrOnData (data) {
|
||||
console.log('STDERR: ' + data)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
conn.on('end', function connOnEnd (err) { SSHerror('CONN END BY HOST', err) })
|
||||
conn.on('close', function connOnClose (err) { SSHerror('CONN CLOSE', err) })
|
||||
conn.on('error', function connOnError (err) { SSHerror('CONN ERROR', err) })
|
||||
conn.on('keyboard-interactive', function connOnKeyboardInteractive (name, instructions, instructionsLang, prompts, finish) {
|
||||
debugWebSSH2('conn.on(\'keyboard-interactive\')')
|
||||
finish([socket.request.session.userpassword])
|
||||
})
|
||||
if (socket.request.session.username && socket.request.session.userpassword && socket.request.session.ssh) {
|
||||
// console.log('hostkeys: ' + hostkeys[0].[0])
|
||||
conn.connect({
|
||||
host: socket.request.session.ssh.host,
|
||||
port: socket.request.session.ssh.port,
|
||||
username: socket.request.session.username,
|
||||
password: socket.request.session.userpassword,
|
||||
tryKeyboard: true,
|
||||
algorithms: socket.request.session.ssh.algorithms,
|
||||
readyTimeout: socket.request.session.ssh.readyTimeout,
|
||||
keepaliveInterval: socket.request.session.ssh.keepaliveInterval,
|
||||
keepaliveCountMax: socket.request.session.ssh.keepaliveCountMax,
|
||||
debug: debug('ssh2')
|
||||
})
|
||||
} else {
|
||||
debugWebSSH2('Attempt to connect without session.username/password or session varialbles defined, potentially previously abandoned client session. disconnecting websocket client.\r\nHandshake information: \r\n ' + JSON.stringify(socket.handshake))
|
||||
socket.emit('ssherror', 'WEBSOCKET ERROR - Refresh the browser and try again')
|
||||
socket.request.session.destroy()
|
||||
socket.disconnect(true)
|
||||
}
|
||||
|
||||
/**
|
||||
* Error handling for various events. Outputs error to client, logs to
|
||||
* server, destroys session and disconnects socket.
|
||||
* @param {string} myFunc Function calling this function
|
||||
* @param {object} err error object or error message
|
||||
*/
|
||||
function SSHerror (myFunc, err) {
|
||||
var theError
|
||||
if (socket.request.session) {
|
||||
// we just want the first error of the session to pass to the client
|
||||
socket.request.session.error = (socket.request.session.error) || ((err) ? err.message : undefined)
|
||||
theError = (socket.request.session.error) ? ': ' + socket.request.session.error : ''
|
||||
// log unsuccessful login attempt
|
||||
if (err && (err.level === 'client-authentication')) {
|
||||
console.log('WebSSH2 ' + 'error: Authentication failure'.red.bold +
|
||||
' user=' + socket.request.session.username.yellow.bold.underline +
|
||||
' from=' + socket.handshake.address.yellow.bold.underline)
|
||||
socket.emit('allowreauth', socket.request.session.ssh.allowreauth)
|
||||
socket.emit('reauth')
|
||||
} else {
|
||||
console.log('WebSSH2 Logout: user=' + socket.request.session.username + ' from=' + socket.handshake.address + ' host=' + socket.request.session.ssh.host + ' port=' + socket.request.session.ssh.port + ' sessionID=' + socket.request.sessionID + '/' + socket.id + ' allowreplay=' + socket.request.session.ssh.allowreplay + ' term=' + socket.request.session.ssh.term)
|
||||
if (err) {
|
||||
theError = (err) ? ': ' + err.message : ''
|
||||
console.log('WebSSH2 error' + theError)
|
||||
}
|
||||
}
|
||||
socket.emit('ssherror', 'SSH ' + myFunc + theError)
|
||||
socket.request.session.destroy()
|
||||
socket.disconnect(true)
|
||||
} else {
|
||||
theError = (err) ? ': ' + err.message : ''
|
||||
socket.disconnect(true)
|
||||
}
|
||||
debugWebSSH2('SSHerror ' + myFunc + theError)
|
||||
}
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
'use strict'
|
||||
/* jshint esversion: 6, asi: true, node: true */
|
||||
// util.js
|
||||
|
||||
// private
|
||||
require('colors') // allow for color property extensions in log messages
|
||||
var debug = require('debug')('WebSSH2')
|
||||
var Auth = require('basic-auth')
|
||||
|
||||
exports.basicAuth = function basicAuth (req, res, next) {
|
||||
var myAuth = Auth(req)
|
||||
if (myAuth && myAuth.pass !== '') {
|
||||
req.session.username = myAuth.name
|
||||
req.session.userpassword = myAuth.pass
|
||||
debug('myAuth.name: ' + myAuth.name.yellow.bold.underline +
|
||||
' and password ' + ((myAuth.pass) ? 'exists'.yellow.bold.underline
|
||||
: 'is blank'.underline.red.bold))
|
||||
next()
|
||||
} else {
|
||||
res.statusCode = 401
|
||||
debug('basicAuth credential request (401)')
|
||||
res.setHeader('WWW-Authenticate', 'Basic realm="WebSSH"')
|
||||
res.end('Username and password required for web SSH service.')
|
||||
}
|
||||
}
|
||||
|
||||
// takes a string, makes it boolean (true if the string is true, false otherwise)
|
||||
exports.parseBool = function parseBool (str) {
|
||||
return (str.toLowerCase() === 'true')
|
||||
}
|
150
app/socket.js
Normal file
|
@ -0,0 +1,150 @@
|
|||
// app/socket.js
|
||||
"use strict";
|
||||
|
||||
const debug = require("debug");
|
||||
const debugWebSSH2 = require("debug")("WebSSH2");
|
||||
const SSH = require("ssh2").Client;
|
||||
|
||||
const menuData = `
|
||||
<a id="logBtn"><i class="fas fa-clipboard fa-fw"></i> Start Log</a>
|
||||
<a id="downloadLogBtn"><i class="fas fa-download fa-fw"></i> Download Log</a>
|
||||
`;
|
||||
|
||||
module.exports = function (io) {
|
||||
io.on("connection", (socket) => {
|
||||
let conn = null;
|
||||
let stream = null;
|
||||
console.log(`SOCKET CONNECT: ${socket.id}`);
|
||||
|
||||
// Remove existing listeners to prevent duplicates
|
||||
socket.removeAllListeners("authenticate");
|
||||
socket.removeAllListeners("data");
|
||||
socket.removeAllListeners("resize");
|
||||
socket.removeAllListeners("disconnect");
|
||||
|
||||
// Authenticate user
|
||||
socket.on("authenticate", (credentials) => {
|
||||
console.log(`SOCKET AUTHENTICATE: ${socket.id}`);
|
||||
if (isValidCredentials(credentials)) {
|
||||
console.log(`SOCKET AUTHENTICATE SUCCESS: ${socket.id}`);
|
||||
initializeConnection(socket, credentials);
|
||||
} else {
|
||||
console.log(`SOCKET AUTHENTICATE FAILED: ${socket.id}`);
|
||||
socket.emit("auth_result", {
|
||||
success: false,
|
||||
message: "Invalid credentials",
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
socket.on("disconnect", (reason) => {
|
||||
debugWebSSH2(`SOCKET DISCONNECT: ${socket.id}, Reason: ${reason}`);
|
||||
if (conn) {
|
||||
conn.end();
|
||||
}
|
||||
// Clean up listeners
|
||||
socket.removeAllListeners();
|
||||
});
|
||||
|
||||
socket.on("data", (data) => {
|
||||
if (stream) {
|
||||
stream.write(data);
|
||||
}
|
||||
});
|
||||
|
||||
socket.on("resize", (data) => {
|
||||
if (stream) {
|
||||
stream.setWindow(data.rows, data.cols);
|
||||
}
|
||||
});
|
||||
|
||||
function initializeConnection(socket, credentials) {
|
||||
if (conn) {
|
||||
// If there's an existing connection, end it before creating a new one
|
||||
conn.end();
|
||||
}
|
||||
|
||||
conn = new SSH();
|
||||
|
||||
conn.on("ready", () => {
|
||||
console.log(
|
||||
`WebSSH2 Login: user=${credentials.username} from=${socket.handshake.address} host=${credentials.host} port=${credentials.port} sessionID=${socket.id}`
|
||||
);
|
||||
|
||||
socket.emit("auth_result", { success: true });
|
||||
socket.emit("menu", menuData);
|
||||
socket.emit("title", `ssh://${credentials.host}`);
|
||||
socket.emit("status", "SSH CONNECTION ESTABLISHED");
|
||||
socket.emit("statusBackground", "green");
|
||||
|
||||
conn.shell(
|
||||
{
|
||||
term: credentials.term,
|
||||
cols: credentials.cols,
|
||||
rows: credentials.rows,
|
||||
},
|
||||
(err, str) => {
|
||||
if (err) {
|
||||
return SSHerror("EXEC ERROR", err);
|
||||
}
|
||||
stream = str;
|
||||
|
||||
stream.on("data", (data) => {
|
||||
socket.emit("data", data.toString("utf-8"));
|
||||
});
|
||||
|
||||
stream.on("close", (code, signal) => {
|
||||
SSHerror("STREAM CLOSE", {
|
||||
message:
|
||||
code || signal
|
||||
? `CODE: ${code} SIGNAL: ${signal}`
|
||||
: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
stream.stderr.on("data", (data) => {
|
||||
console.log("STDERR: " + data);
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
conn.on("banner", (data) => {
|
||||
socket.emit("data", data.replace(/\r?\n/g, "\r\n"));
|
||||
});
|
||||
|
||||
conn.on("end", () => SSHerror("CONN END BY HOST"));
|
||||
conn.on("close", () => SSHerror("CONN CLOSE"));
|
||||
conn.on("error", (err) => SSHerror("CONN ERROR", err));
|
||||
|
||||
conn.connect({
|
||||
host: credentials.host,
|
||||
port: credentials.port,
|
||||
username: credentials.username,
|
||||
password: credentials.password,
|
||||
tryKeyboard: true,
|
||||
algorithms: credentials.algorithms,
|
||||
readyTimeout: credentials.readyTimeout,
|
||||
keepaliveInterval: credentials.keepaliveInterval,
|
||||
keepaliveCountMax: credentials.keepaliveCountMax,
|
||||
debug: debug("ssh2"),
|
||||
});
|
||||
}
|
||||
|
||||
function SSHerror(myFunc, err) {
|
||||
const errorMessage = err ? `: ${err.message}` : "";
|
||||
console.log(`WebSSH2 error: ${myFunc}${errorMessage}`);
|
||||
socket.emit("ssherror", `SSH ${myFunc}${errorMessage}`);
|
||||
if (conn) {
|
||||
conn.end();
|
||||
}
|
||||
// Don't disconnect the socket here, let the client handle reconnection if necessary
|
||||
// socket.disconnect(true);
|
||||
}
|
||||
|
||||
function isValidCredentials(credentials) {
|
||||
// Implement your credential validation logic here
|
||||
return credentials && credentials.username && credentials.password;
|
||||
}
|
||||
});
|
||||
};
|
13
app/util.js
Normal file
|
@ -0,0 +1,13 @@
|
|||
"use strict";
|
||||
// app/util.js
|
||||
/* jshint esversion: 6, asi: true, node: true */
|
||||
|
||||
// private
|
||||
require("colors"); // allow for color property extensions in log messages
|
||||
var debug = require("debug")("WebSSH2");
|
||||
var Auth = require("basic-auth");
|
||||
|
||||
// takes a string, makes it boolean (true if the string is true, false otherwise)
|
||||
exports.parseBool = function parseBool(str) {
|
||||
return str.toLowerCase() === "true";
|
||||
};
|
30
index.js
Normal file
|
@ -0,0 +1,30 @@
|
|||
"use strict";
|
||||
/* jshint esversion: 6, asi: true, node: true */
|
||||
/*
|
||||
* index.js
|
||||
*
|
||||
* WebSSH2 - Web to SSH2 gateway
|
||||
* Bill Church - https://github.com/billchurch/WebSSH2 - May 2017
|
||||
*
|
||||
*/
|
||||
const { server, config } = require("./app/app");
|
||||
|
||||
server.listen(config.listen.port, config.listen.ip, () => {
|
||||
console.log(
|
||||
`WebSSH2 service listening on ${config.listen.ip}:${config.listen.port}`
|
||||
);
|
||||
});
|
||||
|
||||
server.on("error", function (err) {
|
||||
if (err.code === "EADDRINUSE") {
|
||||
config.listen.port++;
|
||||
console.warn(
|
||||
"WebSSH2 Address in use, retrying on port " + config.listen.port
|
||||
);
|
||||
setTimeout(function () {
|
||||
server.listen(config.listen.port);
|
||||
}, 250);
|
||||
} else {
|
||||
console.log("WebSSH2 server.listen ERROR: " + err.code);
|
||||
}
|
||||
});
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "webssh2",
|
||||
"version": "0.2.12",
|
||||
"name": "webssh2-server",
|
||||
"version": "0.2.13",
|
||||
"ignore": [
|
||||
".gitignore"
|
||||
],
|
||||
|
@ -47,46 +47,19 @@
|
|||
},
|
||||
"scripts": {
|
||||
"start": "node index.js",
|
||||
"build": "webpack --progress --colors --config scripts/webpack.prod.js",
|
||||
"builddev": "webpack --progress --colors --config scripts/webpack.dev.js",
|
||||
"analyze": "webpack --json --config scripts/webpack.prod.js | webpack-bundle-size-analyzer",
|
||||
"test": "snyk test",
|
||||
"watch": "nodemon index.js",
|
||||
"standard": "standard --verbose --fix | snazzy",
|
||||
"cleanmac": "find . -name '.DS_Store' -type f -delete"
|
||||
},
|
||||
"standard": {
|
||||
"ignore": [
|
||||
"client/public/webssh2.bundle.js",
|
||||
"bigip/*",
|
||||
"screenshots/*",
|
||||
"bin/*",
|
||||
"build/*",
|
||||
"workspace/*"
|
||||
"build/*"
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@fortawesome/fontawesome-svg-core": "^1.2.12",
|
||||
"@fortawesome/free-solid-svg-icons": "^5.6.3",
|
||||
"clean-webpack-plugin": "^1.0.0",
|
||||
"copy-webpack-plugin": "^4.6.0",
|
||||
"cross-env": "^5.2.0",
|
||||
"css-loader": "^2.1.0",
|
||||
"extract-text-webpack-plugin": "^4.0.0-beta.0",
|
||||
"file-loader": "^3.0.1",
|
||||
"html-webpack-plugin": "^3.2.0",
|
||||
"nodaemon": "0.0.5",
|
||||
"postcss-discard-comments": "^4.0.1",
|
||||
"snazzy": "^8.0.0",
|
||||
"standard": "^12.0.1",
|
||||
"style-loader": "^0.23.1",
|
||||
"uglifyjs-webpack-plugin": "^2.1.1",
|
||||
"url-loader": "^1.1.2",
|
||||
"webpack": "^4.28.4",
|
||||
"webpack-cli": "^3.2.1",
|
||||
"webpack-merge": "^4.2.1",
|
||||
"webpack-stream": "^5.2.1",
|
||||
"xterm": "^3.10.1",
|
||||
"zip-webpack-plugin": "^4.0.1"
|
||||
"standard": "^12.0.1"
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 129 KiB |
Before Width: | Height: | Size: 131 KiB |
Before Width: | Height: | Size: 186 KiB |
Before Width: | Height: | Size: 187 KiB |
Before Width: | Height: | Size: 130 KiB |
Before Width: | Height: | Size: 2.5 MiB |
|
@ -1,42 +0,0 @@
|
|||
#!/bin/bash
|
||||
## Syncs from BIG-IP and builds a release based on version in extensions/ephemeral_auth/package.json
|
||||
#
|
||||
source ./scripts/env.sh
|
||||
source ./scripts/util.sh
|
||||
|
||||
./scripts/pull.sh
|
||||
if [ $? -ne 0 ]; then
|
||||
# failure
|
||||
tput bel;tput bel;tput bel;tput bel
|
||||
echo -e "\n${fgLtRed}Pull command failed. Giving up.${fgLtWhi}\n"
|
||||
echo ${output}
|
||||
exit 255
|
||||
fi
|
||||
|
||||
# get version of package from package.json
|
||||
package_version=$(jq -r ".version" workspace/extensions/webssh2/package.json)
|
||||
# creates new workspace name with version
|
||||
webssh_workspace_name=$webssh_workspace_name-$package_version
|
||||
|
||||
echoNotice "Creating workspace package"
|
||||
runCommand "ssh -o ClearAllForwardings=yes $webssh_ilxhost /bin/tar --exclude='./extensions/webssh2/config.json' -czf - -C /var/ilx/workspaces/Common/$webssh_workspace_name . > Build/Release/$webssh_package_name-$package_version.tgz"
|
||||
|
||||
echoNotice "Creating SHA256 hash"
|
||||
runCommand "shasum -a 256 Build/Release/$webssh_package_name-$package_version.tgz > Build/Release/$webssh_package_name-$package_version.tgz.sha256"
|
||||
|
||||
echoNotice "Copying to current"
|
||||
runCommand "cp Build/Release/$webssh_package_name-$package_version.tgz $webssh_pua_location/$webssh_package_name-current.tgz && \
|
||||
cp Build/Release/$webssh_package_name-$package_version.tgz.sha256 $webssh_pua_location/$webssh_package_name-current.tgz.sha256"
|
||||
|
||||
echoNotice "Deleting any '.DS_Store' files"
|
||||
runCommand "find . -name '.DS_Store' -type f -delete"
|
||||
|
||||
echo -e "\nWorkspace packages located at:\n"
|
||||
echo " Build/Release/$webssh_package_name-$package_version.tgz"
|
||||
echo " Build/Release/$webssh_package_name-$package_version.tgz.sha256"
|
||||
echo " $webssh_pua_location/$webssh_package_name-current.tgz"
|
||||
echo " $webssh_pua_location/$webssh_package_name-current.tgz.sha256"
|
||||
|
||||
echo -e "\n👍 Build Complete 👍\n"
|
||||
|
||||
exit 0
|
|
@ -1,6 +0,0 @@
|
|||
#!/bin/sh
|
||||
#webssh_ilxhost=root@192.168.30.209
|
||||
webssh_ilxhost=root@192.168.30.203
|
||||
webssh_workspace_name=webssh2
|
||||
webssh_package_name=BIG-IP-ILX-WebSSH2
|
||||
webssh_pua_location=./bin
|
|
@ -1,30 +0,0 @@
|
|||
#!/bin/bash
|
||||
#
|
||||
# ./scripts/pull.sh
|
||||
#
|
||||
# bill@f5.com
|
||||
#
|
||||
# Pulls an ILX workspace from a BIG-IP and syncs to ./workspace, excludes
|
||||
# ./workspace/extensions/ephemeral_auth/node_modules.
|
||||
#
|
||||
source ./scripts/env.sh
|
||||
source ./scripts/util.sh
|
||||
|
||||
# get version of package from package.json
|
||||
PACKAGE_VERSION=$(jq -r ".version" workspace/extensions/webssh2/package.json 2>&1)
|
||||
# creates new workspace name with version
|
||||
webssh_workspace_name=$webssh_workspace_name-$PACKAGE_VERSION
|
||||
|
||||
echo "Pull ${fgLtCya}$webssh_workspace_name${fgLtWhi} from ${fgLtCya}$webssh_ilxhost${fgLtWhi}"
|
||||
|
||||
# check to see if the workspace actually exists before attempting to copy over
|
||||
|
||||
echoNotice "Checking for existing workspace ${fgLtCya}$webssh_workspace_name${fgLtWhi}"
|
||||
runCommand "ssh -o ClearAllForwardings=yes $webssh_ilxhost tmsh list ilx workspace $webssh_workspace_name one-line 2>&1"
|
||||
|
||||
echoNotice "Pulling ${fgLtCya}$webssh_workspace_name${fgLtWhi} from ${fgLtCya}$webssh_ilxhost${fgLtWhi}"
|
||||
runCommand "rsync -e 'ssh -o ClearAllForwardings=yes -ax' -avq --include=\"extensions/ephemeral_auth/node_modules/f5-*\" --exclude=\".DS_Store\" --exclude=\"extensions/ephemeral_auth/node_modules/*\" $webssh_ilxhost:/var/ilx/workspaces/Common/$webssh_workspace_name/. workspace/. 2>&1"
|
||||
|
||||
echo -e "\n👍 Pull complete 👍\n"
|
||||
|
||||
exit 0
|
|
@ -1,61 +0,0 @@
|
|||
#!/bin/bash
|
||||
#
|
||||
# ./scripts/push.sh
|
||||
#
|
||||
# bill@f5.com
|
||||
#
|
||||
# Pushes ./workspace to a BIG-IP ILX workspace
|
||||
#
|
||||
source ./scripts/env.sh
|
||||
source ./scripts/util.sh
|
||||
|
||||
# get version of package from package.json
|
||||
PACKAGE_VERSION=$(jq -r ".version" workspace/extensions/webssh2/package.json 2>&1)
|
||||
# creates new workspace name with version
|
||||
webssh_workspace_name=$webssh_workspace_name-$PACKAGE_VERSION
|
||||
|
||||
echo "Push ${fgLtCya}$webssh_workspace_name${fgLtWhi} to ${fgLtCya}$webssh_ilxhost${fgLtWhi}"
|
||||
|
||||
echoNotice "Checking $webssh_ilxhost for workspace $webssh_workspace_name"
|
||||
output=$(ssh -o ClearAllForwardings=yes $webssh_ilxhost tmsh list ilx workspace $webssh_workspace_name one-line 2>&1)
|
||||
result="$?" 2>&1
|
||||
if [ $result -ne 0 ]; then
|
||||
echo "❌"
|
||||
echoNotice "Attempting to create workspace"
|
||||
runCommand "ssh -o ClearAllForwardings=yes $webssh_ilxhost \"tmsh create ilx workspace $webssh_workspace_name node-version 6.9.1\" 2>&1"
|
||||
else
|
||||
echo "✅"
|
||||
fi
|
||||
|
||||
echoNotice "Pushing ./workspace to $webssh_ilxhost at $webssh_workspace_name"
|
||||
runCommand "rsync -e 'ssh -o ClearAllForwardings=yes -ax' -avq --delete --exclude='.DS_Store' --exclude extensions/webssh2/node_modules workspace/. $webssh_ilxhost:/var/ilx/workspaces/Common/$webssh_workspace_name/."
|
||||
|
||||
echoNotice "Installing node modules at $webssh_workspace_name on $webssh_ilxhost"
|
||||
runCommand "ssh -o ClearAllForwardings=yes $webssh_ilxhost \"cd /var/ilx/workspaces/Common/$webssh_workspace_name/extensions/webssh2; npm i --production\" 2>&1"
|
||||
|
||||
echoNotice "Setting permissions at $webssh_workspace_name on $webssh_ilxhost"
|
||||
runCommand "ssh -o ClearAllForwardings=yes $webssh_ilxhost \"chown -R root.sdm /var/ilx/workspaces/Common/$webssh_workspace_name/; \
|
||||
chmod -R ug+rwX,o-w /var/ilx/workspaces/Common/$webssh_workspace_name/; \
|
||||
chmod u+rw,go-w /var/ilx/workspaces/Common/$webssh_workspace_name/version; \
|
||||
chmod u+rw,go-w /var/ilx/workspaces/Common/$webssh_workspace_name/node_version\" 2>&1"
|
||||
|
||||
echoNotice "Deleting $webssh_workspace_name/node_modules/.bin on $webssh_ilxhost"
|
||||
runCommand "ssh -o ClearAllForwardings=yes $webssh_ilxhost \"cd /var/ilx/workspaces/Common/$webssh_workspace_name/extensions/webssh2; rm -rf node_modules/.bin\" 2>&1"
|
||||
|
||||
# switch plugin to new workspace
|
||||
echoNotice "Checking to see if plugin exists"
|
||||
output=$(ssh -o ClearAllForwardings=yes $webssh_ilxhost tmsh list ilx plugin WebSSH_plugin one-line 2>&1)
|
||||
result="$?" 2>&1
|
||||
if [ $result -ne 0 ]; then
|
||||
echo "❌"
|
||||
echoNotice "Attempting to create plugin"
|
||||
runCommand "ssh -o ClearAllForwardings=yes $webssh_ilxhost tmsh create ilx plugin WebSSH_plugin from-workspace $webssh_workspace_name extensions { webssh2 { concurrency-mode single ilx-logging enabled } } 2>&1"
|
||||
else
|
||||
echo "✅"
|
||||
echoNotice "Switching plugin to $webssh_workspace_name"
|
||||
runCommand "ssh -o ClearAllForwardings=yes $webssh_ilxhost tmsh modify ilx plugin WebSSH_plugin from-workspace $webssh_workspace_name extensions { webssh2 { concurrency-mode single ilx-logging enabled } } 2>&1"
|
||||
fi
|
||||
|
||||
echo -e "\n👍 Push complete 👍\n"
|
||||
|
||||
exit 0
|
|
@ -1,74 +0,0 @@
|
|||
#!/bin/bash
|
||||
# Utility functions / scripts
|
||||
|
||||
echoNotice () { echo -e -n "\n$@... "; }
|
||||
|
||||
fgLtRed=$(tput bold;tput setaf 1)
|
||||
fgLtGrn=$(tput bold;tput setaf 2)
|
||||
fgLtYel=$(tput bold;tput setaf 3)
|
||||
fgLtBlu=$(tput bold;tput setaf 4)
|
||||
fgLtMag=$(tput bold;tput setaf 5)
|
||||
fgLtCya=$(tput bold;tput setaf 6)
|
||||
fgLtWhi=$(tput bold;tput setaf 7)
|
||||
fgLtGry=$(tput bold;tput setaf 8)
|
||||
|
||||
echo ${fgLtWhi}
|
||||
|
||||
# check for jq and try to install...
|
||||
output=$(which jq 2>&1)
|
||||
if [[ $? -ne 0 ]]; then
|
||||
echo -e "You need to install jq: https://stedolan.github.io/jq\n"
|
||||
echo -e "If you have *brew* you can install with:\n"
|
||||
echo -e " brew install jq\n"
|
||||
echo -n "Do you want me to try and install that for you (Y/n)? "
|
||||
read -n1 yesno
|
||||
echo
|
||||
if [[ ("$yesno" != "y") ]]; then
|
||||
echo -e "\nUnable to continue, install jq first.\n\n"
|
||||
exit 255
|
||||
else
|
||||
which brew
|
||||
if [[ $? -ne 0 ]]; then
|
||||
echo -e "\nYou're a mess... You don't even have brew installed...\nMaybe you should check it out\n"
|
||||
echo -e " https://brew.sh/\n\n"
|
||||
exit 255
|
||||
fi
|
||||
echo
|
||||
brew install jq
|
||||
if [[ $? -ne 0 ]]; then
|
||||
echo -e "\nLooks like that failed, I can't do everything... Quitting, install jq...\n"
|
||||
exit 255
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# checks the output of a command to get the status and report/handle failure
|
||||
checkOutput() {
|
||||
if [ $result -eq 0 ]; then
|
||||
# success
|
||||
#echo "${fgLtGrn}[OK]${fgLtWhi}"
|
||||
echo "✅"
|
||||
return
|
||||
else
|
||||
# failure
|
||||
tput bel;tput bel;tput bel;tput bel
|
||||
#echo "${fgLtRed}[FAILED]${fgLtWhi}"
|
||||
echo "❌"
|
||||
echo -e "\nPrevious command failed in ${script_path}/${scriptname} with error level: ${result}"
|
||||
echo -e "\nCommand:\n"
|
||||
echo " ${command}"
|
||||
echo -e "\nSTDOUT/STDERR:\n"
|
||||
echo ${output}
|
||||
exit 255
|
||||
fi
|
||||
}
|
||||
|
||||
# run a comand and check call checkOutput
|
||||
runCommand() {
|
||||
# $1 command
|
||||
command=$@
|
||||
output=$((eval $command) 2>&1)
|
||||
result="$?" 2>&1
|
||||
prevline=$(($LINENO-2))
|
||||
checkOutput
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
#!/bin/bash
|
||||
## displays and optionally changes version of product
|
||||
|
||||
source ./scripts/env.sh
|
||||
|
||||
source ./scripts/util.sh
|
||||
|
||||
echo
|
||||
# get current version of workspace, ask to change or rebuild
|
||||
webssh_ilx_ver=$(jq -r ".version" ./workspace/extensions/webssh2/package.json 2>&1)
|
||||
if [[ $? -ne 0 ]]; then exit; echo "error reading ILX irule version";fi
|
||||
|
||||
echo "Current version of $webssh_workspace_name is: $webssh_ilx_ver"
|
||||
|
||||
echo -n "If you want to change this version, enter it now otherwise press enter to retain: "
|
||||
|
||||
read newver
|
||||
|
||||
echo
|
||||
|
||||
if [[ ("$newver" != "") ]]; then
|
||||
echo "Updating version of ILX to: $newver"
|
||||
export newver
|
||||
jq --arg newver "$newver" '.version = $newver' < ./workspace/extensions/webssh2/package.json > ./workspace/extensions/webssh2/package.json.new
|
||||
if [[ $? -ne 0 ]]; then exit; echo "error changing version - ilx";fi
|
||||
mv ./workspace/extensions/webssh2/package.json.new ./workspace/extensions/webssh2/package.json
|
||||
else
|
||||
echo "No changes made"
|
||||
fi
|