all lists on lists.proxmox.com
 help / color / mirror / Atom feed
* [pve-devel] [PATCH xtermjs v2 0/4] rewrite in rust
@ 2020-07-14 11:51 Dominik Csapak
  2020-07-14 11:51 ` [pve-devel] [PATCH xtermjs v2 1/4] termproxy: " Dominik Csapak
                   ` (3 more replies)
  0 siblings, 4 replies; 5+ messages in thread
From: Dominik Csapak @ 2020-07-14 11:51 UTC (permalink / raw)
  To: pve-devel

this series includes a rewrite of termproxy in rust,
and some smaller ui improvements

changes from v1:
* incoroporate suggestions from wolfgang, thx :)
* fix a bug where we did not exhaust reads from either side before
  closing, discarding possibly some information
* include a handlebars template (for pbs)
* improves ui error handling
* RFC: prevent accidental closing of terminal window

needs my previous patches[0] for the proxmox crate (and a bump
of the version after; also in the Cargo.toml here)

also missing a bump and changelog entry

0: https://lists.proxmox.com/pipermail/pbs-devel/2020-July/000047.html

Dominik Csapak (4):
  termproxy: rewrite in rust
  add handlebars template
  ui: improve error message handling
  ui: prevent accidental closing of terminal window

 .cargo/config                   |   5 +
 Cargo.toml                      |  14 ++
 Makefile                        |  52 ++--
 debian/compat                   |   1 -
 debian/control                  |  16 --
 debian/debcargo.toml            |  14 ++
 debian/install                  |   1 +
 debian/rules                    |   8 +-
 debian/source/format            |   1 -
 debian/source/lintian-overrides |   4 +-
 src/Makefile                    |   7 -
 src/PVE/CLI/Makefile            |   8 -
 src/PVE/CLI/termproxy.pm        | 250 -------------------
 src/PVE/Makefile                |   3 -
 src/bin/Makefile                |   7 -
 src/bin/termproxy               |   8 -
 src/main.rs                     | 421 ++++++++++++++++++++++++++++++++
 src/www/Makefile                |  21 --
 src/www/index.html.hbs.in       |  23 ++
 src/www/main.js                 |  34 ++-
 20 files changed, 551 insertions(+), 347 deletions(-)
 create mode 100644 .cargo/config
 create mode 100644 Cargo.toml
 delete mode 100644 debian/compat
 delete mode 100644 debian/control
 create mode 100644 debian/debcargo.toml
 create mode 100644 debian/install
 delete mode 100644 debian/source/format
 delete mode 100644 src/Makefile
 delete mode 100644 src/PVE/CLI/Makefile
 delete mode 100644 src/PVE/CLI/termproxy.pm
 delete mode 100644 src/PVE/Makefile
 delete mode 100644 src/bin/Makefile
 delete mode 100755 src/bin/termproxy
 create mode 100644 src/main.rs
 delete mode 100644 src/www/Makefile
 create mode 100644 src/www/index.html.hbs.in

-- 
2.20.1





^ permalink raw reply	[flat|nested] 5+ messages in thread

* [pve-devel] [PATCH xtermjs v2 1/4] termproxy: rewrite in rust
  2020-07-14 11:51 [pve-devel] [PATCH xtermjs v2 0/4] rewrite in rust Dominik Csapak
