#!/usr/bin/perl # # you can enable unix sockets, tcp sockets, or both (or neither...) # # enabling tcp sockets can be a security risk. If you don't understand why, # you shouldn't enable it! # $use_unix = 1; $use_tcp = 1; # tcp is enabled only when authorization is available use Socket; use strict; use vars qw($use_unix $use_tcp $trace_res $server_quit $max_pkt $unix $tcp $ps_flags $auth @authorized $exclusive $rm $saved_rm %stats); # the '' might be required (i.e. no ()). why?? use Gimp (); use Gimp::Net (); Gimp::set_trace(\$trace_res); Gimp::ignore_functions(qw(gimp_progress_init gimp_progress_update)); # # the protocol is quite easy ;) # at connect() time the server returns # PERL-SERVER protocolversion [AUTH] # # length_of_packet cmd # # cmd response description # AUTH password ok [message] authorize yourself # QUIT quit server # EXEC in-args status out-args run simple command # TRCE trace in-args trace status out-args run simple command (with tracing) # TEST procname bool check for procedure existance # DTRY in-args destroy all argument objects # LOCK lock? shared? lock or unlock # RSET reset server (NYI) # # args is "number of arguments" arguments preceded by length # type is first character # Sscalar-value # Aelem1\0elem2... # Rclass\0scalar-value # $server_quit = 0; my $max_pkt = 1024*1024*8; my $exclusive = 0; sub slog { return if $ps_flags & &Gimp::_PS_FLAG_QUIET; print time(),": ",@_,"\n"; } sub destroy_objects { Gimp::Net::destroy_objects(@_); } # this is hardcoded into handle_request! sub reply { my $fh=shift; my $data=Gimp::Net::args2net(0,@_); print $fh pack("N",length($data)).$data; } sub handle_request($) { my($fh)=@_; my($length,$req,$data,@args,$trace_level); eval { local $SIG{ALRM}=sub { die "\n" }; #alarm(6) unless $ps_flags & &Gimp::_PS_FLAG_BATCH; read($fh,$length,4) == 4 or die "\n"; $length=unpack("N",$length); $length>0 && $length<$max_pkt or die "\n"; #alarm(6) unless $ps_flags & &Gimp::_PS_FLAG_BATCH; read($fh,$req,4) == 4 or die "\n"; #alarm(20) unless $ps_flags & &Gimp::_PS_FLAG_BATCH; read($fh,$data,$length-4) == $length-4 or die "\n"; #alarm(0); }; return 0 if $@; if(!$auth or $authorized[fileno($fh)]) { if($req eq "EXEC") { no strict 'refs'; ($req,@args)=Gimp::Net::net2args(1,$data); @args=eval { Gimp->$req(@args) }; $data=Gimp::Net::args2net(1,$@,@args); print $fh pack("N",length($data)).$data; } elsif ($req eq "TEST") { no strict 'refs'; print $fh (defined(*{"Gimp::Lib::$data"}{CODE}) || Gimp::_gimp_procedure_available($data)) ? "1" : "0"; } elsif ($req eq "DTRY") { Gimp::Net::destroy_objects Gimp::Net::net2args 0,$data; } elsif($req eq "TRCE") { no strict 'refs'; ($trace_level,$req,@args)=Gimp::Net::net2args 1,$data; Gimp::set_trace($trace_level); $trace_res=""; @args=eval { Gimp->$req(@args) }; $data=Gimp::Net::args2net(1,$trace_res,$@,@args); print $fh pack("N",length($data)).$data; Gimp::set_trace(0); } elsif ($req eq "QUIT") { slog "received QUIT request"; $server_quit = 1; } elsif($req eq "AUTH") { $data=Gimp::Net::args2net(0,1,"authorization unnecessary"); print $fh pack("N",length($data)).$data; } elsif($req eq "LOCK") { my($lock,$shared)=unpack("N*",$data); slog "WARNING: shared locking requested but not implemented" if $shared; if($lock) { unless($exclusive) { $saved_rm=$rm; undef $rm; vec($rm,fileno($fh),1)=1; } $exclusive++; } else { if ($exclusive) { $exclusive--; $rm = $saved_rm unless $exclusive; } else { slog "WARNING: client tried to unlock without holding a lock"; } } } else { print $fh pack("N",0); slog "illegal command received, aborting connection"; return 0; } } else { if($req eq "AUTH") { my($ok,$msg); if($data eq $auth) { $ok=1; $authorized[fileno($fh)]=1; } else { $ok=0; $msg="wrong authorization, aborting connection"; slog $msg; sleep 5; # safety measure } $data=Gimp::Net::args2net(0,$ok,$msg); print $fh pack("N",length($data)).$data; return $ok; } else { print $fh pack("N",0); slog "unauthorized command received, aborting connection"; return 0; } } return 1; } sub extension_perl_server { my $run_mode=$_[0]; $ps_flags=$_[1]; my $extra=$_[2]; if ($run_mode == &Gimp::RUN_NONINTERACTIVE) { if ($ps_flags & &Gimp::_PS_FLAG_BATCH) { my($fh) = local *FH; open $fh,"+<&$extra" or die "unable to open Gimp::Net communications socket\n"; select $fh; $|=1; select STDOUT; reply $fh,"PERL-SERVER",$Gimp::_PROT_VERSION; while(!$server_quit and !eof($fh)) { last unless handle_request($fh); } # Gimp::gimp_quit(0); # borken in libgimp #d#FIXME# kill 'KILL',getppid(); # borken do not do this.. #d#FIXME# exit(0); # close $fh; return; } } else { $run_mode=&Gimp::RUN_INTERACTIVE; $ps_flags=0; } my $host = $ENV{'GIMP_HOST'}; $auth = $host=~s/^(.*)\@// ? $1 : undef; # get authorization slog "server version $Gimp::VERSION started".($auth ? ", authorization required" : ""); $SIG{PIPE}='IGNORE'; # may not work, since libgimp (eech) overwrites it. my($unix_path)=$Gimp::Net::default_unix_dir.$Gimp::Net::default_unix_sock; my(%handles,$r,$fh,$f); if ($host ne "") { if ($host=~s{^spawn/}{}) { die "invalid GIMP_HOST: 'spawn' is not a valid connection method for the server"; } elsif ($host=~s{^unix/}{/}) { $unix = local *FH; socket($unix,AF_UNIX,SOCK_STREAM,PF_UNSPEC) && bind($unix,sockaddr_un $host) && listen($unix,5) or die "unable to create listening unix socket: $!\n"; slog "accepting connections in $host"; vec($rm,fileno($unix),1)=1; } else { $host=~s{^tcp/}{}; my($host,$port)=split /:/,$host; $port=$Gimp::Net::default_tcp_port unless $port; $tcp = local *FH; socket($tcp,PF_INET,SOCK_STREAM,scalar getprotobyname('tcp') || 6) && bind($tcp,sockaddr_in $port,INADDR_ANY) && setsockopt($tcp,SOL_SOCKET,SO_REUSEADDR,1) && listen($tcp,5) or die "unable to create listening tcp socket: $!\n"; slog "accepting connections on port $port"; vec($rm,fileno($tcp),1)=1; } } else { if ($use_unix) { unlink $unix_path; rmdir $Gimp::Net::default_unix_dir; mkdir $Gimp::Net::default_unix_dir,0700 or die "$!"; $unix = local *FH; socket($unix,AF_UNIX,SOCK_STREAM,PF_UNSPEC) && bind($unix,sockaddr_un $unix_path) && listen($unix,5) or die "unable to create listening unix socket: $!\n"; slog "accepting connections on $unix_path"; vec($rm,fileno($unix),1)=1; } if ($use_tcp && $auth) { $tcp = local *FH; socket($tcp,PF_INET,SOCK_STREAM,scalar getprotobyname('tcp') || 6) && bind($tcp,sockaddr_in $Gimp::Net::default_tcp_port,INADDR_ANY) && setsockopt($tcp,SOL_SOCKET,SO_REUSEADDR,1) && listen($tcp,5) or die "unable to create listening tcp socket: $!\n"; slog "accepting connections on port $Gimp::Net::default_tcp_port"; vec($rm,fileno($tcp),1)=1; } } !$tcp || $auth or die "authorization required for tcp connections"; sub new_connection { my $fh = shift; select $fh; $|=1; select STDOUT; $handles{fileno($fh)}=$fh; my @r = ("PERL-SERVER",$Gimp::_PROT_VERSION); push(@r,"AUTH") if $auth; reply $fh,@r; vec($rm,fileno($fh),1)=1; $stats{fileno($fh)}=[0,time]; } while(!$server_quit) { if(select($r=$rm,undef,undef,undef)>0) { if ($tcp && vec($r,fileno($tcp),1)) { my $h = local *FH; my ($port,$host) = sockaddr_in (accept ($h,$tcp)) or die "unable to accept tcp connection: $!\n"; new_connection($h); slog "accepted tcp connection from ",inet_ntoa($host),":$port"; } if ($unix && vec($r,fileno($unix),1)) { my $h = local *FH; accept ($h,$unix) or die "unable to accept unix connection: $!\n"; new_connection($h); slog("accepted unix connection"); } for $f (keys(%handles)) { if(vec($r,$f,1)) { $fh=$handles{$f}; if(handle_request($fh)) { $stats{$f}[0]++; } else { slog "closing connection ",$f," ($stats{$f}[0] requests in ",time-$stats{$f}[1]," seconds)"; if ($exclusive) { $rm = $saved_rm; $exclusive = 0; slog "WARNING: client disconnected while holding an active lock\n"; } vec($rm,$f,1)=0; delete $handles{$f}; undef $fh; } last; # this is because the client might have called lock() } } } } slog "server going down..."; if ($use_tcp) { undef $tcp; } if ($use_unix) { undef $unix; unlink $unix_path; rmdir $Gimp::Net::default_unix_dir; } } sub query { Gimp->install_procedure("extension_perl_server", "Start the Gimp-Perl Server", "This is the server for plug-ins written using the Gimp::Net module", "Marc Lehmann ", "Marc Lehmann", "1998-07-22", "/Xtns/Perl/Server", "*",&Gimp::PROC_EXTENSION, [ [&Gimp::PARAM_INT32, "run_mode", "Interactive, [non-interactive]"], [&Gimp::PARAM_INT32, "flags", "internal flags (must be 0)"], [&Gimp::PARAM_INT32, "extra", "multi-purpose ;)"], ],[]); Gimp->install_procedure("gimp_procedural_db_constant_register", "Register a plug-in specific integer constant", "Plug-ins should register their custom constants using this function, so". "other plug-ins (notably script-languages) can access these using symbolic names", "Marc Lehmann ", "Marc Lehmann", "1999-07-07", undef, "*",&Gimp::PROC_EXTENSION, [ [&Gimp::PARAM_STRING, "procedure", "The name of the function that uses this constant"], [&Gimp::PARAM_STRING, "arg_num", "The name of the argument that this constant is used for"], [&Gimp::PARAM_STRING, "constant_name", "The name of the constant, should be all-uppercase"], [&Gimp::PARAM_INT32, "constant_value", "The (integer) value for this constant"], ],[]); Gimp->install_procedure("gimp_procedural_db_set_default", "Set the default value for a plug-in argument", "Plug-ins should register default values for their arguments", "Marc Lehmann ", "Marc Lehmann", "1999-07-07", undef, "*",&Gimp::PROC_EXTENSION, [ [&Gimp::PARAM_STRING, "procedure", "The name of the function that uses this constant"], [&Gimp::PARAM_STRING, "arg_num", "The name of the argument that this constant is used for"], [&Gimp::PARAM_INT32, "default_value", "The default value for this constant"], ],[]); } sub quit { } exit &Gimp::main;