mirror of
https://github.com/paradoxxxzero/butterfly.git
synced 2026-06-10 06:14:39 +00:00
Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
920c435b00 | ||
|
|
27e6aa8a5d | ||
|
|
92633f52ce | ||
|
|
f5f854964b | ||
|
|
55528fdf91 | ||
|
|
9eae13486e | ||
|
|
79bd074dae | ||
|
|
7b0ba2bfe7 | ||
|
|
db17b9d8ac | ||
|
|
b5de82bfcf | ||
|
|
13dbe0434c | ||
|
|
ef0057c23f | ||
|
|
6bc8e1438f | ||
|
|
8856ea9dc4 | ||
|
|
4edb2d269f | ||
|
|
272891470c | ||
|
|
574b3dc74b | ||
|
|
269dd2b618 | ||
|
|
0625e05cbb | ||
|
|
6b1101bc45 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -9,4 +9,4 @@ sass/scss
|
||||
*.egg-info/
|
||||
build/
|
||||
.cache/
|
||||
.env/
|
||||
.env*
|
||||
|
||||
25
CHANGELOG.md
25
CHANGELOG.md
@@ -1,3 +1,28 @@
|
||||
3.2.2
|
||||
=====
|
||||
|
||||
* Fix unescaping entities when linkifying
|
||||
|
||||
3.2.1
|
||||
=====
|
||||
|
||||
* Issue correct X.509 v3 certificates (you will need to re-generate your certs)
|
||||
|
||||
3.1.5
|
||||
=====
|
||||
|
||||
* Fix new option in older tornado version. (#146 thanks @warpkwd)
|
||||
|
||||
3.1.4
|
||||
=====
|
||||
|
||||
* Add --i-hereby-declare-i-dont-want-any-security-whatsoever option (#143)
|
||||
|
||||
3.1.3
|
||||
=====
|
||||
|
||||
* Fix lsof parsing crash on python 2
|
||||
|
||||
3.1.0
|
||||
=====
|
||||
|
||||
|
||||
5
Makefile
5
Makefile
@@ -4,7 +4,7 @@ include Makefile.config
|
||||
all: install lint check-outdated run-debug
|
||||
|
||||
install:
|
||||
test -d $(VENV) || virtualenv $(VENV)
|
||||
test -d $(VENV) || virtualenv $(VENV) -p $(PYTHON_VERSION)
|
||||
$(PIP) install --upgrade --no-cache pip setuptools -e .[lint] devcore
|
||||
$(NPM) install
|
||||
|
||||
@@ -20,9 +20,10 @@ lint:
|
||||
check-outdated:
|
||||
$(PIP) list --outdated --format=columns
|
||||
|
||||
ARGS ?= --port=1212 --unsecure --debug
|
||||
run-debug:
|
||||
sleep 0.5 && $(BROWSER) http://localhost:1212&
|
||||
$(PYTHON) ./butterfly.server.py --port=1212 --unsecure --debug
|
||||
$(PYTHON) ./butterfly.server.py $(ARGS)
|
||||
|
||||
build-coffee:
|
||||
$(NODE_MODULES)/.bin/grunt
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
PROJECT_NAME = butterfly
|
||||
|
||||
# Python env
|
||||
VENV = $(PWD)/.env
|
||||
PYTHON_VERSION ?= python
|
||||
VENV = $(PWD)/.env$(if $(filter $(PYTHON_VERSION),python),,-$(PYTHON_VERSION))
|
||||
PIP = $(VENV)/bin/pip
|
||||
PYTHON = $(VENV)/bin/python
|
||||
PYTEST = $(VENV)/bin/py.test
|
||||
|
||||
@@ -45,7 +45,8 @@ tornado.options.define("unminified", default=False,
|
||||
tornado.options.define("host", default='localhost', help="Server host")
|
||||
tornado.options.define("port", default=57575, type=int, help="Server port")
|
||||
tornado.options.define("keepalive_interval", default=30, type=int,
|
||||
help="Interval between ping packets sent from server to client (in seconds)")
|
||||
help="Interval between ping packets sent from server "
|
||||
"to client (in seconds)")
|
||||
tornado.options.define("one_shot", default=False,
|
||||
help="Run a one-shot instance. Quit at term close")
|
||||
tornado.options.define("shell", help="Shell to execute at login")
|
||||
@@ -54,10 +55,18 @@ tornado.options.define("cmd",
|
||||
help="Command to run instead of shell, f.i.: 'ls -l'")
|
||||
tornado.options.define("unsecure", default=False,
|
||||
help="Don't use ssl not recommended")
|
||||
tornado.options.define("i_hereby_declare_i_dont_want_any_security_whatsoever",
|
||||
default=False,
|
||||
help="Remove all security and warnings. There are some "
|
||||
"use cases for that. Use this if you really know what "
|
||||
"you are doing.")
|
||||
tornado.options.define("login", default=False,
|
||||
help="Use login screen at start")
|
||||
tornado.options.define("pam_profile", default="", type=str,
|
||||
help="When --login=True provided and running as ROOT, use PAM with the specified PAM profile for authentication and then execute the user's default shell. Will override --shell.")
|
||||
help="When --login=True provided and running as ROOT, "
|
||||
"use PAM with the specified PAM profile for "
|
||||
"authentication and then execute the user's default "
|
||||
"shell. Will override --shell.")
|
||||
tornado.options.define("force_unicode_width",
|
||||
default=False,
|
||||
help="Force all unicode characters to the same width."
|
||||
@@ -76,6 +85,7 @@ tornado.options.define("uri_root_path", default='',
|
||||
help="Sets the servier root path: "
|
||||
"example.com/<uri_root_path>/static/")
|
||||
|
||||
|
||||
if os.getuid() == 0:
|
||||
ev = os.getenv('XDG_CONFIG_DIRS', '/etc')
|
||||
else:
|
||||
@@ -131,6 +141,9 @@ log = logging.getLogger('butterfly')
|
||||
host = options.host
|
||||
port = options.port
|
||||
|
||||
if options.i_hereby_declare_i_dont_want_any_security_whatsoever:
|
||||
options.unsecure = True
|
||||
|
||||
|
||||
if not os.path.exists(options.ssl_dir):
|
||||
os.makedirs(options.ssl_dir)
|
||||
@@ -165,6 +178,9 @@ def read(file):
|
||||
with open(file, 'rb') as fd:
|
||||
return fd.read()
|
||||
|
||||
def b(s):
|
||||
return s.encode('utf-8')
|
||||
|
||||
|
||||
if options.generate_certs:
|
||||
from OpenSSL import crypto
|
||||
@@ -175,6 +191,7 @@ if options.generate_certs:
|
||||
ca_pk = crypto.PKey()
|
||||
ca_pk.generate_key(crypto.TYPE_RSA, 2048)
|
||||
ca_cert = crypto.X509()
|
||||
ca_cert.set_version(2)
|
||||
ca_cert.get_subject().CN = 'Butterfly CA on %s' % socket.gethostname()
|
||||
fill_fields(ca_cert.get_subject())
|
||||
ca_cert.set_serial_number(uuid.uuid4().int)
|
||||
@@ -182,6 +199,21 @@ if options.generate_certs:
|
||||
ca_cert.gmtime_adj_notAfter(315360000) # to 10y
|
||||
ca_cert.set_issuer(ca_cert.get_subject()) # Self signed
|
||||
ca_cert.set_pubkey(ca_pk)
|
||||
ca_cert.add_extensions([
|
||||
crypto.X509Extension(
|
||||
b('basicConstraints'), True, b('CA:TRUE, pathlen:0')),
|
||||
crypto.X509Extension(
|
||||
b('keyUsage'), True, b('keyCertSign, cRLSign')),
|
||||
crypto.X509Extension(
|
||||
b('subjectKeyIdentifier'), False, b('hash'), subject=ca_cert),
|
||||
])
|
||||
ca_cert.add_extensions([
|
||||
crypto.X509Extension(
|
||||
b('authorityKeyIdentifier'), False,
|
||||
b('issuer:always, keyid:always'),
|
||||
issuer=ca_cert, subject=ca_cert
|
||||
)
|
||||
])
|
||||
ca_cert.sign(ca_pk, 'sha512')
|
||||
|
||||
write(ca, crypto.dump_certificate(crypto.FILETYPE_PEM, ca_cert))
|
||||
@@ -195,12 +227,23 @@ if options.generate_certs:
|
||||
server_pk = crypto.PKey()
|
||||
server_pk.generate_key(crypto.TYPE_RSA, 2048)
|
||||
server_cert = crypto.X509()
|
||||
server_cert.set_version(2)
|
||||
server_cert.get_subject().CN = host
|
||||
alt = 'subjectAltName'
|
||||
value = 'DNS:%s' % host
|
||||
server_cert.add_extensions([crypto.X509Extension(
|
||||
alt.encode('utf-8'), False, value.encode('utf-8'))])
|
||||
|
||||
server_cert.add_extensions([
|
||||
crypto.X509Extension(
|
||||
b('basicConstraints'), False, b('CA:FALSE')),
|
||||
crypto.X509Extension(
|
||||
b('subjectKeyIdentifier'), False, b('hash'), subject=server_cert),
|
||||
crypto.X509Extension(
|
||||
b('subjectAltName'), False, b('DNS:%s' % host)),
|
||||
])
|
||||
server_cert.add_extensions([
|
||||
crypto.X509Extension(
|
||||
b('authorityKeyIdentifier'), False,
|
||||
b('issuer:always, keyid:always'),
|
||||
issuer=ca_cert, subject=ca_cert
|
||||
)
|
||||
])
|
||||
fill_fields(server_cert.get_subject())
|
||||
server_cert.set_serial_number(uuid.uuid4().int)
|
||||
server_cert.gmtime_adj_notBefore(0) # From now
|
||||
@@ -250,6 +293,7 @@ if (options.generate_current_user_pkcs or
|
||||
client_pk.generate_key(crypto.TYPE_RSA, 2048)
|
||||
|
||||
client_cert = crypto.X509()
|
||||
client_cert.set_version(2)
|
||||
client_cert.get_subject().CN = user
|
||||
fill_fields(client_cert.get_subject())
|
||||
client_cert.set_serial_number(uuid.uuid4().int)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
__title__ = "butterfly"
|
||||
__version__ = "3.1.2"
|
||||
__version__ = "3.2.2"
|
||||
|
||||
__summary__ = "A sleek web based terminal emulator"
|
||||
__uri__ = "https://github.com/paradoxxxzero/butterfly"
|
||||
|
||||
@@ -51,6 +51,8 @@ body
|
||||
|
||||
.extra
|
||||
display: block
|
||||
white-space: pre-wrap
|
||||
word-break: break-all
|
||||
|
||||
&::-webkit-scrollbar
|
||||
background: $scroll-bg
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
(function() {
|
||||
var Popup, Selection, _set_theme_href, _theme, alt, cancel, clean_ansi, copy, ctrl, first, histSize, linkify, maybePack, nextLeaf, packSize, popup, previousLeaf, selection, setAlarm, tid, virtualInput, walk,
|
||||
var Popup, Selection, _set_theme_href, _theme, alt, cancel, clean_ansi, copy, ctrl, escape, first, histSize, linkify, maybePack, nextLeaf, packSize, popup, previousLeaf, selection, setAlarm, tags, tid, virtualInput, walk,
|
||||
indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
|
||||
|
||||
clean_ansi = function(data) {
|
||||
@@ -202,12 +202,25 @@
|
||||
return text.replace(urlPattern, '<a href="$&">$&</a>').replace(pseudoUrlPattern, '$1<a href="http://$2">$2</a>').replace(emailAddressPattern, '<a href="mailto:$&">$&</a>');
|
||||
};
|
||||
|
||||
tags = {
|
||||
'&': '&',
|
||||
'<': '<',
|
||||
'>': '>'
|
||||
};
|
||||
|
||||
escape = function(s) {
|
||||
return s.replace(/[&<>]/g, function(tag) {
|
||||
return tags[tag] || tag;
|
||||
});
|
||||
};
|
||||
|
||||
Terminal.on('change', function(line) {
|
||||
return walk(line, function() {
|
||||
var linkified, newNode;
|
||||
var linkified, newNode, val;
|
||||
if (this.nodeType === 3) {
|
||||
linkified = linkify(this.nodeValue);
|
||||
if (linkified !== this.nodeValue) {
|
||||
val = this.nodeValue;
|
||||
linkified = linkify(escape(val));
|
||||
if (linkified !== val) {
|
||||
newNode = document.createElement('span');
|
||||
newNode.innerHTML = linkified;
|
||||
this.parentElement.replaceChild(newNode, this);
|
||||
|
||||
4
butterfly/static/ext.min.js
vendored
4
butterfly/static/ext.min.js
vendored
File diff suppressed because one or more lines are too long
@@ -2827,7 +2827,9 @@ body {
|
||||
cursor: zoom-out;
|
||||
background-color: #09080a; }
|
||||
body .line.extended.expanded .extra {
|
||||
display: block; }
|
||||
display: block;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all; }
|
||||
body::-webkit-scrollbar {
|
||||
background: #110f13;
|
||||
width: 0.75em; }
|
||||
|
||||
2
butterfly/static/main.min.js
vendored
2
butterfly/static/main.min.js
vendored
File diff suppressed because one or more lines are too long
@@ -19,7 +19,7 @@
|
||||
! ! {{ colors.red if opts.unsecure else colors.green }}{{ butterfly.socket.remote_addr }}:{{ butterfly.socket.remote_port }}{{ colors.reset }}
|
||||
|
||||
For more information type: {{ colors.white }}$ {{ colors.green }}butterfly help{{ colors.reset }}
|
||||
{% if opts.unsecure %}{{ colors.light_red + '\x1b[5m' }}/!\{{ colors.reset }} {{ colors.red }}This session is UNSECURE everyone can access you terminal at:
|
||||
{% if opts.unsecure and not opts.i_hereby_declare_i_dont_want_any_security_whatsoever %}{{ colors.light_red + '\x1b[5m' }}/!\{{ colors.reset }} {{ colors.red }}This session is UNSECURE everyone can access you terminal at:
|
||||
{{ uri }}
|
||||
{% else %}You can share your session with the following uri:
|
||||
{{ uri }}
|
||||
|
||||
@@ -196,19 +196,19 @@ class Terminal(object):
|
||||
tty, os.getpid(),
|
||||
self.callee.name, self.uri)
|
||||
|
||||
if not tornado.options.options.unsecure or (
|
||||
self.socket.local and
|
||||
self.caller == self.callee and
|
||||
server == self.callee
|
||||
) and not tornado.options.options.login:
|
||||
local_login = (
|
||||
self.socket.local and self.caller == self.callee and
|
||||
server == self.callee)
|
||||
secure = not tornado.options.options.unsecure
|
||||
force_login = tornado.options.options.login
|
||||
ignore_security = (
|
||||
tornado.options.options.
|
||||
i_hereby_declare_i_dont_want_any_security_whatsoever)
|
||||
|
||||
if not force_login and (ignore_security or secure or local_login):
|
||||
# User has been auth with ssl or is the same user as server
|
||||
# or login is explicitly turned off
|
||||
if (
|
||||
not tornado.options.options.unsecure and not (
|
||||
self.socket.local and
|
||||
self.caller == self.callee and
|
||||
server == self.callee
|
||||
)):
|
||||
if secure and not local_login:
|
||||
# User is authed by ssl, setting groups
|
||||
try:
|
||||
os.initgroups(self.callee.name, self.callee.gid)
|
||||
@@ -264,7 +264,7 @@ class Terminal(object):
|
||||
args = ['/bin/su']
|
||||
|
||||
args.append('-l')
|
||||
if sys.platform == 'linux' and tornado.options.options.shell:
|
||||
if sys.platform.startswith('linux') and tornado.options.options.shell:
|
||||
args.append('-s')
|
||||
args.append(tornado.options.options.shell)
|
||||
args.append(self.callee.name)
|
||||
|
||||
@@ -168,7 +168,7 @@ def get_lsof_socket_line(addr, port):
|
||||
# May want to make this into a dictionary in the future...
|
||||
regex = "\w+\s+(?P<pid>\d+)\s+(?P<user>\w+).*\s" \
|
||||
"(?P<laddr>.*?):(?P<lport>\d+)->(?P<raddr>.*?):(?P<rport>\d+)"
|
||||
output = subprocess.check_output(['lsof', '-Pni'])
|
||||
output = subprocess.check_output(['lsof', '-Pni']).decode('utf-8')
|
||||
lines = output.split('\n')
|
||||
for line in lines:
|
||||
# Look for local address with peer port
|
||||
|
||||
@@ -14,11 +14,19 @@ linkify = (text) ->
|
||||
.replace(pseudoUrlPattern, '$1<a href="http://$2">$2</a>')
|
||||
.replace(emailAddressPattern, '<a href="mailto:$&">$&</a>')
|
||||
|
||||
tags =
|
||||
'&': '&'
|
||||
'<': '<'
|
||||
'>': '>'
|
||||
|
||||
escape = (s) -> s.replace(/[&<>]/g, (tag) -> tags[tag] or tag)
|
||||
|
||||
Terminal.on 'change', (line) ->
|
||||
walk line, ->
|
||||
if @nodeType is 3
|
||||
linkified = linkify @nodeValue
|
||||
if linkified isnt @nodeValue
|
||||
val = @nodeValue
|
||||
linkified = linkify escape(val)
|
||||
if linkified isnt val
|
||||
newNode = document.createElement('span')
|
||||
newNode.innerHTML = linkified
|
||||
@parentElement.replaceChild newNode, @
|
||||
|
||||
Reference in New Issue
Block a user