@ 2020-07-14 11:51 ` Dominik Csapak
  2020-07-14 11:51 ` [pve-devel] [PATCH xtermjs v2 2/4] add handlebars template Dominik Csapak
                   ` (2 subsequent siblings)
  3 siblings, 0 replies; 5+ messages in thread
From: Dominik Csapak @ 2020-07-14 11:51 UTC (permalink / raw)
  To: pve-devel

termproxy is now completely written in rust (instead of perl) but
it is a drop-in replacement

this contains all other necessary changes to the build-system
for it to successfully build

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
changes from v1:
* include wolfgangs suggestions
* exhaust reads/writes before closing

 .cargo/config                   |   5 +
 Cargo.toml                      |  14 ++
 Makefile                        |  52 ++--
 debian/compat                   |   1 -
 debian/control                  |  16 --
 debian/debcargo.toml            |  14 ++
 debian/install                  |   1 +
 debian/rules                    |   7 +-
 debian/source/format            |   1 -
 debian/source/lintian-overrides |   4 +-
 src/Makefile                    |   7 -
 src/PVE/CLI/Makefile            |   8 -
 src/PVE/CLI/termproxy.pm        | 250 -------------------
 src/PVE/Makefile                |   3 -
 src/bin/Makefile                |   7 -
 src/bin/termproxy               |   8 -
 src/main.rs                     | 421 ++++++++++++++++++++++++++++++++
 src/www/Makefile                |  21 --
 18 files changed, 501 insertions(+), 339 deletions(-)
 create mode 100644 .cargo/config
 create mode 100644 Cargo.toml
 delete mode 100644 debian/compat
 delete mode 100644 debian/control
 create mode 100644 debian/debcargo.toml
 create mode 100644 debian/install
 delete mode 100644 debian/source/format
 delete mode 100644 src/Makefile
 delete mode 100644 src/PVE/CLI/Makefile
 delete mode 100644 src/PVE/CLI/termproxy.pm
 delete mode 100644 src/PVE/Makefile
 delete mode 100644 src/bin/Makefile
 delete mode 100755 src/bin/termproxy
 create mode 100644 src/main.rs
 delete mode 100644 src/www/Makefile

diff --git a/.cargo/config b/.cargo/config
new file mode 100644
index 0000000..3b5b6e4
--- /dev/null
+++ b/.cargo/config
@@ -0,0 +1,5 @@
+[source]
+[source.debian-packages]
+directory = "/usr/share/cargo/registry"
+[source.crates-io]
+replace-with = "debian-packages"
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..55869ac
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,14 @@
+[package]
+name = "termproxy"
+version = "4.3.0"
+authors = ["Dominik Csapak <d.csapak@proxmox.com>"]
+edition = "2018"
+license = "AGPL-3"
+
+exclude = [ "build", "debian" ]
+
+[dependencies]
+mio = "0.6"
+curl = "0.4"
+clap = "2.33"
+proxmox = { version = "0.2.0", default-features = false }
diff --git a/Makefile b/Makefile
index d4aeee4..7a73fe7 100644
--- a/Makefile
+++ b/Makefile
@@ -1,6 +1,8 @@
 include /usr/share/dpkg/pkg-info.mk
+include /usr/share/dpkg/architecture.mk
 
 PACKAGE=pve-xtermjs
+CRATENAME=termproxy
 
 export VERSION=${DEB_VERSION_UPSTREAM_REVISION}
 
@@ -11,31 +13,53 @@ FITADDONVER=0.4.0
 FITADDONTGZ=xterm-addon-fit-${FITADDONVER}.tgz
 
 SRCDIR=src
-BUILDDIR ?= ${PACKAGE}-${DEB_VERSION_UPSTREAM}
 GITVERSION:=$(shell git rev-parse HEAD)
 
-DEB=${PACKAGE}_${VERSION}_all.deb
-DSC=${PACKAGE}_${VERSION}.dsc
+DEB=${PACKAGE}_${DEB_VERSION_UPSTREAM_REVISION}_${DEB_BUILD_ARCH}.deb
+DSC=rust-${CRATENAME}_${DEB_VERSION_UPSTREAM_REVISION}.dsc
 
-all: ${DEB}
-	@echo ${DEB}
+ifeq ($(BUILD_MODE), release)
+CARGO_BUILD_ARGS += --release
+COMPILEDIR := target/release
+else
+COMPILEDIR := target/debug
+endif
+
+all: cargo-build $(SRCIDR)
+
+.PHONY: $(SUBDIRS)
+$(SUBDIRS):
+	make -C $@
+
+.PHONY: cargo-build
+cargo-build:
+	cargo build $(CARGO_BUILD_ARGS)
 
-${BUILDDIR}: ${SRCDIR} debian
-	rm -rf ${BUILDDIR}
-	rsync -a ${SRCDIR}/ debian ${BUILDDIR}
-	echo "git clone git://git.proxmox.com/git/pve-xtermjs.git\\ngit checkout ${GITVERSION}" > ${BUILDDIR}/debian/SOURCE
+.PHONY: build
+build:
+	rm -rf build
+	debcargo package \
+	--config debian/debcargo.toml \
+	--changelog-ready \
+	--no-overlay-write-back \
+	--directory build \
+	$(CRATENAME) \
+	$(shell dpkg-parsechangelog -l debian/changelog -SVersion | sed -e 's/-.*//')
+	rm build/Cargo.lock
+	find build/debian -name "*.hint" -delete
+	echo "git clone git://git.proxmox.com/git/pve-xtermjs.git\\ngit checkout ${GITVERSION}" > build/debian/SOURCE
 
 .PHONY: deb
 deb: ${DEB}
-${DEB}: ${BUILDDIR}
-	cd ${BUILDDIR}; dpkg-buildpackage -b -uc -us
+$(DEB): build
+	cd build; dpkg-buildpackage -b -uc -us --no-pre-clean
 	lintian ${DEB}
 	@echo ${DEB}
 
 .PHONY: dsc
 dsc: ${DSC}
-${DSC}: ${BUILDDIR}
-	cd ${BUILDDIR}; dpkg-buildpackage -S -us -uc -d
+$(DSC): build
+	cd build; dpkg-buildpackage -S -us -uc -d -nc
 	lintian ${DSC}
 
 X_EXCLUSIONS=--exclude=addons/attach --exclude=addons/fullscreen --exclude=addons/search \
@@ -59,7 +83,7 @@ distclean: clean
 
 .PHONY: clean
 clean:
-	rm -rf *~ debian/*~ ${PACKAGE}-*/ *.deb *.changes *.dsc *.tar.gz *.buildinfo
+	rm -rf *~ debian/*~ ${PACKAGE}-*/ build/ *.deb *.changes *.dsc *.tar.?z *.buildinfo
 
 .PHONY: dinstall
 dinstall: deb
diff --git a/debian/compat b/debian/compat
deleted file mode 100644
index f599e28..0000000
--- a/debian/compat
+++ /dev/null
@@ -1 +0,0 @@
-10
diff --git a/debian/control b/debian/control
deleted file mode 100644
index 216b672..0000000
--- a/debian/control
+++ /dev/null
@@ -1,16 +0,0 @@
-Source: pve-xtermjs
-Section: web
-Priority: optional
-Maintainer: Proxmox Support Team <support@proxmox.com>
-Build-Depends: debhelper (>= 10~),
-               libpve-common-perl,
-Standards-Version: 4.1.3
-
-Package: pve-xtermjs
-Architecture: all
-Depends: libpve-common-perl (>= 5.0-23),
-         libwww-perl,
-         ${misc:Depends},
-         ${perl:Depends}
-Description: HTML/JS Shell client
- This is an xterm.js client for PVE Host, Container and Qemu Serial Terminal
diff --git a/debian/debcargo.toml b/debian/debcargo.toml
new file mode 100644
index 0000000..cf78dba
--- /dev/null
+++ b/debian/debcargo.toml
@@ -0,0 +1,14 @@
+overlay = "."
+crate_src_path = ".."
+bin_name = "pve-xtermjs"
+
+[source]
+maintainer = "Proxmox Support Team <support@proxmox.com>"
+section = "admin"
+homepage = "http://www.proxmox.com"
+vcs_git = "git://git.proxmox.com/git/pve-xtermjs.git"
+vcs_browser = "https://git.proxmox.com/?p=pve-xtermjs.git;a=summary"
+
+[package]
+summary = "HTML/JS Shell client"
+description = "This is an xterm.js client/proxy for Proxmox Hosts, PVE containers or QEMU Serial Terminals"
diff --git a/debian/install b/debian/install
new file mode 100644
index 0000000..04be689
--- /dev/null
+++ b/debian/install
@@ -0,0 +1 @@
+src/www/*		/usr/share/pve-xtermjs/
diff --git a/debian/rules b/debian/rules
index 2d33f6a..6263218 100755
--- a/debian/rules
+++ b/debian/rules
@@ -1,4 +1,9 @@
 #!/usr/bin/make -f
 
 %:
-	dh $@
+	dh $@ --buildsystem cargo
+
+override_dh_auto_build:
+	dh_auto_build
+	sed -e 's/@VERSION@/${VERSION}/' src/www/index.html.tpl.in > src/www/index.html.tpl
+	rm src/www/index.html.tpl.in
diff --git a/debian/source/format b/debian/source/format
deleted file mode 100644
index d3827e7..0000000
--- a/debian/source/format
+++ /dev/null
@@ -1 +0,0 @@
-1.0
diff --git a/debian/source/lintian-overrides b/debian/source/lintian-overrides
index a991da2..100187c 100644
--- a/debian/source/lintian-overrides
+++ b/debian/source/lintian-overrides
@@ -1,2 +1,2 @@
-pve-xtermjs source: source-is-missing www/xterm.js line length is *
-pve-xtermjs source: source-is-missing www/addons/fit/fit.js line length is *
+rust-termproxy source: source-is-missing src/www/xterm.js line length is *
+rust-termproxy source: source-is-missing src/www/xterm-addon-fit.js line length is *
diff --git a/src/Makefile b/src/Makefile
deleted file mode 100644
index 4164587..0000000
--- a/src/Makefile
+++ /dev/null
@@ -1,7 +0,0 @@
-all:
-
-.PHONY: install
-install:
-	make -C bin install
-	make -C PVE install
-	make -C www install
diff --git a/src/PVE/CLI/Makefile b/src/PVE/CLI/Makefile
deleted file mode 100644
index 8ea5eff..0000000
--- a/src/PVE/CLI/Makefile
+++ /dev/null
@@ -1,8 +0,0 @@
-PERLLIBDIR=${DESTDIR}/usr/share/perl5
-
-all:
-
-.PHONY: install
-install:
-	install -d ${PERLLIBDIR}/PVE/CLI
-	install -m 0644 termproxy.pm ${PERLLIBDIR}/PVE/CLI/
diff --git a/src/PVE/CLI/termproxy.pm b/src/PVE/CLI/termproxy.pm
deleted file mode 100644
index 089d9b7..0000000
--- a/src/PVE/CLI/termproxy.pm
+++ /dev/null
@@ -1,250 +0,0 @@
-package PVE::CLI::termproxy;
-
-use strict;
-use warnings;
-
-use PVE::CLIHandler;
-use PVE::JSONSchema qw(get_standard_option);
-use PVE::PTY;
-use LWP::UserAgent;
-use IO::Select;
-use IO::Socket::IP;
-
-use base qw(PVE::CLIHandler);
-
-use constant MAX_QUEUE_LEN => 16*1024;
-
-sub verify_ticket {
-    my ($ticket, $user, $path, $perm) = @_;
-
-    # get all loopback addresses even if no IPv4 or IPv6 address is setup on
-    # the host, IO::Socket::IP sets AI_ADDRCONFIG (man getaddrinfo) per default
-    local @LWP::Protocol::http::EXTRA_SOCK_OPTS = (
-	GetAddrInfoFlags => 0,
-    );
-
-    my $ua = LWP::UserAgent->new();
-
-    my $params = {
-	username => $user,
-	password => $ticket,
-	path => $path,
-    };
-
-    $params->{privs} = $perm if $perm;
-
-    my $res = $ua->post ('http://127.0.0.1:85/api2/json/access/ticket', Content => $params);
-
-    if (!$res->is_success) {
-	my $err = $res->status_line;
-	die "Authentication failed: '$err'\n";
-    }
-}
-
-sub listen_and_authenticate {
-    my ($port, $timeout, $path, $perm) = @_;
-
-    my $params = {
-	Listen => 1,
-	ReuseAddr => 1,
-	Proto => &Socket::IPPROTO_TCP,
-	GetAddrInfoFlags => 0,
-	LocalAddr => 'localhost',
-	LocalPort => $port,
-    };
-
-    my $socket = IO::Socket::IP->new(%$params) or die "failed to open socket: $!\n";
-
-    alarm 0;
-    local $SIG{ALRM} = sub { die "timed out waiting for client\n" };
-    alarm $timeout;
-    my $client = $socket->accept; # Wait for a client
-    alarm 0;
-    close($socket);
-
-    my $queue;
-    my $n = sysread($client, $queue, 4096);
-    if ($n && $queue =~ s/^([^:]+):(.+)\n//) {
-	my $user = $1;
-	my $ticket = $2;
-
-	verify_ticket($ticket, $user, $path, $perm);
-
-	die "aknowledge failed\n"
-	    if !syswrite($client, "OK");
-
-    } else {
-	die "malformed authentication string\n";
-    }
-
-    return ($queue, $client);
-}
-
-sub run_pty {
-    my ($cmd, $webhandle, $queue) = @_;
-
-    foreach my $k (keys %ENV) {
-	next if $k eq 'PATH' || $k eq 'USER' || $k eq 'HOME' || $k eq 'LANG' || $k eq 'LANGUAGE';
-	next if $k =~ m/^LC_/;
-	delete $ENV{$k};
-    }
-
-    $ENV{TERM} = 'xterm-256color';
-
-    my $pty = PVE::PTY->new();
-
-    my $pid = fork();
-    die "fork: $!\n" if !defined($pid);
-    if (!$pid) {
-	$pty->make_controlling_terminal();
-	exec {$cmd->[0]} @$cmd
-	    or POSIX::_exit(1);
-    }
-
-    $pty->set_size(80,20);
-
-    read_write_loop($webhandle, $pty->master, $queue, $pty);
-
-    $pty->close();
-    waitpid($pid,0);
-    exit(0);
-}
-
-sub read_write_loop {
-    my ($webhandle, $cmdhandle, $queue, $pty) = @_;
-
-    my $select = new IO::Select;
-
-    $select->add($webhandle);
-    $select->add($cmdhandle);
-
-    my @handles;
-
-    # we may have already messages from the first read
-    $queue = process_queue($queue, $cmdhandle, $pty);
-
-    my $timeout = 5*60;
-
-    while($select->count && scalar(@handles = $select->can_read($timeout))) {
-	foreach my $h (@handles) {
-	    my $buf;
-	    my $n = $h->sysread($buf, 4096);
-
-	    if ($h == $webhandle) {
-		if ($n && (length($queue) + $n) < MAX_QUEUE_LEN) {
-		    $queue = process_queue($queue.$buf, $cmdhandle, $pty);
-		} else {
-		    return;
-		}
-	    } elsif ($h == $cmdhandle) {
-		if ($n) {
-		    syswrite($webhandle, $buf);
-		} else {
-		    return;
-		}
-	    }
-	}
-    }
-}
-
-sub process_queue {
-    my ($queue, $handle, $pty) = @_;
-
-    my $msg;
-    while(length($queue)) {
-	($queue, $msg) = remove_message($queue, $pty);
-	last if !defined($msg);
-	syswrite($handle, $msg);
-    }
-    return $queue;
-}
-
-
-# we try to remove a whole message
-# if we succeed, we return the remaining queue and the msg
-# if we fail, the message is undef and the queue is not changed
-sub remove_message {
-    my ($queue, $pty) = @_;
-
-    my $msg;
-    my $type = substr $queue, 0, 1;
-
-    if ($type eq '0') {
-	# normal message
-	my ($length) = $queue =~ m/^0:(\d+):/;
-	my $begin = 3 + length($length);
-	if (defined($length) && length($queue) >= ($length + $begin)) {
-	    $msg = substr $queue, $begin, $length;
-	    if (defined($msg)) {
-		# msg contains now $length chars after 0:$length:
-		$queue = substr $queue, $begin + $length;
-	    }
-	}
-    } elsif ($type eq '1') {
-	# resize message
-	my ($cols, $rows) = $queue =~ m/^1:(\d+):(\d+):/;
-	if (defined($cols) && defined($rows)) {
-	    $queue = substr $queue, (length($cols) + length ($rows) + 4);
-	    eval { $pty->set_size($cols, $rows) if defined($pty) };
-	    warn $@ if $@;
-	    $msg = "";
-	}
-    } elsif ($type eq '2') {
-	# ping
-	$queue = substr $queue, 1;
-	$msg = "";
-    } else {
-	# ignore other input
-	$queue = substr $queue, 1;
-	$msg = "";
-    }
-
-    return ($queue, $msg);
-}
-
-__PACKAGE__->register_method ({
-    name => 'exec',
-    path => 'exec',
-    method => 'POST',
-    description => "Connects a TCP Socket with a commandline",
-    parameters => {
-	additionalProperties => 0,
-	properties => {
-	    port => {
-		type => 'integer',
-		description => "The port to listen on."
-	    },
-	    path => {
-		type => 'string',
-		description => "The Authentication path.",
-	    },
-	    perm => {
-		type => 'string',
-		description => "The Authentication Permission.",
-		optional => 1,
-	    },
-	    'extra-args' => get_standard_option('extra-args'),
-	},
-    },
-    returns => { type => 'null'},
-    code => sub {
-	my ($param) = @_;
-
-	my $cmd;
-	if (defined($param->{'extra-args'})) {
-	    $cmd = [@{$param->{'extra-args'}}];
-	} else {
-	    die "No command given\n";
-	}
-
-	my ($queue, $handle) = listen_and_authenticate($param->{port}, 10,
-	    $param->{path}, $param->{perm});
-
-	run_pty($cmd, $handle, $queue);
-
-	return undef;
-    }});
-
-our $cmddef = [ __PACKAGE__, 'exec', ['port', 'extra-args' ]];
-
-1;
diff --git a/src/PVE/Makefile b/src/PVE/Makefile
deleted file mode 100644
index b0321d7..0000000
--- a/src/PVE/Makefile
+++ /dev/null
@@ -1,3 +0,0 @@
-.PHONY: install
-install:
-	make -C CLI install
diff --git a/src/bin/Makefile b/src/bin/Makefile
deleted file mode 100644
index a8c2842..0000000
--- a/src/bin/Makefile
+++ /dev/null
@@ -1,7 +0,0 @@
-BINDIR=${DESTDIR}/usr/bin
-
-.PHONY: install
-install: termproxy
-	perl -I.. -T -e "use PVE::CLI::termproxy; PVE::CLI::termproxy->verify_api();"
-	install -d ${BINDIR}
-	install -m 0755 termproxy ${BINDIR}
diff --git a/src/bin/termproxy b/src/bin/termproxy
deleted file mode 100755
index a28bcd9..0000000
--- a/src/bin/termproxy
+++ /dev/null
@@ -1,8 +0,0 @@
-#!/usr/bin/perl
-
-use strict;
-use warnings;
-
-use PVE::CLI::termproxy;
-
-PVE::CLI::termproxy->run_cli_handler();
diff --git a/src/main.rs b/src/main.rs
new file mode 100644
index 0000000..4169d28
--- /dev/null
+++ b/src/main.rs
@@ -0,0 +1,421 @@
+use std::cmp::min;
+use std::collections::HashMap;
+use std::ffi::{OsStr, OsString};
+use std::io::{ErrorKind, Result, Write};
+use std::net::{SocketAddr, TcpStream};
+use std::os::unix::io::AsRawFd;
+use std::os::unix::process::CommandExt;
+use std::process::Command;
+use std::time::{Duration, Instant};
+
+use clap::{App, AppSettings, Arg};
+use curl::easy::Easy;
+use mio::net::TcpListener;
+use mio::unix::{EventedFd, UnixReady};
+use mio::{Events, Poll, PollOpt, Ready, Token};
+
+use proxmox::sys::error::io_err_other;
+use proxmox::sys::linux::pty::{make_controlling_terminal, PTY};
+use proxmox::tools::byte_buffer::ByteBuffer;
+use proxmox::{io_format_err, io_bail};
+
+const MSG_TYPE_DATA: u8 = 0;
+const MSG_TYPE_RESIZE: u8 = 1;
+//const MSG_TYPE_PING: u8 = 2;
+
+fn remove_number(buf: &mut ByteBuffer) -> Option<usize> {
+    loop {
+        if let Some(pos) = &buf.iter().position(|&x| x == b':') {
+            let data = buf.remove_data(*pos);
+            buf.consume(1); // the ':'
+            let len = match std::str::from_utf8(&data) {
+                Ok(lenstring) => match lenstring.parse() {
+                    Ok(len) => len,
+                    Err(err) => {
+                        eprintln!("error parsing number: '{}'", err);
+                        break;
+                    }
+                },
+                Err(err) => {
+                    eprintln!("error parsing number: '{}'", err);
+                    break;
+                }
+            };
+            return Some(len);
+        } else if buf.len() > 20 {
+            buf.consume(20);
+        } else {
+            break;
+        }
+    }
+    None
+}
+
+fn process_queue(buf: &mut ByteBuffer, pty: &mut PTY) -> Option<usize> {
+    if buf.is_empty() {
+        return None;
+    }
+
+    loop {
+        if buf.len() < 2 {
+            break;
+        }
+
+        let msgtype = buf[0] - b'0';
+
+        if msgtype == MSG_TYPE_DATA {
+            buf.consume(2);
+            if let Some(len) = remove_number(buf) {
+                return Some(len);
+            }
+        } else if msgtype == MSG_TYPE_RESIZE {
+            buf.consume(2);
+            if let Some(cols) = remove_number(buf) {
+                if let Some(rows) = remove_number(buf) {
+                    pty.set_size(cols as u16, rows as u16).ok()?;
+                }
+            }
+            // ignore incomplete messages
+        } else {
+            buf.consume(1);
+            // ignore invalid or ping (msgtype 2)
+        }
+    }
+
+    None
+}
+
+/// Reads from the stream and returns the first line and the rest
+fn read_ticket_line(
+    stream: &mut TcpStream,
+    buf: &mut ByteBuffer,
+    timeout: Duration,
+) -> Result<(Box<[u8]>, Box<[u8]>)> {
+
+    let now = Instant::now();
+    while !&buf[..].contains(&b'\n') {
+        if buf.is_full() || now.elapsed() >= timeout {
+            io_bail!("authentication data is incomplete: {:?}", &buf[..]);
+        }
+        stream.set_read_timeout(Some(Duration::new(1, 0)))?;
+        match buf.read_from(stream) {
+            Ok(n) => {
+                if n == 0 {
+                    io_bail!("connection closed before authentication");
+                }
+            },
+            Err(err) if err.kind() == ErrorKind::WouldBlock => {},
+            Err(err) => return Err(err),
+        }
+    }
+
+    stream.set_read_timeout(None)?;
+    let newline_idx = &buf[..]
+        .iter()
+        .position(|&x| x == b'\n')
+        .unwrap();
+
+    let line = buf.remove_data(*newline_idx);
+    buf.consume(1); // discard newline
+
+    match line.iter().position(|&b| b == b':') {
+        Some(pos) => {
+            let (username, ticket) = line.split_at(pos);
+            Ok((username.into(), ticket[1..].into()))
+        }
+        None => io_bail!("authentication data is invalid"),
+    }
+}
+
+fn authenticate(
+    username: &[u8],
+    ticket: &[u8],
+    path: &str,
+    perm: Option<&str>,
+    authport: u16,
+) -> Result<()> {
+
+    let mut curl = Easy::new();
+    curl.url(&format!(
+        "http://localhost:{}/api2/json/access/ticket",
+        authport
+    ))?;
+
+    let username = curl.url_encode(username);
+    let ticket = curl.url_encode(ticket);
+    let path = curl.url_encode(path.as_bytes());
+    let mut post_fields = format!("username={}&password={}&path={}",username, ticket, path);
+
+    if let Some(perm) = perm {
+        let perm = curl.url_encode(perm.as_bytes());
+        post_fields = format!("{}&privs={}", post_fields, perm);
+    }
+
+    curl.post_fields_copy(post_fields.as_bytes())?;
+    curl.post(true)?;
+    curl.perform()?;
+
+    let response_code = curl.response_code()?;
+
+    if response_code != 200 {
+        io_bail!("invalid authentication, code {}", response_code);
+    }
+
+    Ok(())
+}
+
+fn listen_and_accept(hostname: &str, port: u16, timeout: Duration) -> Result<(TcpStream, SocketAddr)> {
+    let listener = std::net::TcpListener::bind((hostname, port))?;
+    let listener = TcpListener::from_std(listener)?;
+    let poll = Poll::new()?;
+
+    poll.register(
+        &listener,
+        Token(0),
+        Ready::readable(),
+        PollOpt::edge(),
+    )?;
+
+    let mut events = Events::with_capacity(1);
+
+    let mut timeout = timeout;
+    let now = Instant::now();
+    loop {
+        poll.poll(&mut events, Some(timeout))?;
+
+        let elapsed = now.elapsed();
+        if !events.is_empty() {
+            return listener.accept_std();
+        }
+        timeout = timeout - elapsed;
+    }
+}
+
+fn run_pty(cmd: &OsStr, params: clap::OsValues) -> Result<PTY> {
+    let (mut pty, secondary_name) = PTY::new().map_err(io_err_other)?;
+
+    let mut filtered_env: HashMap<OsString, OsString> = std::env::vars_os()
+        .filter(|&(ref k, _)| {
+            k == "PATH" || k == "USER" || k == "HOME" || k == "LANG" || k == "LANGUAGE" ||
+            k.to_string_lossy().starts_with("LC_")
+        }).collect();
+    filtered_env.insert("TERM".into(), "xterm-256color".into());
+
+    let mut command = Command::new(cmd);
+
+    command
+        .args(params)
+        .env_clear()
+        .envs(&filtered_env);
+
+    unsafe {
+        command.pre_exec(move || {
+            make_controlling_terminal(&secondary_name).map_err(io_err_other)?;
+            Ok(())
+        });
+    }
+
+    command.spawn()?;
+
+    pty.set_size(80, 20).map_err(|x| x.as_errno().unwrap())?;
+    Ok(pty)
+}
+
+const TCP: Token = Token(0);
+const PTY: Token = Token(1);
+
+fn do_main() -> Result<()> {
+    let matches = App::new("termproxy")
+        .setting(AppSettings::TrailingVarArg)
+        .arg(Arg::with_name("port").takes_value(true).required(true))
+        .arg(
+            Arg::with_name("authport")
+                .takes_value(true)
+                .long("authport"),
+        )
+        .arg(
+            Arg::with_name("path")
+                .takes_value(true)
+                .long("path")
+                .required(true),
+        )
+        .arg(Arg::with_name("perm").takes_value(true).long("perm"))
+        .arg(Arg::with_name("cmd").multiple(true).required(true))
+        .get_matches();
+
+    let port: u16 = matches .value_of("port") .unwrap() .parse() .map_err(io_err_other)?;
+    let path = matches.value_of("path").unwrap();
+    let perm: Option<&str> = matches.value_of("perm");
+    let mut cmdparams = matches.values_of_os("cmd").unwrap();
+    let cmd = cmdparams.next().unwrap();
+    let authport: u16 = matches .value_of("authport") .unwrap_or("85") .parse() .map_err(io_err_other)?;
+    let mut pty_buf = ByteBuffer::new();
+    let mut tcp_buf = ByteBuffer::new();
+
+    let (mut stream, client) = listen_and_accept("localhost", port, Duration::new(10, 0))
+        .map_err(|err| io_format_err!("failed waiting for client: {}", err))?;
+
+    println!("client connection: {:?}", client);
+    let (username, ticket) = read_ticket_line(&mut stream, &mut pty_buf, Duration::new(10, 0))
+        .map_err(|err| io_format_err!("failed reading ticket: {}", err))?;
+    authenticate(&username, &ticket, path, perm, authport)?;
+    stream.write_all(b"OK").expect("error writing response");
+
+    let mut tcp_handle = mio::net::TcpStream::from_stream(stream)?;
+
+
+    let poll = Poll::new()?;
+    let mut events = Events::with_capacity(128);
+
+    let mut pty = run_pty(cmd, cmdparams)?;
+
+    poll.register(
+        &tcp_handle,
+        TCP,
+        Ready::readable() | Ready::writable() | UnixReady::hup(),
+        PollOpt::edge(),
+    )?;
+    poll.register(
+        &EventedFd(&pty.as_raw_fd()),
+        PTY,
+        Ready::readable() | Ready::writable() | UnixReady::hup(),
+        PollOpt::edge(),
+    )?;
+
+    let mut tcp_writable = true;
+    let mut pty_writable = true;
+    let mut tcp_readable = true;
+    let mut pty_readable = true;
+    let mut remaining = 0;
+    let mut finished = false;
+
+    while !finished {
+        if tcp_readable && !pty_buf.is_full() || pty_readable && !tcp_buf.is_full() {
+            poll.poll(&mut events, Some(Duration::new(0, 0)))?;
+        } else {
+            poll.poll(&mut events, None)?;
+        }
+
+        for event in &events {
+            let readiness = event.readiness();
+            let writable = readiness.is_writable();
+            let readable = readiness.is_readable();
+            if UnixReady::from(readiness).is_hup() {
+                finished = true;
+            }
+            match event.token() {
+                TCP => {
+                    if readable {
+                        tcp_readable = true;
+                    }
+                    if writable {
+                        tcp_writable = true;
+                    }
+                }
+                PTY => {
+                    if readable {
+                        pty_readable = true;
+                    }
+                    if writable {
+                        pty_writable = true;
+                    }
+                }
+                _ => unreachable!(),
+            }
+        }
+
+        while tcp_readable && !pty_buf.is_full() {
+            let bytes = match pty_buf.read_from(&mut tcp_handle) {
+                Ok(bytes) => bytes,
+                Err(err) if err.kind() == ErrorKind::WouldBlock => {
+                    tcp_readable = false;
+                    break;
+                }
+                Err(err) => {
+                    if !finished {
+                        return Err(io_format_err!("error reading from tcp: {}", err));
+                    }
+                    break;
+                }
+            };
+            if bytes == 0 {
+                finished = true;
+                break;
+            }
+        }
+
+        while pty_readable && !tcp_buf.is_full() {
+            let bytes = match tcp_buf.read_from(&mut pty) {
+                Ok(bytes) => bytes,
+                Err(err) if err.kind() == ErrorKind::WouldBlock => {
+                    pty_readable = false;
+                    break;
+                }
+                Err(err) => {
+                    if !finished {
+                        return Err(io_format_err!("error reading from pty: {}", err));
+                    }
+                    break;
+                }
+            };
+            if bytes == 0 {
+                finished = true;
+                break;
+            }
+        }
+
+        while !tcp_buf.is_empty() && tcp_writable {
+            let bytes = match tcp_handle.write(&tcp_buf[..]) {
+                Ok(bytes) => bytes,
+                Err(err) if err.kind() == ErrorKind::WouldBlock => {
+                    tcp_writable = false;
+                    break;
+                }
+                Err(err) => {
+                    if !finished {
+                        return Err(io_format_err!("error writing to tcp : {}", err));
+                    }
+                    break;
+                }
+            };
+            tcp_buf.consume(bytes);
+        }
+
+        while !pty_buf.is_empty() && pty_writable {
+            if remaining == 0 {
+                remaining = match process_queue(&mut pty_buf, &mut pty) {
+                    Some(val) => val,
+                    None => break,
+                };
+            }
+            let len = min(remaining, pty_buf.len());
+            let bytes = match pty.write(&pty_buf[..len]) {
+                Ok(bytes) => bytes,
+                Err(err) if err.kind() == ErrorKind::WouldBlock => {
+                    pty_writable = false;
+                    break;
+                }
+                Err(err) => {
+                    if !finished {
+                        return Err(io_format_err!("error writing to pty : {}", err));
+                    }
+                    break;
+                }
+            };
+            remaining -= bytes;
+            pty_buf.consume(bytes);
+        }
+    }
+
+    Ok(())
+}
+
+fn main() {
+    std::process::exit(match do_main() {
+        Ok(_) => 0,
+        Err(err) => {
+            eprintln!("{}", err);
+            1
+        },
+    });
+}
diff --git a/src/www/Makefile b/src/www/Makefile
deleted file mode 100644
index 5e51258..0000000
--- a/src/www/Makefile
+++ /dev/null
@@ -1,21 +0,0 @@
-WWWBASEDIR=${DESTDIR}/usr/share/pve-xtermjs
-
-SOURCE = \
-	xterm-addon-fit.js \
-	xterm-addon-fit.js.map \
-	index.html.tpl \
-	main.js \
-	style.css \
-	util.js \
-	xterm.css \
-	xterm.js \
-	xterm.js.map
-
-index.html.tpl: index.html.tpl.in
-	sed -e 's/@VERSION@/${VERSION}/' $< >$@.tmp
-	mv $@.tmp $@
-
-.PHONY: install
-install: ${SOURCE}
-	install -d ${WWWBASEDIR}
-	set -e && for i in ${SOURCE}; do install -m 0644 $$i ${WWWBASEDIR}; done
-- 
2.20.1





^ permalink raw reply	[flat|nested] 5+ messages in thread

* [pve-devel] [PATCH xtermjs v2 2/4] add handlebars template
  2020-07-14 11:51 [pve-devel] [PATCH xtermjs v2 0/4] rewrite in rust Dominik Csapak
  2020-07-14 11:51 ` [pve-devel] [PATCH xtermjs v2 1/4] termproxy: " Dominik Csapak
