use Digest::MD5;
# use AnyEvent::Strict; # only use this for debugging
use AnyEvent::Util qw(guard fh_nonblocking WSAEWOULDBLOCK WSAEINPROGRESS);
+use AnyEvent::Socket;
use AnyEvent::Handle;
use AnyEvent::TLS;
use AnyEvent::IO;
#print "SEND(without content) $res\n" if $self->{debug};
$res .= "\015\012";
- $res .= $content;
+ $res .= $content if $content;
$self->log_request($reqstate, $reqstate->{request});
}
}
+sub handle_spice_proxy_request {
+ my ($self, $reqstate, $vmid, $node) = @_;
+
+ eval {
+
+ my $remip;
+
+ if ($node ne 'localhost' && $node ne PVE::INotify::nodename()) {
+ $remip = PVE::Cluster::remote_node_ip($node);
+ die "unable to get remote IP address for none '$node'\n";
+ }
+
+ if ($remip) {
+ die "not implemented";
+
+ return;
+ }
+
+ $reqstate->{hdl}->timeout(0);
+
+ # local node
+ my $socket = PVE::QemuServer::spice_socket($vmid);
+
+ print "$$: CONNECT $vmid, $node, $socket\n" if $self->{debug};
+
+ # fixme: this needs root privs
+ tcp_connect "unix/", $socket, sub {
+ my ($fh) = @_
+ or die "connect to '$socket' failed: $!";
+
+ print "$$: CONNECTed to $socket\n" if $self->{debug};
+ $reqstate->{proxyhdl} = AnyEvent::Handle->new(
+ fh => $fh,
+ rbuf_max => 64*1024,
+ wbuf_max => 64*10*1024,
+ timeout => 0,
+ #linger => 0, # avoid problems with ssh - really needed ?
+ on_eof => sub {
+ my ($hdl) = @_;
+ eval {
+ $self->log_aborted_request($reqstate);
+ $self->client_do_disconnect($reqstate);
+ };
+ if (my $err = $@) { syslog('err', $err); }
+ },
+ on_error => sub {
+ my ($hdl, $fatal, $message) = @_;
+ eval {
+ $self->log_aborted_request($reqstate, $message);
+ $self->client_do_disconnect($reqstate);
+ };
+ if (my $err = $@) { syslog('err', "$err"); }
+ },
+ on_read => sub {
+ my ($hdl) = @_;
+
+ my $len = length($hdl->{rbuf});
+ my $data = substr($hdl->{rbuf}, 0, $len, '');
+
+ #print "READ1 $len\n";
+ $reqstate->{hdl}->push_write($data) if $reqstate->{hdl};
+ });
+
+ $reqstate->{hdl}->on_read(sub {
+ my ($hdl) = @_;
+
+ my $len = length($hdl->{rbuf});
+ my $data = substr($hdl->{rbuf}, 0, $len, '');
+
+ #print "READ0 $len\n";
+ $reqstate->{proxyhdl}->push_write($data) if $reqstate->{proxyhdl};
+ });
+
+ $reqstate->{hdl}->wbuf_max(64*10*1024);
+
+ # fixme: use stop_read/start_read if write buffer grows to much
+
+ my $proto = $reqstate->{proto} ? $reqstate->{proto}->{str} : 'HTTP/1.0';
+ my $res = "$proto 200 OK\015\012"; # hope this is the right answer?
+ $reqstate->{hdl}->push_write($res);
+ };
+ };
+ if (my $err = $@) {
+ $self->log_aborted_request($reqstate, $err);
+ $self->client_do_disconnect($reqstate);
+ }
+}
+
sub handle_request {
my ($self, $reqstate, $auth) = @_;
# header processing complete - authenticate now
my $auth = {};
- if ($path =~ m!$baseuri!) {
+ if ($self->{spiceproxy}) {
+ my $connect_str = $r->header('Host');
+ my ($vmid, $node) = PVE::AccessControl::verify_spice_connect_url($connect_str);
+ if (!($vmid && $node)) {
+ $self->error($reqstate, HTTP_UNAUTHORIZED, "invalid ticket");
+ return;
+ }
+ $self->handle_spice_proxy_request($reqstate, $vmid, $node);
+ return;
+ } elsif ($path =~ m!$baseuri!) {
my $token = $r->header('CSRFPreventionToken');
my $cookie = $r->header('Cookie');
my $ticket = PVE::REST::extract_auth_cookie($cookie);
my ($hdl, $line) = @_;
eval {
- # print "got request header: $line\n" if $self->{debug};
+ #print "got request header: $line\n" if $self->{debug};
$reqstate->{keep_alive}--;
$self->{tls_ctx} = AnyEvent::TLS->new(%{$self->{ssl}});
}
+ if ($self->{spiceproxy}) {
+ $known_methods = { CONNECT => 1 };
+ }
+
$self->open_access_log($self->{logfile}) if $self->{logfile};
$self->{max_conn_soft_limit} = $self->{max_conn} > 100 ? $self->{max_conn} - 20 : $self->{max_conn};
pvectl \
pvedaemon \
pveproxy \
+ spiceproxy \
pveversion \
pvesubscription \
pvemailforward.pl \
pvestatd.1 \
pvedaemon.1 \
pveproxy.1 \
+ spiceproxy.1 \
pveversion.1 \
pvesubscription.1 \
pveupgrade.1 \
pve-manager \
pvedaemon \
pveproxy \
+ spiceproxy \
pvebanner \
pvestatd \
pvenetcommit
--- /dev/null
+#!/bin/sh
+
+### BEGIN INIT INFO
+# Provides: spiceproxy
+# Required-Start: $remote_fs $network $syslog pveproxy
+# Required-Stop: $remote_fs $network $syslog pveproxy
+# Default-Start: 2 3 4 5
+# Default-Stop: 0 1 6
+# Short-Description: PVE SPICE Proxy Server
+### END INIT INFO
+
+. /lib/lsb/init-functions
+
+PATH=/sbin:/bin:/usr/bin:/usr/sbin
+DAEMON=/usr/bin/spiceproxy
+NAME=spiceproxy
+DESC="PVE SPICE Proxy Server"
+RUNDIR=/var/run/pveproxy
+PIDFILE=${RUNDIR}/spiceproxy.pid
+
+test -f $DAEMON || exit 0
+
+# avoid warnings about uninstalled locales when pveproxy executes commands
+export LC_ALL="C"
+
+mkdir -p ${RUNDIR} || true
+chmod 0700 ${RUNDIR} || true
+chown www-data:www-data ${RUNDIR} || true
+
+DAEMON_OPTS=""
+
+# Include defaults if available
+if [ -f /etc/default/$NAME ] ; then
+ ALLOW_FROM=""
+ DENY_FROM=""
+ POLICY=""
+
+ . /etc/default/$NAME
+
+ if [ -n "$ALLOW_FROM" ] ; then
+ DAEMON_OPTS="${DAEMON_OPTS} --allow-from ${ALLOW_FROM}"
+ fi
+
+ if [ -n "$DENY_FROM" ] ; then
+ DAEMON_OPTS="${DAEMON_OPTS} --deny-from ${DENY_FROM}"
+ fi
+
+ if [ -n "$POLICY" ] ; then
+ DAEMON_OPTS="${DAEMON_OPTS} --policy $POLICY"
+ fi
+fi
+
+
+case "$1" in
+ start)
+ log_daemon_msg "Starting $DESC" "$NAME"
+ start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON -- ${DAEMON_OPTS}
+ log_end_msg $?
+ ;;
+ stop)
+ log_daemon_msg "Stopping $DESC" "$NAME"
+ start-stop-daemon --stop --quiet --retry TERM/2/TERM/15/KILL/2 --pidfile $PIDFILE
+ log_end_msg $?
+ ;;
+ reload)
+ log_daemon_msg "Reloading $DESC" "$NAME"
+ if ( [ -e $PIDFILE ] && kill -0 `cat $PIDFILE`) then
+ start-stop-daemon --stop --signal HUP --pidfile $PIDFILE
+ else
+ start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON -- ${DAEMON_OPTS}
+ fi
+ log_end_msg $?
+ ;;
+ restart|force-reload)
+ log_daemon_msg "Restarting $DESC" "$NAME"
+ start-stop-daemon --stop --quiet --retry TERM/2/TERM/15/KILL/2 --pidfile $PIDFILE
+ sleep 2
+ start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON -- ${DAEMON_OPTS}
+ log_end_msg $?
+ ;;
+ *)
+ N=/etc/init.d/$NAME
+ echo "Usage: $N {start|stop|restart|force-reload}"
+ exit 1
+ ;;
+esac
+
+exit 0
--- /dev/null
+#!/usr/bin/perl -w -T
+
+$ENV{'PATH'} = '/sbin:/bin:/usr/sbin:/usr/bin';
+
+delete @ENV{qw(IFS CDPATH ENV BASH_ENV)};
+
+use lib '..';
+use strict;
+use English;
+use Getopt::Long;
+use PVE::SafeSyslog;
+use PVE::APIDaemon;
+
+my $pidfile = "/var/run/pveproxy/spiceproxy.pid";
+my $lockfile = "/var/lock/spiceproxy.lck";
+
+my $opt_debug;
+
+initlog ('spiceproxy');
+
+if (!GetOptions ('debug' => \$opt_debug)) {
+ die "usage: $0 [--debug]\n";
+}
+
+$SIG{'__WARN__'} = sub {
+ my $err = $@;
+ my $t = $_[0];
+ chomp $t;
+ syslog('warning', "WARNING: %s", $t);
+ $@ = $err;
+};
+
+$0 = "spiceproxy";
+
+my $gid = getgrnam('www-data') || die "getgrnam failed - $!\n";
+POSIX::setgid($gid) || die "setgid $gid failed - $!\n";
+$EGID = "$gid $gid"; # this calls setgroups
+my $uid = getpwnam('www-data') || die "getpwnam failed - $!\n";
+POSIX::setuid($uid) || die "setuid $uid failed - $!\n";
+
+# just to be sure
+die "detected strange uid/gid\n" if !($UID == $uid && $EUID == $uid && $GID eq "$gid $gid" && $EGID eq "$gid $gid");
+
+my $cpid;
+my $daemon;
+eval {
+ $daemon = PVE::APIDaemon->new(
+ port => 3128,
+ keep_alive => 0,
+ max_workers => 1, # do we need more?
+ max_conn => 500,
+ lockfile => $lockfile,
+ debug => $opt_debug,
+ spiceproxy => 1,
+ logfile => '/var/log/pveproxy/spice.log',
+ );
+};
+
+my $err = $@;
+
+if ($err) {
+ syslog ('err' , "unable to start server: $err");
+ print STDERR $err;
+ exit (-1);
+}
+
+if ($opt_debug || !($cpid = fork ())) {
+
+ $SIG{PIPE} = 'IGNORE';
+ $SIG{INT} = 'IGNORE' if !$opt_debug;
+
+ $SIG{TERM} = $SIG{QUIT} = sub {
+ syslog ('info' , "server closing");
+
+ $SIG{INT} = 'DEFAULT';
+
+ unlink "$pidfile";
+
+ exit (0);
+ };
+
+ syslog ('info' , "starting server");
+
+ if (!$opt_debug) {
+ # redirect STDIN/STDOUT/SDTERR to /dev/null
+ open STDIN, '</dev/null' || die "can't read /dev/null [$!]";
+ open STDOUT, '>/dev/null' || die "can't write /dev/null [$!]";
+ open STDERR, '>&STDOUT' || die "can't open STDERR to STDOUT [$!]";
+ }
+
+ POSIX::setsid();
+
+ eval {
+ $daemon->start_server();
+ };
+ my $err = $@;
+
+ if ($err) {
+ syslog ('err' , "unexpected server error: $err");
+ print STDERR $err if $opt_debug;
+ exit (-1);
+ }
+
+} else {
+
+ open (PIDFILE, ">$pidfile") ||
+ die "cant write '$pidfile' - $! :ERROR";
+ print PIDFILE "$cpid\n";
+ close (PIDFILE) ||
+ die "cant write '$pidfile' - $! :ERROR";
+}
+
+exit (0);
+
+__END__
+
+=head1 NAME
+
+spiceproxy - SPICE proxy server for Proxmox VE
+
+=head1 SYNOPSIS
+
+spiceproxy [--debug]
+
+=head1 DESCRIPTION
+
+SPICE proxy server for Proxmox VE. Listens on port 3128.
+
+=head1 COPYRIGHT AND DISCLAIMER
+
+ Copyright (C) 2007-2013 Proxmox Server Solutions GmbH
+
+ This program is free software: you can redistribute it and/or modify it
+ under the terms of the GNU Affero General Public License as published
+ by the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public
+ License along with this program. If not, see
+ <http://www.gnu.org/licenses/>.
+
/etc/init.d/pve-manager
/etc/init.d/pvedaemon
/etc/init.d/pveproxy
+/etc/init.d/spiceproxy
/etc/init.d/pvebanner
/etc/init.d/pvenetcommit
/etc/init.d/pvestatd
test -e /proxmox_install_mode || invoke-rc.d pvedaemon restart
test -e /proxmox_install_mode || invoke-rc.d pvestatd restart
test -e /proxmox_install_mode || invoke-rc.d pveproxy restart
+ test -e /proxmox_install_mode || invoke-rc.d spiceproxy restart
exit 0;;
update-rc.d pvedaemon defaults 21 79 >/dev/null
update-rc.d pveproxy defaults 21 79 >/dev/null
+ update-rc.d spiceproxy defaults 21 79 >/dev/null
update-rc.d pvestatd defaults 21 79 >/dev/null
update-rc.d pvebanner start 99 2 3 4 5 . >/dev/null
update-rc.d pvenetcommit start 15 S . >/dev/null
test -e /proxmox_install_mode || invoke-rc.d pvedaemon restart
test -e /proxmox_install_mode || invoke-rc.d pveproxy restart
+ test -e /proxmox_install_mode || invoke-rc.d spiceproxy restart
test -e /proxmox_install_mode || invoke-rc.d pvestatd restart
# rewrite banner
if [ "$1" = purge ]; then
update-rc.d pvedaemon remove >/dev/null 2>&1
update-rc.d pveproxy remove >/dev/null 2>&1
+ update-rc.d spiceproxy remove >/dev/null 2>&1
update-rc.d pvestatd remove >/dev/null 2>&1
update-rc.d pvebanner remove >/dev/null 2>&1
update-rc.d pvenetcommit remove >/dev/null 2>&1