2 # Handles xml-rpc requests from arbitrary clients. Each is a call to a
3 # function in a Webmin module.
4 # XXX special function for file transfer?
5 # XXX command-line mode
7 if (!$ENV{'GATEWAY_INTERFACE'}) {
10 $ENV{'WEBMIN_CONFIG'} ||= "/etc/webmin";
11 $ENV{'WEBMIN_VAR'} ||= "/var/webmin";
12 if ($0 =~ /^(.*\/)[^\/]+$/) {
16 $0 = "$pwd/xmlrpc.pl";
18 $< == 0 || die "xmlrpc.pl must be run as root";
23 $force_lang = $default_lang;
25 $main::error_must_die = 1;
27 # Can this user make remote calls?
29 %access = &get_module_acl();
30 if ($access{'rpc'} == 0 || $access{'rpc'} == 2 &&
31 $base_remote_user ne 'admin' && $base_remote_user ne 'root' &&
32 $base_remote_user ne 'sysadm') {
33 &error_exit(1, "Invalid user for RPC");
37 # Load the XML parser module
38 eval "use XML::Parser";
40 &error_exit(2, "XML::Parser Perl module is not installed");
53 my $clen = $ENV{'CONTENT_LENGTH'};
54 while(length($rawxml) < $clen) {
56 my $got = read(STDIN, $buf, $clen - length($rawxml));
58 &error_exit(3, "Failed to read $clen bytes");
65 my $parser = new XML::Parser('Style' => 'Tree');
67 eval { $xml = $parser->parse($rawxml); };
69 &error_exit(4, "Invalid XML : $@");
72 # Look for the method calls, and invoke each one
73 my $xmlrv = "<?xml version=\"1.0\" encoding=\"$default_charset\"?>\n";
74 foreach my $mc (&find_xmls("methodCall", $xml)) {
75 # Find the method name and module
76 my ($mn) = &find_xmls("methodName", $mc);
78 my ($mod, $func) = $mn->[1]->[2] =~ /::/ ?
79 split(/::/, $mn->[1]->[2]) :
80 $mn->[1]->[2] =~ /\./ ?
81 split(/\./, $mn->[1]->[2]) :
82 (undef, $mn->[1]->[2]);
85 my ($params) = &find_xmls("params", $mc);
86 my @params = &find_xmls("param", $params);
88 foreach my $p (@params) {
89 my ($value) = &find_xmls("value", $p, 1);
90 my $perlv = &parse_xml_value($value);
94 # Require the module, if needed
96 if (!$done_require_module{$mod}) {
97 if (!&foreign_check($mod)) {
99 "Webmin module $mod does not exist");
101 my %minfo = &get_module_info($mod);
102 my @libs = split(/\s+/, $minfo{'library'});
104 push(@libs, "$mod-lib.pl");
106 foreach my $lib (@libs) {
107 eval { &foreign_require($mod, $lib); };
109 $xmlrv .= &make_error_xml(6,
110 "Failed to load library ".
120 if ($func eq "eval") {
121 # Execute some Perl code
122 @rv = eval "$args[0]";
124 $xmlrv .= &make_error_xml(8, "Eval failed : $@");
128 # A real function call
129 eval { @rv = &foreign_call($mod, $func, @args); };
131 $xmlrv .= &make_error_xml(7,
132 "Function call $func failed : $@");
138 $xmlrv .= "<methodResponse>\n";
139 $xmlrv .= "<params>\n";
140 $xmlrv .= "<param><value>\n";
142 $xmlrv .= &encode_xml_value($rv[0]);
145 $xmlrv .= &encode_xml_value(\@rv);
147 $xmlrv .= "</value></param>\n";
148 $xmlrv .= "</params>\n";
149 $xmlrv .= "</methodResponse>\n";
152 # Flush all modified files, as some APIs require a call to this function
155 # Return results to caller
156 if (!$command_line) {
157 print "Content-type: text/xml\n";
158 print "Content-length: ",length($xmlrv),"\n";
163 # parse_xml_value(&value)
164 # Given a <value> object, returns a Perl scalar, hash ref or array ref for
169 my ($scalar) = &find_xmls([ "int", "i4", "boolean", "string", "double" ],
171 my ($date) = &find_xmls([ "dateTime.iso8601" ], $value, 1);
172 my ($base64) = &find_xmls("base64", $value, 1);
173 my ($struct) = &find_xmls("struct", $value, 1);
174 my ($array) = &find_xmls("array", $value, 1);
176 return $scalar->[1]->[2];
179 # Need to decode date
184 return &decode_base64($base64->[1]->[2]);
187 # Parse member names and values
189 foreach my $member (&find_xmls("member", $struct, 1)) {
190 my ($name) = &find_xmls("name", $member, 1);
191 my ($value) = &find_xmls("value", $member, 1);
192 my $perlv = &parse_xml_value($value);
193 $rv{$name->[1]->[2]} = $perlv;
200 my ($data) = &find_xmls("data", $array, 1);
201 foreach my $value (&find_xmls("value", $data, 1)) {
202 my $perlv = &parse_xml_value($value);
208 # Fallback - just a string directly in the value
209 return $value->[1]->[2];
213 # encode_xml_value(string|int|&hash|&array)
214 # Given a Perl object, returns XML lines representing it for return to a caller
218 if (ref($perlv) eq "ARRAY") {
219 # Convert to array XML format
220 my $xmlrv = "<array>\n<data>\n";
221 foreach my $v (@$perlv) {
222 $xmlrv .= "<value>\n";
223 $xmlrv .= &encode_xml_value($v);
224 $xmlrv .= "</value>\n";
226 $xmlrv .= "</data>\n</array>\n";
229 elsif (ref($perlv) eq "HASH") {
230 # Convert to struct XML format
231 my $xmlrv = "<struct>\n";
232 foreach my $k (keys %$perlv) {
233 $xmlrv .= "<member>\n";
234 $xmlrv .= "<name>".&html_escape($k)."</name>\n";
235 $xmlrv .= "<value>\n";
236 $xmlrv .= &encode_xml_value($perlv->{$k});
237 $xmlrv .= "</value>\n";
238 $xmlrv .= "</member>\n";
240 $xmlrv .= "</struct>\n";
243 elsif ($perlv =~ /^\-?\d+$/) {
245 return "<int>$perlv</int>\n";
247 elsif ($perlv =~ /^\-?\d*\.\d+$/) {
249 return "<double>$perlv</double>\n";
251 elsif ($perlv =~ /^[\40-\377]*$/) {
253 return "<string>".&html_escape($perlv)."</string>\n";
256 # Contains non-printable characters, so return as base64
257 return "<base64>".&encode_base64($perlv)."</base64>\n";
261 # find_xmls(name|&names, &config, [depth])
262 # Returns the XMLs object with some name, by recursively searching the XML
265 local ($name, $conf, $depth) = @_;
266 local @m = ref($name) ? @$name : ( $name );
267 if (&indexoflc($conf->[0], @m) >= 0) {
272 # Need to recursively scan all sub-elements, except for the first
273 # which is just the tags of this element
274 if (defined($depth) && !$depth) {
275 # Gone too far .. stop
279 local $list = $conf->[1];
281 for($i=1; $i<@$list; $i+=2) {
282 local @srv = &find_xmls($name,
283 [ $list->[$i], $list->[$i+1] ],
284 defined($depth) ? $depth-1 : undef);
292 # error_exit(code, message)
293 # Output an XML error message
296 my ($code, $msg) = @_;
300 # Construct error XML
301 my $xmlerr = "<?xml version=\"1.0\"?>\n";
302 $xmlerr .= &make_error_xml($code, $msg);
305 if (!$command_line) {
306 print "Content-type: text/xml\n";
307 print "Content-length: ",length($xmlerr),"\n";
311 exit($command_line ? $code : 0);
316 my ($code, $msg) = @_;
317 $xmlerr .= "<methodResponse>\n";
318 $xmlerr .= "<fault>\n";
319 $xmlerr .= "<value>\n";
320 $xmlerr .= &encode_xml_value( { 'faultCode' => $code,
321 'faultString' => $msg });
322 $xmlerr .= "</value>\n";
323 $xmlerr .= "</fault>\n";
324 $xmlerr .= "</methodResponse>\n";