From: Dominik Csapak <d.csapak@proxmox.com>
To: pve-devel@lists.proxmox.com
Subject: [pve-devel] [PATCH xtermjs v2 1/4] termproxy: rewrite in rust
Date: Tue, 14 Jul 2020 13:51:03 +0200 [thread overview]
Message-ID: <20200714115106.30195-2-d.csapak@proxmox.com> (raw)
In-Reply-To: <20200714115106.30195-1-d.csapak@proxmox.com>
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
next prev parent reply other threads:[~2020-07-14 11:51 UTC|newest]
Thread overview: 5+ messages / expand[flat|nested] mbox.gz Atom feed top
2020-07-14 11:51 [pve-devel] [PATCH xtermjs v2 0/4] " Dominik Csapak
2020-07-14 11:51 ` Dominik Csapak [this message]
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
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20200714115106.30195-2-d.csapak@proxmox.com \
--to=d.csapak@proxmox.com \
--cc=pve-devel@lists.proxmox.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
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.