[pbs-devel] [PATCH xtermjs v3 1/4] termproxy: rewrite in rust
Dominik Csapak
d.csapak at proxmox.com
Tue Jul 21 11:00:45 CEST 2020
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 at proxmox.com>
---
.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 | 456 ++++++++++++++++++++++++++++++++
src/www/Makefile | 21 --
18 files changed, 536 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 at 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 at 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 at 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..c7bd32e
--- /dev/null
+++ b/src/main.rs
@@ -0,0 +1,456 @@
+use std::cmp::min;
+use std::collections::HashMap;
+use std::ffi::{OsStr, OsString};
+use std::io::{ErrorKind, Read, Result, Write};
+use std::net::TcpStream;
+use std::os::unix::io::{AsRawFd, FromRawFd};
+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_bail, io_format_err};
+
+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
+}
+
+type TicketResult = Result<(Box<[u8]>, Box<[u8]>)>;
+
+/// Reads from the stream and returns the first line and the rest
+fn read_ticket_line(
+ stream: &mut TcpStream,
+ buf: &mut ByteBuffer,
+ timeout: Duration,
+) -> TicketResult {
+ 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,
+ port: Option<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 = Vec::with_capacity(5);
+ post_fields.push(format!("username={}", username));
+ post_fields.push(format!("password={}", ticket));
+ post_fields.push(format!("path={}", path));
+
+ if let Some(perm) = perm {
+ let perm = curl.url_encode(perm.as_bytes());
+ post_fields.push(format!("privs={}", perm));
+ }
+
+ if let Some(port) = port {
+ post_fields.push(format!("port={}", port));
+ }
+
+ curl.post_fields_copy(post_fields.join("&").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: u64,
+ port_as_fd: bool,
+ timeout: Duration,
+) -> Result<(TcpStream, u16)> {
+ let listener = if port_as_fd {
+ unsafe { std::net::TcpListener::from_raw_fd(port as i32) }
+ } else {
+ std::net::TcpListener::bind((hostname, port as u16))?
+ };
+ let port = listener.local_addr()?.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;
+ loop {
+ let now = Instant::now();
+ poll.poll(&mut events, Some(timeout))?;
+ let elapsed = now.elapsed();
+ if !events.is_empty() {
+ let (stream, client) = listener.accept_std()?;
+ println!("client connection: {:?}", client);
+ return Ok((stream, port));
+ }
+ if timeout >= elapsed {
+ timeout -= elapsed;
+ } else {
+ io_bail!("timed out");
+ }
+ }
+}
+
+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("use-port-as-fd").long("port-as-fd"))
+ .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: u64 = 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 use_port_as_fd = matches.is_present("use-port-as-fd");
+
+ if use_port_as_fd && port > u16::MAX as u64 {
+ return Err(io_format_err!("port too big"));
+ } else if port > i32::MAX as u64 {
+ return Err(io_format_err!("Invalid FD number"));
+ }
+
+ let (mut stream, port) =
+ listen_and_accept("localhost", port, use_port_as_fd, Duration::new(10, 0))
+ .map_err(|err| io_format_err!("failed waiting for client: {}", err))?;
+
+ 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))?;
+ let port = if use_port_as_fd { Some(port) } else { None };
+ authenticate(&username, &ticket, path, perm, authport, port)?;
+ 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
More information about the pbs-devel
mailing list