]> git.proxmox.com Git - pve-manager.git/commitdiff
add spiceproxy server
authorDietmar Maurer <dietmar@proxmox.com>
Wed, 26 Jun 2013 11:21:14 +0000 (13:21 +0200)
committerDietmar Maurer <dietmar@proxmox.com>
Wed, 26 Jun 2013 11:21:14 +0000 (13:21 +0200)
PVE/HTTPServer.pm
bin/Makefile
bin/init.d/Makefile
bin/init.d/spiceproxy [new file with mode: 0755]
bin/spiceproxy [new file with mode: 0755]
debian/conffiles
debian/postinst
debian/postrm

index 3ed5ae26234a2444bd214480b3e8cac56fe6c806..50b6fca19eebf15dab0b8dc1212cab67483327ce 100755 (executable)
@@ -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};
index 4181c6ece0b465adfb8285d2586f9da6c250a580..1b0a3bc82f230d16285cff6a1e494f1952fa959c 100644 (file)
@@ -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            \
index 00eae7f1637290da12fad130a9c2311322483068..bfb8139d7d3e39fc199fbb009167c80ab6b7edd7 100644 (file)
@@ -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 (executable)
index 0000000..160311a
--- /dev/null
@@ -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 (executable)
index 0000000..db85e1d
--- /dev/null
@@ -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 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/>.
+
index 73e61a460d528dbfc392e0fedffb49aa70571414..d832de86f9e419ee78b0e11518b25ff116550f71 100644 (file)
@@ -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
index 9eb55b7df72a029a056b3454fa01a3586e98ffe9..af587c739aa7d839506417ffd5de4f1ef9ad5271 100755 (executable)
@@ -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
index 092b2b079c56ad63b492eab1f538f6c83be419e8..f5b9f764cbac95da88d1a63241e67db9c826bf91 100755 (executable)
@@ -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