@ 2020-07-14 11:51 ` Dominik Csapak
  2020-07-14 11:51 ` [pve-devel] [PATCH xtermjs v2 3/4] ui: improve error message handling Dominik Csapak
  2020-07-14 11:51 ` [pve-devel] [PATCH xtermjs v2 4/4] ui: prevent accidental closing of terminal window Dominik Csapak
  3 siblings, 0 replies; 5+ messages in thread
From: Dominik Csapak @ 2020-07-14 11:51 UTC (permalink / raw)
  To: pve-devel

for rust projects, we do not use Perl::Template, but handlebars,
so provide a separate template for that

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
new in v2
 debian/rules              |  3 ++-
 src/www/index.html.hbs.in | 23 +++++++++++++++++++++++
 2 files changed, 25 insertions(+), 1 deletion(-)
 create mode 100644 src/www/index.html.hbs.in

diff --git a/debian/rules b/debian/rules
index 6263218..2b62edd 100755
--- a/debian/rules
+++ b/debian/rules
@@ -6,4 +6,5 @@
 override_dh_auto_build:
 	dh_auto_build
 	sed -e 's/@VERSION@/${VERSION}/' src/www/index.html.tpl.in > src/www/index.html.tpl
-	rm src/www/index.html.tpl.in
+	sed -e 's/@VERSION@/${VERSION}/' src/www/index.html.hbs.in > src/www/index.html.hbs
+	rm src/www/index.html.tpl.in src/www/index.html.hbs.in
diff --git a/src/www/index.html.hbs.in b/src/www/index.html.hbs.in
new file mode 100644
index 0000000..ecbea1b
--- /dev/null
+++ b/src/www/index.html.hbs.in
@@ -0,0 +1,23 @@
+<!doctype html>
+<html>
+    <head>
+	<title>{{ nodename }} - Proxmox Console</title>
+	<link rel="stylesheet" href="/xtermjs/xterm.css?version=@VERSION@" />
+	<link rel="stylesheet" href="/xtermjs/style.css?version=@VERSION@" />
+	<script src="/xtermjs/xterm.js?version=@VERSION@" ></script>
+	<script src="/xtermjs/xterm-addon-fit.js?version=@VERSION@" ></script>
+	<script src="/xtermjs/util.js?version=@VERSION@" ></script>
+    </head>
+    <body>
+	<div id="status_bar"></div>
+	<div id="wrap">
+	<div id="terminal-container"></div>
+	</div>
+	<script type="text/javascript">
+	    if (typeof(PVE) === 'undefined') PVE = {};
+	    PVE.UserName = '{{ UserName }}';
+	    PVE.CSRFPreventionToken = '{{ CSRFPreventionToken }}';
+	</script>
+	<script src="/xtermjs/main.js?version=@VERSION@" defer ></script>
+    </body>
+</html>
-- 
2.20.1





^ permalink raw reply	[flat|nested] 5+ messages in thread

* [pve-devel] [PATCH xtermjs v2 3/4] ui: improve error message handling
  2020-07-14 11:51 [pve-devel] [PATCH xtermjs v2 0/4] rewrite in rust Dominik Csapak
  2020-07-14 11:51 ` [pve-devel] [PATCH xtermjs v2 1/4] termproxy: " Dominik Csapak
  2020-07-14 11:51 ` [pve-devel] [PATCH xtermjs v2 2/4] add handlebars template Dominik Csapak
@ 2020-07-14 11:51 ` Dominik Csapak
  2020-07-14 11:51 ` [pve-devel] [PATCH xtermjs v2 4/4] ui: prevent accidental closing of terminal window Dominik Csapak
  3 siblings, 0 replies; 5+ messages in thread
From: Dominik Csapak @ 2020-07-14 11:51 UTC (permalink / raw)
  To: pve-devel

by splitting the msg and code, and only showing the existing parts
also actually read the msg/code from the event by giving it from
tryReconnect to stopTerminal

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
new in v2
 src/www/main.js | 21 ++++++++++++++-------
 1 file changed, 14 insertions(+), 7 deletions(-)

diff --git a/src/www/main.js b/src/www/main.js
index 4da40b0..55834eb 100644
--- a/src/www/main.js
+++ b/src/www/main.js
@@ -26,7 +26,7 @@ var nodename = getQueryParameter('node');
 var cmd = getQueryParameter('cmd');
 var cmdOpts = getQueryParameter('cmd-opts');
 
-function updateState(newState, msg) {
+function updateState(newState, msg, code) {
     var timeout, severity, message;
     switch (newState) {
 	case states.connecting:
@@ -77,8 +77,15 @@ function updateState(newState, msg) {
 	default:
 	    throw "unknown state";
     }
-    if (msg) {
-	message += " (" + msg + ")";
+    let msgArr = [];
+    if (msg !== undefined) {
+	msgArr.push(msg);
+    }
+    if (code !== undefined) {
+	msgArr.push(`Code: ${code}`);
+    }
+    if (msgArr.length > 0) {
+	message += ` (${msgArr.join(', ')})`;
     }
     state = newState;
     showMsg(message, timeout, severity);
@@ -279,11 +286,11 @@ function checkMigration() {
     });
 }
 
-function tryReconnect() {
+function tryReconnect(event) {
     var time_since_started = new Date() - starttime;
     var type = getQueryParameter('console');
     if (time_since_started < 5*1000 || type === 'shell' || type === 'cmd') { // 5 seconds
-	stopTerminal();
+	stopTerminal(event);
 	return;
     }
 
@@ -301,7 +308,7 @@ function stopTerminal(event) {
     clearEvents();
     clearInterval(ping);
     socket.close();
-    updateState(states.disconnected, event.msg + event.code);
+    updateState(states.disconnected, event.reason, event.code);
 }
 
 function errorTerminal(event) {
@@ -310,5 +317,5 @@ function errorTerminal(event) {
     clearInterval(ping);
     socket.close();
     term.dispose();
-    updateState(states.disconnected, event.msg + event.code);
+    updateState(states.disconnected, event.msg, event.code);
 }
-- 
2.20.1





^ permalink raw reply	[flat|nested] 5+ messages in thread

* [pve-devel] [PATCH xtermjs v2 4/4] ui: prevent accidental closing of terminal window
  2020-07-14 11:51 [pve-devel] [PATCH xtermjs v2 0/4] rewrite in rust Dominik Csapak
                   ` (2 preceding siblings ...)
  2020-07-14 11:51 ` [pve-devel] [PATCH xtermjs v2 3/4] ui: improve error message handling Dominik Csapak
@ 2020-07-14 11:51 ` Dominik Csapak
  3 siblings, 0 replies; 5+ messages in thread
