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