From 33afb29b2928267abbefc72c526756691050f952 Mon Sep 17 00:00:00 2001 From: Dietmar Maurer Date: Wed, 26 Jun 2013 13:21:14 +0200 Subject: [PATCH] add spiceproxy server --- PVE/HTTPServer.pm | 108 ++++++++++++++++++++++++++++++- bin/Makefile | 2 + bin/init.d/Makefile | 1 + bin/init.d/spiceproxy | 88 +++++++++++++++++++++++++ bin/spiceproxy | 146 ++++++++++++++++++++++++++++++++++++++++++ debian/conffiles | 1 + debian/postinst | 3 + debian/postrm | 1 + 8 files changed, 347 insertions(+), 3 deletions(-) create mode 100755 bin/init.d/spiceproxy create mode 100755 bin/spiceproxy diff --git a/PVE/HTTPServer.pm b/PVE/HTTPServer.pm index 3ed5ae26..50b6fca1 100755 --- a/PVE/HTTPServer.pm +++ b/PVE/HTTPServer.pm @@ -11,6 +11,7 @@ use File::stat qw(); 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; @@ -215,7 +216,7 @@ sub response { #print "SEND(without content) $res\n" if $self->{debug}; $res .= "\015\012"; - $res .= $content; + $res .= $content if $content; $self->log_request($reqstate, $reqstate->{request}); @@ -475,6 +476,94 @@ sub handle_api2_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) = @_; @@ -753,7 +842,16 @@ sub unshift_read_header { # 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); @@ -862,7 +960,7 @@ sub push_request_header { 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}--; @@ -1142,6 +1240,10 @@ sub new { $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}; diff --git a/bin/Makefile b/bin/Makefile index 4181c6ec..1b0a3bc8 100644 --- a/bin/Makefile +++ b/bin/Makefile @@ -12,6 +12,7 @@ SCRIPTS = \ pvectl \ pvedaemon \ pveproxy \ + spiceproxy \ pveversion \ pvesubscription \ pvemailforward.pl \ @@ -25,6 +26,7 @@ MANS = \ pvestatd.1 \ pvedaemon.1 \ pveproxy.1 \ + spiceproxy.1 \ pveversion.1 \ pvesubscription.1 \ pveupgrade.1 \ diff --git a/bin/init.d/Makefile b/bin/init.d/Makefile index 00eae7f1..bfb8139d 100644 --- a/bin/init.d/Makefile +++ b/bin/init.d/Makefile @@ -6,6 +6,7 @@ SCRIPTS = \ pve-manager \ pvedaemon \ pveproxy \ + spiceproxy \ pvebanner \ pvestatd \ pvenetcommit diff --git a/bin/init.d/spiceproxy b/bin/init.d/spiceproxy new file mode 100755 index 00000000..160311a2 --- /dev/null +++ b/bin/init.d/spiceproxy @@ -0,0 +1,88 @@ +#!/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 diff --git a/bin/spiceproxy b/bin/spiceproxy new file mode 100755 index 00000000..db85e1df --- /dev/null +++ b/bin/spiceproxy @@ -0,0 +1,146 @@ +#!/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 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 + . + diff --git a/debian/conffiles b/debian/conffiles index 73e61a46..d832de86 100644 --- a/debian/conffiles +++ b/debian/conffiles @@ -1,6 +1,7 @@ /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 diff --git a/debian/postinst b/debian/postinst index 9eb55b7d..af587c73 100755 --- a/debian/postinst +++ b/debian/postinst @@ -28,6 +28,7 @@ case "$1" in 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;; @@ -60,6 +61,7 @@ case "$1" in 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 @@ -67,6 +69,7 @@ case "$1" in 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 diff --git a/debian/postrm b/debian/postrm index 092b2b07..f5b9f764 100755 --- a/debian/postrm +++ b/debian/postrm @@ -6,6 +6,7 @@ set -e 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 -- 2.39.5