From: Dominik Csapak @ 2020-07-14 11:51 UTC (permalink / raw)
  To: pve-devel

this prevents closing the window e.g. when pressing ctrl+w

most browsers have a function to block a website from doing that,
so users that do not want that, simply have to click that option

also, the message will (at least in chrome) not be displayed,
instead a fixed message will appear

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
new in v2, please interpret as RFC
 src/www/main.js | 13 ++++++++++++-
 1 file changed, 12 insertions(+), 1 deletion(-)

diff --git a/src/www/main.js b/src/www/main.js
index 55834eb..43fe656 100644
--- a/src/www/main.js
+++ b/src/www/main.js
@@ -143,7 +143,7 @@ function createTerminal() {
 	    socket.onopen = runTerminal;
 	    socket.onclose = tryReconnect;
 	    socket.onerror = tryReconnect;
-	    window.onbeforeunload = stopTerminal;
+	    window.onbeforeunload = windowUnload;
 	    updateState(states.connecting);
 	},
 	failure: function(msg) {
@@ -303,6 +303,17 @@ function clearEvents() {
     term.onData(() => {});
 }
 
+function windowUnload(e) {
+    let message = "Are you sure you want to leave this page?";
+
+    e = e || window.event;
+    if (e) {
+	e.returnValue = message;
+    }
+
+    return message;
+}
+
 function stopTerminal(event) {
     event = event || {};
     clearEvents();
-- 
2.20.1





^ permalink raw reply	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2020-07-14 11:51 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2020-07-14 11:51 [pve-devel] [PATCH xtermjs v2 0/4] rewrite in rust Dominik Csapak
2020-07-14 11:51 ` [pve-devel] [PATCH xtermjs v2 1/4] termproxy: " Dominik Csapak
2020-07-14 11:51 ` [pve-devel] [PATCH xtermjs v2 2/4] add handlebars template Dominik Csapak
2020-07-14 11:51 ` [pve-devel] [PATCH xtermjs v2 3/4] ui: improve error message handling Dominik Csapak
2020-07-14 11:51 ` [pve-devel] [PATCH xtermjs v2 4/4] ui: prevent accidental closing of terminal window Dominik Csapak

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal