Don't point STDOUT to SOCK
[webmin.git] / fastrpc.cgi
1 #!/usr/local/bin/perl
2 # Handles remote_* function calls by a faster method. When first called
3 # as a CGI, forks and starts listening on a port which is returned to the
4 # client. From then on, direct TCP connections can be made to this port
5 # to send requests and get replies.
6
7 do './web-lib.pl';
8 use POSIX;
9 use Socket;
10 $force_lang = $default_lang;
11 &init_config();
12 print "Content-type: text/plain\n\n";
13
14 # Can this user make remote calls?
15 %access = &get_module_acl();
16 if ($access{'rpc'} == 0 || $access{'rpc'} == 2 &&
17     $base_remote_user ne 'admin' && $base_remote_user ne 'root' &&
18     $base_remote_user ne 'sysadm') {
19         print "0 Invalid user for RPC\n";
20         exit;
21         }
22
23 # Find a free port
24 &get_miniserv_config(\%miniserv);
25 $port = $miniserv{'port'} || 10000;
26 $aerr = &allocate_socket(MAIN, \$port);
27 if ($aerr) {
28         print "0 $aerr\n";
29         exit;
30         }
31 if (open(RANDOM, "/dev/urandom")) {
32         local $tmpsid;
33         read(RANDOM, $tmpsid, 16);
34         $sid = lc(unpack('h*', $tmpsid));
35         close RANDOM;
36         }
37 else {
38         $sid = time()*$$;
39         }
40 $version = &get_webmin_version();
41 print "1 $port $sid $version\n";
42
43 # Fork and listen for calls ..
44 $pid = fork();
45 if ($pid < 0) {
46         die "fork() failed : $!";
47         }
48 elsif ($pid) {
49         exit;
50         }
51 untie(*STDIN);
52 untie(*STDOUT);
53
54 # Accept the TCP connection
55 $acptaddr = accept(SOCK, MAIN);
56 die "accept failed!" if (!$acptaddr);
57 $oldsel = select(SOCK);
58 $| = 1;
59 select($oldsel);
60
61 $rcount = 0;
62 while(1) {
63         # Wait for the request. Wait longer if this isn't the first one
64         local $rmask;
65         vec($rmask, fileno(SOCK), 1) = 1;
66         local $sel = select($rmask, undef, undef, $rcount ? 360 : 60);
67         if ($sel <= 0) {
68                 print STDERR "fastrpc: session timed out\n"
69                         if ($gconfig{'rpcdebug'});
70                 last;
71                 }
72
73         local $line = <SOCK>;
74         last if (!$line);
75         local ($len, $auth) = split(/\s+/, $line);
76         die "Invalid session ID" if ($auth ne $sid);
77         local $rawarg;
78         while(length($rawarg) < $len) {
79                 local $got;
80                 local $rv = read(SOCK, $got, $len - length($rawarg));
81                 exit if ($rv <= 0);
82                 $rawarg .= $got;
83                 }
84         print STDERR "fastrpc: raw $rawarg\n" if ($gconfig{'rpcdebug'});
85         local $arg = &unserialise_variable($rawarg);
86
87         # Process it
88         local $rawrv;
89         if ($arg->{'action'} eq 'ping') {
90                 # Just respond with an OK
91                 print STDERR "fastrpc: ping\n" if ($gconfig{'rpcdebug'});
92                 $rawrv = &serialise_variable( { 'status' => 1 } );
93                 }
94         elsif ($arg->{'action'} eq 'check') {
95                 # Check if some module is supported
96                 print STDERR "fastrpc: check $arg->{'module'}\n" if ($gconfig{'rpcdebug'});
97                 $rawrv = &serialise_variable(
98                         { 'status' => 1,
99                           'rv' => &foreign_check($arg->{'module'}, undef, undef,
100                                                  $arg->{'api'}) } );
101                 }
102         elsif ($arg->{'action'} eq 'config') {
103                 # Get the config for some module
104                 print STDERR "fastrpc: config $arg->{'module'}\n" if ($gconfig{'rpcdebug'});
105                 local %config = &foreign_config($arg->{'module'});
106                 $rawrv = &serialise_variable(
107                         { 'status' => 1, 'rv' => \%config } );
108                 }
109         elsif ($arg->{'action'} eq 'write') {
110                 # Transfer data to a local temp file
111                 local $file = $arg->{'file'} ? $arg->{'file'} :
112                               $arg->{'name'} ? &tempname($arg->{'name'}) :
113                                                &tempname();
114                 print STDERR "fastrpc: write $file\n" if ($gconfig{'rpcdebug'});
115                 open(FILE, ">$file");
116                 binmode(FILE);
117                 print FILE $arg->{'data'};
118                 close(FILE);
119                 $rawrv = &serialise_variable(
120                         { 'status' => 1, 'rv' => $file } );
121                 }
122         elsif ($arg->{'action'} eq 'tcpwrite') {
123                 # Transfer data to a local temp file over TCP connection
124                 local $file = $arg->{'file'} ? $arg->{'file'} :
125                               $arg->{'name'} ? &tempname($arg->{'name'}) :
126                                                &tempname();
127                 print STDERR "fastrpc: tcpwrite $file\n" if ($gconfig{'rpcdebug'});
128                 local $tsock = time().$$;
129                 local $tport = $port + 1;
130                 &allocate_socket($tsock, \$tport);
131                 if (!fork()) {
132                         # Accept connection in separate process
133                         print STDERR "fastrpc: tcpwrite $file port $tport\n" if ($gconfig{'rpcdebug'});
134                         local $rmask;
135                         vec($rmask, fileno($tsock), 1) = 1;
136                         local $sel = select($rmask, undef, undef, 30);
137                         exit if ($sel <= 0);
138                         accept(TRANS, $tsock) || exit;
139                         print STDERR "fastrpc: tcpwrite $file accepted\n" if ($gconfig{'rpcdebug'});
140                         local $buf;
141                         local $err;
142                         if (open(FILE, ">$file")) {
143                                 binmode(FILE);
144                                 print STDERR "fastrpc: tcpwrite $file writing\n" if ($gconfig{'rpcdebug'});
145                                 while(read(TRANS, $buf, 1024) > 0) {
146                                         local $ok = (print FILE $buf);
147                                         if (!$ok) {
148                                                 $err = "Write to $file failed : $!";
149                                                 last;
150                                                 }
151                                         }
152                                 close(FILE);
153                                 print STDERR "fastrpc: tcpwrite $file written\n" if ($gconfig{'rpcdebug'});
154                                 }
155                         else {
156                                 print STDERR "fastrpc: tcpwrite $file open failed $!\n" if ($gconfig{'rpcdebug'});
157                                 $err = "Failed to open $file : $!";
158                                 }
159                         print TRANS $err ? "$err\n" : "OK\n";
160                         close(TRANS);
161                         exit;
162                         }
163                 close($tsock);
164                 print STDERR "fastrpc: tcpwrite $file done\n" if ($gconfig{'rpcdebug'});
165                 $rawrv = &serialise_variable(
166                         { 'status' => 1, 'rv' => [ $file, $tport ] } );
167                 }
168         elsif ($arg->{'action'} eq 'read') {
169                 # Transfer data from a file
170                 print STDERR "fastrpc: read $arg->{'file'}\n" if ($gconfig{'rpcdebug'});
171                 local ($data, $got);
172                 open(FILE, $arg->{'file'});
173                 binmode(FILE);
174                 while(read(FILE, $got, 1024) > 0) {
175                         $data .= $got;
176                         }
177                 close(FILE);
178                 $rawrv = &serialise_variable(
179                         { 'status' => 1, 'rv' => $data } );
180                 }
181         elsif ($arg->{'action'} eq 'tcpread') {
182                 # Transfer data from a file over TCP connection
183                 print STDERR "fastrpc: tcpread $arg->{'file'}\n" if ($gconfig{'rpcdebug'});
184                 if (!open(FILE, $arg->{'file'})) {
185                         $rawrv = &serialise_variable(
186                                 { 'status' => 1, 'rv' => [ undef, "Failed to open $arg->{'file'} : $!" ] } );
187                         }
188                 else {
189                         binmode(FILE);
190                         local $tsock = time().$$;
191                         local $tport = $port + 1;
192                         &allocate_socket($tsock, \$tport);
193                         if (!fork()) {
194                                 # Accept connection in separate process
195                                 local $rmask;
196                                 vec($rmask, fileno($tsock), 1) = 1;
197                                 local $sel = select($rmask, undef, undef, 30);
198                                 exit if ($sel <= 0);
199                                 accept(TRANS, $tsock) || exit;
200                                 local $buf;
201                                 while(read(FILE, $buf, 1024) > 0) {
202                                         print TRANS $buf;
203                                         }
204                                 close(FILE);
205                                 close(TRANS);
206                                 exit;
207                                 }
208                         close(FILE);
209                         close($tsock);
210                         print STDERR "fastrpc: tcpread $arg->{'file'} done\n" if ($gconfig{'rpcdebug'});
211                         $rawrv = &serialise_variable(
212                                 { 'status' => 1, 'rv' => [ $arg->{'file'}, $tport ] } );
213                         }
214                 }
215         elsif ($arg->{'action'} eq 'require') {
216                 # require a library
217                 print STDERR "fastrpc: require $arg->{'module'}/$arg->{'file'}\n" if ($gconfig{'rpcdebug'});
218                 eval {
219                         &foreign_require($arg->{'module'},
220                                          $arg->{'file'});
221                         };
222                 if ($@) {
223                         print STDERR "fastrpc: require error $@\n" if ($gconfig{'rpcdebug'});
224                         $rawrv = &serialise_variable( { 'status' => 0,
225                                                         'rv' => $@ });
226                         }
227                 else {
228                         print STDERR "fastrpc: require done\n" if ($gconfig{'rpcdebug'});
229                         $rawrv = &serialise_variable( { 'status' => 1 });
230                         }
231                 }
232         elsif ($arg->{'action'} eq 'call') {
233                 # execute a function
234                 print STDERR "fastrpc: call $arg->{'module'}::$arg->{'func'}(",join(",", @{$arg->{'args'}}),")\n" if ($gconfig{'rpcdebug'});
235                 local @rv;
236                 local $main::error_must_die = 1;
237                 eval {
238                         @rv = &foreign_call($arg->{'module'},
239                                             $arg->{'func'},
240                                             @{$arg->{'args'}});
241                         };
242                 if ($@) {
243                         print STDERR "fastrpc: call error $@\n" if ($gconfig{'rpcdebug'});
244                         $rawrv = &serialise_variable(
245                                 { 'status' => 0, 'rv' => $@ } );
246                         }
247                 elsif (@rv == 1) {
248                         $rawrv = &serialise_variable(
249                                 { 'status' => 1, 'rv' => $rv[0] } );
250                         }
251                 else {
252                         $rawrv = &serialise_variable(
253                                 { 'status' => 1, 'arv' => \@rv } );
254                         }
255                 print STDERR "fastrpc: call $arg->{'module'}::$arg->{'func'} done = ",join(",", @rv),"\n" if ($gconfig{'rpcdebug'});
256                 }
257         elsif ($arg->{'action'} eq 'eval') {
258                 # eval some perl code
259                 print STDERR "fastrpc: eval $arg->{'module'} $arg->{'code'}\n" if ($gconfig{'rpcdebug'});
260                 local $rv;
261                 if ($arg->{'module'}) {
262                         local $pkg = $arg->{'module'};
263                         $pkg =~ s/[^A-Za-z0-9]/_/g;
264                         $rv = eval "package $pkg;\n".
265                                    $arg->{'code'}."\n";
266                         }
267                 else {
268                         $rv = eval $arg->{'code'};
269                         }
270                 print STDERR "fastrpc: eval $arg->{'module'} $arg->{'code'} done = $rv error = $@\n" if ($gconfig{'rpcdebug'});
271                 $rawrv = &serialise_variable(
272                         { 'status' => 1, 'rv' => $rv } );
273                 }
274         elsif ($arg->{'action'} eq 'quit') {
275                 print STDERR "fastrpc: quit\n" if ($gconfig{'rpcdebug'});
276                 $rawrv = &serialise_variable( { 'status' => 1 } );
277                 }
278         else {
279                 print STDERR "fastrpc: unknown $arg->{'action'}\n" if ($gconfig{'rpcdebug'});
280                 $rawrv = &serialise_variable( { 'status' => 0 } );
281                 }
282
283         # Send back to the client
284         print SOCK length($rawrv),"\n";
285         print SOCK $rawrv;
286         last if ($arg->{'action'} eq 'quit');
287         $rcount++;
288         }
289
290 # allocate_socket(handle, &port)
291 sub allocate_socket
292 {
293 local ($fh, $port) = @_;
294 local $proto = getprotobyname('tcp');
295 if (!socket($fh, PF_INET, SOCK_STREAM, $proto)) {
296         return "socket failed : $!";
297         }
298 setsockopt($fh, SOL_SOCKET, SO_REUSEADDR, pack("l", 1));
299 while(1) {
300         $$port++;
301         last if (bind($fh, sockaddr_in($$port, INADDR_ANY)));
302         }
303 listen($fh, SOMAXCONN);
304 return undef;
305 }
306