Code

Updated opsi stuff to gain more speed
[gosa.git] / gosa-si / server / events / opsi_com.pm
1 ## @file
2 # @details A GOsa-SI-server event module containing all functions for message handling.
3 # @brief Implementation of an event module for GOsa-SI-server. 
6 package opsi_com;
7 use Exporter;
8 @ISA = qw(Exporter);
9 my @events = (
10     "get_events",
11     "opsi_install_client",
12     "opsi_get_netboot_products",  
13     "opsi_get_local_products",
14     "opsi_get_client_hardware",
15     "opsi_get_client_software",
16     "opsi_get_product_properties",
17     "opsi_set_product_properties",
18     "opsi_list_clients",
19     "opsi_del_client",
20     "opsi_add_client",
21     "opsi_modify_client",
22     "opsi_add_product_to_client",
23     "opsi_del_product_from_client",
24         "opsi_createLicensePool",
25         "opsi_deleteLicensePool",
26         "opsi_createLicense",
27         "opsi_assignSoftwareLicenseToHost",
28         "opsi_unassignSoftwareLicenseFromHost",
29         "opsi_unassignAllSoftwareLicensesFromHost",
30         "opsi_getSoftwareLicense_hash",
31         "opsi_getLicensePool_hash",
32         "opsi_getSoftwareLicenseUsages",
33         "opsi_getSoftwareLicenseUsagesForProductId",
34         "opsi_getLicensePools_listOfHashes",
35         "opsi_getLicenseInformationForProduct",
36         "opsi_getPool",
37         "opsi_getAllSoftwareLicenses",
38         "opsi_removeLicense",
39         "opsi_getReservedLicenses",
40         "opsi_boundHostToLicense",
41         "opsi_unboundHostFromLicense",
42         "opsi_test",
43    );
44 @EXPORT = @events;
46 use strict;
47 use warnings;
48 use GOSA::GosaSupportDaemon;
49 use Data::Dumper;
50 use XML::Quote qw(:all);
52 BEGIN {}
54 END {}
56 # ----------------------------------------------------------------------------
57 #                          D E C L A R A T I O N S
58 # ----------------------------------------------------------------------------
60 my $licenseTyp_hash = { 'OEM'=>'', 'VOLUME'=>'', 'RETAIL'=>''};
61 my ($opsi_enabled, $opsi_server, $opsi_admin, $opsi_password, $opsi_url, $opsi_client);
62 my %cfg_defaults = (
63                 "Opsi" => {
64                 "enabled"  => [\$opsi_enabled, "false"],
65                 "server"   => [\$opsi_server, "localhost"],
66                 "admin"    => [\$opsi_admin, "opsi-admin"],
67                 "password" => [\$opsi_password, "secret"],
68                 },
69 );
70 &read_configfile($main::cfg_file, %cfg_defaults);
71 if ($opsi_enabled eq "true") {
72         use JSON::RPC::Client;
73         use XML::Quote qw(:all);
74         use Time::HiRes qw( time );
75         $opsi_url= "https://".$opsi_admin.":".$opsi_password."@".$opsi_server.":4447/rpc";
76         $opsi_client = new JSON::RPC::Client;
77 }
79 # ----------------------------------------------------------------------------
80 #   external methods handling the comunication with GOsa/GOsa-si
81 # ----------------------------------------------------------------------------
83 ################################
84 # @brief A function returning a list of functions which are exported by importing the module.
85 # @return List of all provided functions
86 sub get_events {
87     return \@events;
88 }
90 ################################
91 # @brief Adds an Opsi product to an Opsi client.
92 # @param msg - STRING - xml message with tags hostId and productId
93 # @param msg_hash - HASHREF - message information parsed into a hash
94 # @param session_id - INTEGER - POE session id of the processing of this message
95 # @return out_msg - STRING - feedback to GOsa in success and error case
96 sub opsi_add_product_to_client {
97         my $startTime = Time::HiRes::time;
98     my ($msg, $msg_hash, $session_id) = @_;
99     my $header = @{$msg_hash->{'header'}}[0];
100     my $source = @{$msg_hash->{'source'}}[0];
101     my $target = @{$msg_hash->{'target'}}[0];
102     my $forward_to_gosa = @{$msg_hash->{'forward_to_gosa'}}[0];
104     # Build return message
105     my $out_hash = &main::create_xml_hash("answer_$header", $main::server_address, $source);
106     if (defined $forward_to_gosa) {
107         &add_content2xml_hash($out_hash, "forward_to_gosa", $forward_to_gosa);
108     }
110     # Sanity check of needed parameter
111     if ((not exists $msg_hash->{'hostId'}) || (@{$msg_hash->{'hostId'}} != 1) || (@{$msg_hash->{'hostId'}}[0] eq ref 'HASH'))  {
112                 return &_giveErrorFeedback($msg_hash, "no hostId specified or hostId tag invalid", $session_id);
113     }
114     if ((not exists $msg_hash->{'productId'}) || (@{$msg_hash->{'productId'}} != 1) || (@{$msg_hash->{'productId'}}[0] eq ref 'HASH')) {
115                 return &_giveErrorFeedback($msg_hash, "no productId specified or productId tag invalid", $session_id);
116     }
118         # Get hostId
119         my $hostId = @{$msg_hash->{'hostId'}}[0];
120         &add_content2xml_hash($out_hash, "hostId", $hostId);
122         # Get productID
123         my $productId = @{$msg_hash->{'productId'}}[0];
124         &add_content2xml_hash($out_hash, "productId", $productId);
126         # Do an action request for all these -> "setup".
127         my $callobj = {
128                 method  => 'setProductActionRequest',
129                 params  => [ $productId, $hostId, "setup" ],
130                 id  => 1, }; 
131         my $res = $main::opsi_client->call($main::opsi_url, $callobj);
133         if (&check_opsi_res($res)) { return ( (caller(0))[3]." : ".$_, 1 ); };
135         &main::daemon_log("0 DEBUG: time to process gosa-si message '$header' : ".sprintf("%.4f", (Time::HiRes::time - $startTime))." seconds", 1034);
136     return ( &create_xml_string($out_hash) );
139 ################################
140 # @brief Deletes an Opsi-product from an Opsi-client. 
141 # @param msg - STRING - xml message with tags hostId and productId
142 # @param msg_hash - HASHREF - message information parsed into a hash
143 # @param session_id - INTEGER - POE session id of the processing of this message
144 # @return out_msg - STRING - feedback to GOsa in success and error case
145 sub opsi_del_product_from_client {
146         my $startTime = Time::HiRes::time;
147     my ($msg, $msg_hash, $session_id) = @_;
148     my $header = @{$msg_hash->{'header'}}[0];
149     my $source = @{$msg_hash->{'source'}}[0];
150     my $target = @{$msg_hash->{'target'}}[0];
151     my $forward_to_gosa = @{$msg_hash->{'forward_to_gosa'}}[0];
152     my ($hostId, $productId);
153     my $error = 0;
154     my ($sres, $sres_err, $sres_err_string);
156     # Build return message
157     my $out_hash = &main::create_xml_hash("answer_$header", $main::server_address, $source);
158     if (defined $forward_to_gosa) {
159         &add_content2xml_hash($out_hash, "forward_to_gosa", $forward_to_gosa);
160     }
162     # Sanity check of needed parameter
163     if ((not exists $msg_hash->{'hostId'}) || (@{$msg_hash->{'hostId'}} != 1) || (@{$msg_hash->{'hostId'}}[0] eq ref 'HASH'))  {
164         $error++;
165         &add_content2xml_hash($out_hash, "error_string", "no hostId specified or hostId tag invalid");
166         &add_content2xml_hash($out_hash, "error", "hostId");
167         &main::daemon_log("$session_id ERROR: no hostId specified or hostId tag invalid: $msg", 1); 
169     }
170     if ((not exists $msg_hash->{'productId'}) || (@{$msg_hash->{'productId'}} != 1) || (@{$msg_hash->{'productId'}}[0] eq ref 'HASH')) {
171         $error++;
172         &add_content2xml_hash($out_hash, "error_string", "no productId specified or productId tag invalid");
173         &add_content2xml_hash($out_hash, "error", "productId");
174         &main::daemon_log("$session_id ERROR: no productId specified or procutId tag invalid: $msg", 1); 
175     }
177     # All parameter available
178     if (not $error) {
179         # Get hostId
180         $hostId = @{$msg_hash->{'hostId'}}[0];
181         &add_content2xml_hash($out_hash, "hostId", $hostId);
183         # Get productID
184         $productId = @{$msg_hash->{'productId'}}[0];
185         &add_content2xml_hash($out_hash, "productId", $productId);
187         # Check to get product action list 
188         my $callobj = {
189             method  => 'getPossibleProductActions_list',
190             params  => [ $productId ],
191             id  => 1, };
192         $sres = $main::opsi_client->call($main::opsi_url, $callobj);
193         ($sres_err, $sres_err_string) = &check_opsi_res($sres);
194         if ($sres_err){
195             &main::daemon_log("$session_id ERROR: cannot get product action list: ".$sres_err_string, 1);
196             &add_content2xml_hash($out_hash, "error", $sres_err_string);
197             $error++;
198         }
199     }
201     # Check action uninstall of product
202     if (not $error) {
203         my $uninst_possible= 0;
204         foreach my $r (@{$sres->result}) {
205             if ($r eq 'uninstall') {
206                 $uninst_possible= 1;
207             }
208         }
209         if (!$uninst_possible){
210             &main::daemon_log("$session_id ERROR: cannot uninstall product '$productId', product do not has the action 'uninstall'", 1);
211             &add_content2xml_hash($out_hash, "error", "cannot uninstall product '$productId', product do not has the action 'uninstall'");
212             $error++;
213         }
214     }
216     # Set product state to "none"
217     # Do an action request for all these -> "setup".
218     if (not $error) {
219         my $callobj = {
220             method  => 'setProductActionRequest',
221             params  => [ $productId, $hostId, "none" ],
222             id  => 1, 
223         }; 
224         $sres = $main::opsi_client->call($main::opsi_url, $callobj);
225         ($sres_err, $sres_err_string) = &check_opsi_res($sres);
226         if ($sres_err){
227             &main::daemon_log("$session_id ERROR: cannot delete product: ".$sres_err_string, 1);
228             &add_content2xml_hash($out_hash, "error", $sres_err_string);
229         }
230     }
232     # Return message
233         &main::daemon_log("0 DEBUG: time to process gosa-si message '$header' : ".sprintf("%.4f", (Time::HiRes::time - $startTime))." seconds", 1034);
234     return ( &create_xml_string($out_hash) );
237 ################################
238 # @brief Adds an Opsi client to Opsi.
239 # @param msg - STRING - xml message with tags hostId and macaddress
240 # @param msg_hash - HASHREF - message information parsed into a hash
241 # @param session_id - INTEGER - POE session id of the processing of this message
242 # @return out_msg - STRING - feedback to GOsa in success and error case
243 sub opsi_add_client {
244         my $startTime = Time::HiRes::time;
245     my ($msg, $msg_hash, $session_id) = @_;
246     my $header = @{$msg_hash->{'header'}}[0];
247     my $source = @{$msg_hash->{'source'}}[0];
248     my $target = @{$msg_hash->{'target'}}[0];
249     my $forward_to_gosa = @{$msg_hash->{'forward_to_gosa'}}[0];
250     my ($hostId, $mac);
251     my $error = 0;
252     my ($sres, $sres_err, $sres_err_string);
254     # Build return message with twisted target and source
255     my $out_hash = &main::create_xml_hash("answer_$header", $main::server_address, $source);
256     if (defined $forward_to_gosa) {
257         &add_content2xml_hash($out_hash, "forward_to_gosa", $forward_to_gosa);
258     }
260     # Sanity check of needed parameter
261     if ((not exists $msg_hash->{'hostId'}) || (@{$msg_hash->{'hostId'}} != 1) || (@{$msg_hash->{'hostId'}}[0] eq ref 'HASH'))  {
262         $error++;
263         &add_content2xml_hash($out_hash, "error_string", "no hostId specified or hostId tag invalid");
264         &add_content2xml_hash($out_hash, "error", "hostId");
265         &main::daemon_log("$session_id ERROR: no hostId specified or hostId tag invalid: $msg", 1); 
266     }
267     if ((not exists $msg_hash->{'macaddress'}) || (@{$msg_hash->{'macaddress'}} != 1) || (@{$msg_hash->{'macaddress'}}[0] eq ref 'HASH'))  {
268         $error++;
269         &add_content2xml_hash($out_hash, "error_string", "no macaddress specified or macaddress tag invalid");
270         &add_content2xml_hash($out_hash, "error", "macaddress");
271         &main::daemon_log("$session_id ERROR: no macaddress specified or macaddress tag invalid: $msg", 1); 
272     }
274     if (not $error) {
275         # Get hostId
276         $hostId = @{$msg_hash->{'hostId'}}[0];
277         &add_content2xml_hash($out_hash, "hostId", $hostId);
279         # Get macaddress
280         $mac = @{$msg_hash->{'macaddress'}}[0];
281         &add_content2xml_hash($out_hash, "macaddress", $mac);
283         my $name= $hostId;
284         $name=~ s/^([^.]+).*$/$1/;
285         my $domain= $hostId;
286         $domain=~ s/^[^.]+\.(.*)$/$1/;
287         my ($description, $notes, $ip);
289         if (defined @{$msg_hash->{'description'}}[0]){
290             $description = @{$msg_hash->{'description'}}[0];
291         }
292         if (defined @{$msg_hash->{'notes'}}[0]){
293             $notes = @{$msg_hash->{'notes'}}[0];
294         }
295         if (defined @{$msg_hash->{'ip'}}[0]){
296             $ip = @{$msg_hash->{'ip'}}[0];
297         }
299         my $callobj;
300         $callobj = {
301             method  => 'createClient',
302             params  => [ $name, $domain, $description, $notes, $ip, $mac ],
303             id  => 1,
304         };
306         $sres = $main::opsi_client->call($main::opsi_url, $callobj);
307         ($sres_err, $sres_err_string) = &check_opsi_res($sres);
308         if ($sres_err){
309             &main::daemon_log("$session_id ERROR: cannot create client: ".$sres_err_string, 1);
310             &add_content2xml_hash($out_hash, "error", $sres_err_string);
311         } else {
312             &main::daemon_log("$session_id INFO: add opsi client '$hostId' with mac '$mac'", 5); 
313         }
314     }
316     # Return message
317         &main::daemon_log("0 DEBUG: time to process gosa-si message '$header' : ".sprintf("%.4f", (Time::HiRes::time - $startTime))." seconds", 1034);
318     return ( &create_xml_string($out_hash) );
321 ################################
322 # @brief Modifies the parameters description, mac or notes for an Opsi client if the corresponding message tags are given.
323 # @param msg - STRING - xml message with tag hostId and optional description, mac or notes
324 # @param msg_hash - HASHREF - message information parsed into a hash
325 # @param session_id - INTEGER - POE session id of the processing of this message    
326 # @return out_msg - STRING - feedback to GOsa in success and error case
327 sub opsi_modify_client {
328         my $startTime = Time::HiRes::time;
329     my ($msg, $msg_hash, $session_id) = @_;
330     my $header = @{$msg_hash->{'header'}}[0];
331     my $source = @{$msg_hash->{'source'}}[0];
332     my $target = @{$msg_hash->{'target'}}[0];
333     my $forward_to_gosa = @{$msg_hash->{'forward_to_gosa'}}[0];
334     my $hostId;
335     my $error = 0;
336     my ($sres, $sres_err, $sres_err_string);
338     # Build return message with twisted target and source
339     my $out_hash = &main::create_xml_hash("answer_$header", $main::server_address, $source);
340     if (defined $forward_to_gosa) {
341         &add_content2xml_hash($out_hash, "forward_to_gosa", $forward_to_gosa);
342     }
344     # Sanity check of needed parameter
345     if ((not exists $msg_hash->{'hostId'}) || (@{$msg_hash->{'hostId'}} != 1) || (@{$msg_hash->{'hostId'}}[0] eq ref 'HASH'))  {
346         $error++;
347         &add_content2xml_hash($out_hash, "error_string", "no hostId specified or hostId tag invalid");
348         &add_content2xml_hash($out_hash, "error", "hostId");
349         &main::daemon_log("$session_id ERROR: no hostId specified or hostId tag invalid: $msg", 1); 
350     }
352     if (not $error) {
353         # Get hostId
354         $hostId = @{$msg_hash->{'hostId'}}[0];
355         &add_content2xml_hash($out_hash, "hostId", $hostId);
356         my $name= $hostId;
357         $name=~ s/^([^.]+).*$/$1/;
358         my $domain= $hostId;
359         $domain=~ s/^[^.]+(.*)$/$1/;
361         # Modify description, notes or mac if defined
362         my ($description, $notes, $mac);
363         my $callobj;
364         if ((exists $msg_hash->{'description'}) && (@{$msg_hash->{'description'}} == 1) ){
365             $description = @{$msg_hash->{'description'}}[0];
366             $callobj = {
367                 method  => 'setHostDescription',
368                 params  => [ $hostId, $description ],
369                 id  => 1,
370             };
371             my $sres = $main::opsi_client->call($main::opsi_url, $callobj);
372             my ($sres_err, $sres_err_string) = &check_opsi_res($sres);
373             if ($sres_err){
374                 &main::daemon_log("ERROR: cannot set description: ".$sres_err_string, 1);
375                 &add_content2xml_hash($out_hash, "error", $sres_err_string);
376             }
377         }
378         if ((exists $msg_hash->{'notes'}) && (@{$msg_hash->{'notes'}} == 1)) {
379             $notes = @{$msg_hash->{'notes'}}[0];
380             $callobj = {
381                 method  => 'setHostNotes',
382                 params  => [ $hostId, $notes ],
383                 id  => 1,
384             };
385             my $sres = $main::opsi_client->call($main::opsi_url, $callobj);
386             my ($sres_err, $sres_err_string) = &check_opsi_res($sres);
387             if ($sres_err){
388                 &main::daemon_log("ERROR: cannot set notes: ".$sres_err_string, 1);
389                 &add_content2xml_hash($out_hash, "error", $sres_err_string);
390             }
391         }
392         if ((exists $msg_hash->{'mac'}) && (@{$msg_hash->{'mac'}} == 1)){
393             $mac = @{$msg_hash->{'mac'}}[0];
394             $callobj = {
395                 method  => 'setMacAddress',
396                 params  => [ $hostId, $mac ],
397                 id  => 1,
398             };
399             my $sres = $main::opsi_client->call($main::opsi_url, $callobj);
400             my ($sres_err, $sres_err_string) = &check_opsi_res($sres);
401             if ($sres_err){
402                 &main::daemon_log("ERROR: cannot set mac address: ".$sres_err_string, 1);
403                 &add_content2xml_hash($out_hash, "error", $sres_err_string);
404             }
405         }
406     }
408     # Return message
409         &main::daemon_log("0 DEBUG: time to process gosa-si message '$header' : ".sprintf("%.4f", (Time::HiRes::time - $startTime))." seconds", 1034);
410     return ( &create_xml_string($out_hash) );
412  
413 ################################
414 # @brief Get netboot products for specific host.
415 # @param msg - STRING - xml message with tag hostId
416 # @param msg_hash - HASHREF - message information parsed into a hash
417 # @param session_id - INTEGER - POE session id of the processing of this message
418 # @return out_msg - STRING - feedback to GOsa in success and error case
419 sub opsi_get_netboot_products {
420     my $startTime = Time::HiRes::time;
421     my ($msg, $msg_hash, $session_id) = @_;
422     my $header = @{$msg_hash->{'header'}}[0];
423     my $source = @{$msg_hash->{'source'}}[0];
424     my $target = @{$msg_hash->{'target'}}[0];
425     my $forward_to_gosa = @{$msg_hash->{'forward_to_gosa'}}[0];
426     my $hostId;
428     # Build return message with twisted target and source
429     my $out_hash = &main::create_xml_hash("answer_$header", $main::server_address, $source);
430     if (defined $forward_to_gosa) {
431         &add_content2xml_hash($out_hash, "forward_to_gosa", $forward_to_gosa);
432     }
433     &add_content2xml_hash($out_hash, "xxx", "");
435     # Get hostId if defined
436     if ((exists $msg_hash->{'hostId'}) && (@{$msg_hash->{'hostId'}} == 1))  {
437         $hostId = @{$msg_hash->{'hostId'}}[0];
438         &add_content2xml_hash($out_hash, "hostId", $hostId);
439     }
441     # Move to XML string
442     my $xml_msg= &create_xml_string($out_hash);
444     my $callobj;
445     # Check if we need to get host or global information
446     if (defined $hostId){
447       $callobj = {
448           method  => 'getProductHostInformation_list',
449           params  => [ $hostId, undef, 'netboot'],
450           id  => 1,
451       };
453       my $res = $main::opsi_client->call($main::opsi_url, $callobj);
454       if (not &check_opsi_res($res)){
455           foreach my $product (@{$res->result}){
456                my $replace= "<item><productId>".xml_quote($product->{'productId'})."<\/productId><name>".xml_quote($product->{'name'})."<\/name><description>".xml_quote($product->{'description'})."<\/description><state>".xml_quote($product->{'installationStatus'})."</state><action>".xml_quote($product->{'actionRequest'})."</action><\/item><xxx><\/xxx>";
457                $xml_msg=~ s/<xxx><\/xxx>/\n$replace/;
458           }
459       }
461     } else {
463       # For hosts, only return the products that are or get installed
464       $callobj = {
465           method  => 'getProductInformation_list',
466           params  => [ undef, 'netboot' ],
467           id  => 1,
468       };
470       my $res = $main::opsi_client->call($main::opsi_url, $callobj);
471       if (not &check_opsi_res($res)){
472           foreach my $product (@{$res->result}) {
473                my $replace= "<item><productId>".xml_quote($product->{'productId'})."<\/productId><name>".xml_quote($product->{'name'})."<\/name><description>".xml_quote($product->{'description'})."<\/description><\/item><xxx><\/xxx>";
474                $xml_msg=~ s/<xxx><\/xxx>/\n$replace/;
475           }
476       }
477     }
479     $xml_msg=~ s/<xxx><\/xxx>//;
481     # Retrun Message
482         &main::daemon_log("0 DEBUG: time to process gosa-si message '$header' : ".sprintf("%.4f", (Time::HiRes::time - $startTime))." seconds", 1034);
483     return ( $xml_msg );
486 ################################   
487 # @brief Get product properties for a product and a specific host or gobally for a product.
488 # @param msg - STRING - xml message with tags productId and optional hostId
489 # @param msg_hash - HASHREF - message information parsed into a hash
490 # @param session_id - INTEGER - POE session id of the processing of this message
491 # @return out_msg - STRING - feedback to GOsa in success and error case
492 sub opsi_get_product_properties {
493         my $startTime = Time::HiRes::time;
494     my ($msg, $msg_hash, $session_id) = @_;
495     my $header = @{$msg_hash->{'header'}}[0];
496     my $source = @{$msg_hash->{'source'}}[0];
497     my $target = @{$msg_hash->{'target'}}[0];
498     my $forward_to_gosa = @{$msg_hash->{'forward_to_gosa'}}[0];
499     my ($hostId, $productId);
500     my $xml_msg;
502     # Build return message with twisted target and source
503     my $out_hash = &main::create_xml_hash("answer_$header", $main::server_address, $source);
504     if (defined $forward_to_gosa) {
505         &add_content2xml_hash($out_hash, "forward_to_gosa", $forward_to_gosa);
506     }
508     # Sanity check of needed parameter
509     if ((not exists $msg_hash->{'productId'}) || (@{$msg_hash->{'productId'}} != 1) || (@{$msg_hash->{'productId'}}[0] eq ref 'HASH'))  {
510         &add_content2xml_hash($out_hash, "error_string", "no productId specified or productId tag invalid");
511         &add_content2xml_hash($out_hash, "error", "productId");
512         &main::daemon_log("$session_id ERROR: no productId specified or productId tag invalid: $msg", 1); 
514         # Return message
515         return ( &create_xml_string($out_hash) );
516     }
518     # Get productid
519     $productId = @{$msg_hash->{'productId'}}[0];
520     &add_content2xml_hash($out_hash, "producId", "$productId");
522     # Get hostId if defined
523     if (defined @{$msg_hash->{'hostId'}}[0]){
524       $hostId = @{$msg_hash->{'hostId'}}[0];
525       &add_content2xml_hash($out_hash, "hostId", $hostId);
526     }
528     # Load actions
529     my $callobj = {
530       method  => 'getPossibleProductActions_list',
531       params  => [ $productId ],
532       id  => 1,
533     };
534     my $res = $main::opsi_client->call($main::opsi_url, $callobj);
535     if (not &check_opsi_res($res)){
536       foreach my $action (@{$res->result}){
537         &add_content2xml_hash($out_hash, "action", $action);
538       }
539     }
541     # Add place holder
542     &add_content2xml_hash($out_hash, "xxx", "");
544     # Move to XML string
545     $xml_msg= &create_xml_string($out_hash);
547     # JSON Query
548     if (defined $hostId){
549       $callobj = {
550           method  => 'getProductProperties_hash',
551           params  => [ $productId, $hostId ],
552           id  => 1,
553       };
554     } else {
555       $callobj = {
556           method  => 'getProductProperties_hash',
557           params  => [ $productId ],
558           id  => 1,
559       };
560     }
561     $res = $main::opsi_client->call($main::opsi_url, $callobj);
563     # JSON Query 2
564     $callobj = {
565       method  => 'getProductPropertyDefinitions_listOfHashes',
566       params  => [ $productId ],
567       id  => 1,
568     };
570     # Assemble options
571     my $res2 = $main::opsi_client->call($main::opsi_url, $callobj);
572     my $values = {};
573     my $descriptions = {};
574     if (not &check_opsi_res($res2)){
575         my $r= $res2->result;
577           foreach my $entr (@$r){
578             # Unroll values
579             my $cnv;
580             if (UNIVERSAL::isa( $entr->{'values'}, "ARRAY" )){
581               foreach my $v (@{$entr->{'values'}}){
582                 $cnv.= "<value>$v</value>";
583               }
584             } else {
585               $cnv= $entr->{'values'};
586             }
587             $values->{$entr->{'name'}}= $cnv;
588             $descriptions->{$entr->{'name'}}= "<description>".$entr->{'description'}."</description>";
589           }
590     }
592     if (not &check_opsi_res($res)){
593         my $r= $res->result;
594         foreach my $key (keys %{$r}) {
595             my $item= "\n<item>";
596             my $value= $r->{$key};
597             my $dsc= "";
598             my $vals= "";
599             if (defined $descriptions->{$key}){
600               $dsc= $descriptions->{$key};
601             }
602             if (defined $values->{$key}){
603               $vals= $values->{$key};
604             }
605             $item.= "<$key>$dsc<default>".xml_quote($value)."</default>$vals</$key>";
606             $item.= "</item>";
607             $xml_msg=~ s/<xxx><\/xxx>/$item<xxx><\/xxx>/;
608         }
609     }
611     $xml_msg=~ s/<xxx><\/xxx>//;
613     # Return message
614         &main::daemon_log("0 DEBUG: time to process gosa-si message '$header' : ".sprintf("%.4f", (Time::HiRes::time - $startTime))." seconds", 1034);
615     return ( $xml_msg );
618 ################################   
619 # @brief Set product properities for a specific host or globaly. Message needs one xml tag 'item' and within one xml tag 'name' and 'value'. The xml tags action and state are optional.
620 # @param msg - STRING - xml message with tags productId, action, state and optional hostId, action and state
621 # @param msg_hash - HASHREF - message information parsed into a hash
622 # @param session_id - INTEGER - POE session id of the processing of this message
623 # @return out_msg - STRING - feedback to GOsa in success and error case
624 sub opsi_set_product_properties {
625         my $startTime = Time::HiRes::time;
626     my ($msg, $msg_hash, $session_id) = @_;
627     my $header = @{$msg_hash->{'header'}}[0];
628     my $source = @{$msg_hash->{'source'}}[0];
629     my $target = @{$msg_hash->{'target'}}[0];
630     my $forward_to_gosa = @{$msg_hash->{'forward_to_gosa'}}[0];
631     my ($productId, $hostId);
633     # Build return message with twisted target and source
634     my $out_hash = &main::create_xml_hash("answer_$header", $main::server_address, $source);
635     if (defined $forward_to_gosa) {
636         &add_content2xml_hash($out_hash, "forward_to_gosa", $forward_to_gosa);
637     }
639     # Sanity check of needed parameter
640     if ((not exists $msg_hash->{'productId'}) || (@{$msg_hash->{'productId'}} != 1) || (@{$msg_hash->{'productId'}}[0] eq ref 'HASH'))  {
641         &add_content2xml_hash($out_hash, "error_string", "no productId specified or productId tag invalid");
642         &add_content2xml_hash($out_hash, "error", "productId");
643         &main::daemon_log("$session_id ERROR: no productId specified or productId tag invalid: $msg", 1); 
644         return ( &create_xml_string($out_hash) );
645     }
646     if (not exists $msg_hash->{'item'}) {
647         &add_content2xml_hash($out_hash, "error_string", "message needs one xml-tag 'item' and within the xml-tags 'name' and 'value'");
648         &add_content2xml_hash($out_hash, "error", "item");
649         &main::daemon_log("$session_id ERROR: message needs one xml-tag 'item' and within the xml-tags 'name' and 'value': $msg", 1); 
650         return ( &create_xml_string($out_hash) );
651     } else {
652         if ((not exists @{$msg_hash->{'item'}}[0]->{'name'}) || (@{@{$msg_hash->{'item'}}[0]->{'name'}} != 1 )) {
653             &add_content2xml_hash($out_hash, "error_string", "message needs within the xml-tag 'item' one xml-tags 'name'");
654             &add_content2xml_hash($out_hash, "error", "name");
655             &main::daemon_log("$session_id ERROR: message needs within the xml-tag 'item' one xml-tags 'name': $msg", 1); 
656             return ( &create_xml_string($out_hash) );
657         }
658         if ((not exists @{$msg_hash->{'item'}}[0]->{'value'}) || (@{@{$msg_hash->{'item'}}[0]->{'value'}} != 1 )) {
659             &add_content2xml_hash($out_hash, "error_string", "message needs within the xml-tag 'item' one xml-tags 'value'");
660             &add_content2xml_hash($out_hash, "error", "value");
661             &main::daemon_log("$session_id ERROR: message needs within the xml-tag 'item' one xml-tags 'value': $msg", 1); 
662             return ( &create_xml_string($out_hash) );
663         }
664     }
665     # if no hostId is given, set_product_properties will act on globally
666     if ((exists $msg_hash->{'hostId'}) && (@{$msg_hash->{'hostId'}} > 1))  {
667         &add_content2xml_hash($out_hash, "error_string", "hostId contains no or more than one values");
668         &add_content2xml_hash($out_hash, "error", "hostId");
669         &main::daemon_log("$session_id ERROR: hostId contains no or more than one values: $msg", 1); 
670         return ( &create_xml_string($out_hash) );
671     }
673         
674     # Get productId
675     $productId =  @{$msg_hash->{'productId'}}[0];
676     &add_content2xml_hash($out_hash, "productId", $productId);
678     # Get hostId if defined
679     if (exists $msg_hash->{'hostId'}){
680         $hostId = @{$msg_hash->{'hostId'}}[0];
681         &add_content2xml_hash($out_hash, "hostId", $hostId);
682     }
684     # Set product states if requested
685     if (defined @{$msg_hash->{'action'}}[0]){
686         &_set_action($productId, @{$msg_hash->{'action'}}[0], $hostId);
687     }
688     if (defined @{$msg_hash->{'state'}}[0]){
689         &_set_state($productId, @{$msg_hash->{'state'}}[0], $hostId);
690     }
692     # Find properties
693     foreach my $item (@{$msg_hash->{'item'}}){
694         # JSON Query
695         my $callobj;
697         if (defined $hostId){
698             $callobj = {
699                 method  => 'setProductProperty',
700                 params  => [ $productId, $item->{'name'}[0], $item->{'value'}[0], $hostId ],
701                 id  => 1,
702             };
703         } else {
704             $callobj = {
705                 method  => 'setProductProperty',
706                 params  => [ $productId, $item->{'name'}[0], $item->{'value'}[0] ],
707                 id  => 1,
708             };
709         }
711         my $res = $main::opsi_client->call($main::opsi_url, $callobj);
712         my ($res_err, $res_err_string) = &check_opsi_res($res);
714         if ($res_err){
715             &main::daemon_log("$session_id ERROR: communication failed while setting '".$item->{'name'}[0]."': ".$res_err_string, 1);
716             &add_content2xml_hash($out_hash, "error", $res_err_string);
717         }
718     }
721     # Return message
722         &main::daemon_log("0 DEBUG: time to process gosa-si message '$header' : ".sprintf("%.4f", (Time::HiRes::time - $startTime))." seconds", 1034);
723     return ( &create_xml_string($out_hash) );
726 ################################   
727 # @brief Reports client hardware inventory.
728 # @param msg - STRING - xml message with tag hostId
729 # @param msg_hash - HASHREF - message information parsed into a hash
730 # @param session_id - INTEGER - POE session id of the processing of this message
731 # @return out_msg - STRING - feedback to GOsa in success and error case
732 sub opsi_get_client_hardware {
733         my $startTime = Time::HiRes::time;
734     my ($msg, $msg_hash, $session_id) = @_;
735     my $header = @{$msg_hash->{'header'}}[0];
736     my $source = @{$msg_hash->{'source'}}[0];
737     my $target = @{$msg_hash->{'target'}}[0];
738     my $forward_to_gosa = @{$msg_hash->{'forward_to_gosa'}}[0];
739     my $hostId;
740     my $error = 0;
741     my $xml_msg;
743     # Sanity check of needed parameter
744         if (&_check_xml_tag_is_ok ($msg_hash, 'hostId')) {
745         $hostId = @{$msg_hash->{'hostId'}}[0];
746         } else {
747                 return &_giveErrorFeedback($msg_hash, $_, $session_id);
748         }
751     # Build return message with twisted target and source
752     my $out_hash = &main::create_xml_hash("answer_$header", $main::server_address, $source);
753     if (defined $forward_to_gosa) {
754       &add_content2xml_hash($out_hash, "forward_to_gosa", $forward_to_gosa);
755     }
756         &add_content2xml_hash($out_hash, "hostId", "$hostId");
757         &add_content2xml_hash($out_hash, "xxx", "");
759     # Move to XML string
760     $xml_msg= &create_xml_string($out_hash);
761     
762         my $res = &_callOpsi(method=>'getHardwareInformation_hash', params=>[ $hostId ]);
763         if (not &check_opsi_res($res)){
764                 my $result= $res->result;
765                 if (ref $result eq "HASH") {
766                         foreach my $r (keys %{$result}){
767                                 my $item= "\n<item><id>".xml_quote($r)."</id>";
768                                 my $value= $result->{$r};
769                                 foreach my $sres (@{$value}){
771                                         foreach my $dres (keys %{$sres}){
772                                                 if (defined $sres->{$dres}){
773                                                         $item.= "<$dres>".xml_quote($sres->{$dres})."</$dres>";
774                                                 }
775                                         }
777                                 }
778                                 $item.= "</item>";
779                                 $xml_msg=~ s%<xxx></xxx>%$item<xxx></xxx>%;
781                         }
782                 }
783         }
785         $xml_msg=~ s/<xxx><\/xxx>//;
787     # Return message
788         my $endTime = Time::HiRes::time;
789         my $elapsedTime = sprintf("%.4f", ($endTime - $startTime));
790         &main::daemon_log("0 DEBUG: time to process gosa-si message '$header' : $elapsedTime seconds", 1034);
791     return ( $xml_msg );
794 ################################   
795 # @brief Reports all Opsi clients. 
796 # @param msg - STRING - xml message 
797 # @param msg_hash - HASHREF - message information parsed into a hash
798 # @param session_id - INTEGER - POE session id of the processing of this message
799 # @return out_msg - STRING - feedback to GOsa in success and error case
800 sub opsi_list_clients {
801         my $startTime = Time::HiRes::time;
802     my ($msg, $msg_hash, $session_id) = @_;
803     my $header = @{$msg_hash->{'header'}}[0];
804     my $source = @{$msg_hash->{'source'}}[0];
805     my $target = @{$msg_hash->{'target'}}[0];
806     my $forward_to_gosa = @{$msg_hash->{'forward_to_gosa'}}[0];
808     # Build return message with twisted target and source
809     my $out_hash = &main::create_xml_hash("answer_$header", $main::server_address, $source);
810     if (defined $forward_to_gosa) {
811       &add_content2xml_hash($out_hash, "forward_to_gosa", $forward_to_gosa);
812     }
813     &add_content2xml_hash($out_hash, "xxx", "");
815     # Move to XML string
816     my $xml_msg= &create_xml_string($out_hash);
818     # JSON Query
819     my $callobj = {
820         method  => 'getClientsInformation_listOfHashes',
821         params  => [ ],
822         id  => 1,
823     };
825     my $res = $main::opsi_client->call($main::opsi_url, $callobj);
826     if (not &check_opsi_res($res)){
827         foreach my $host (@{$res->result}){
828             my $item= "\n<item><name>".$host->{'hostId'}."</name>";
829             $item.= "<mac>".xml_quote($host->{'macAddress'})."</mac>";
830             if (defined($host->{'description'})){
831                 $item.= "<description>".xml_quote($host->{'description'})."</description>";
832             }
833             if (defined($host->{'notes'})){
834                 $item.= "<notes>".xml_quote($host->{'notes'})."</notes>";
835             }
836             if (defined($host->{'lastSeen'})){
837                 $item.= "<lastSeen>".xml_quote($host->{'lastSeen'})."</lastSeen>";
838             }
840             $item.= "</item>";
841             $xml_msg=~ s%<xxx></xxx>%$item<xxx></xxx>%;
842         }
843     }
844     $xml_msg=~ s/<xxx><\/xxx>//;
846         &main::daemon_log("0 DEBUG: time to process gosa-si message '$header' : ".sprintf("%.4f", (Time::HiRes::time - $startTime))." seconds", 1034);
847     return ( $xml_msg );
850 ################################   
851 # @brief Reports client software inventory.
852 # @param msg - STRING - xml message with tag hostId
853 # @param msg_hash - HASHREF - message information parsed into a hash
854 # @param session_id - INTEGER - POE session id of the processing of this message
855 # @return out_msg - STRING - feedback to GOsa in success and error case
856 sub opsi_get_client_software {
857         my $startTime = Time::HiRes::time;
858     my ($msg, $msg_hash, $session_id) = @_;
859     my $header = @{$msg_hash->{'header'}}[0];
860     my $source = @{$msg_hash->{'source'}}[0];
861     my $target = @{$msg_hash->{'target'}}[0];
862     my $forward_to_gosa = @{$msg_hash->{'forward_to_gosa'}}[0];
863     my $error = 0;
864     my $hostId;
865     my $xml_msg;
867     # Build return message with twisted target and source
868     my $out_hash = &main::create_xml_hash("answer_$header", $main::server_address, $source);
869     if (defined $forward_to_gosa) {
870       &add_content2xml_hash($out_hash, "forward_to_gosa", $forward_to_gosa);
871     }
873     # Sanity check of needed parameter
874     if ((exists $msg_hash->{'hostId'}) && (@{$msg_hash->{'hostId'}} != 1) || (@{$msg_hash->{'hostId'}}[0] eq ref 'HASH'))  {
875         $error++;
876         &add_content2xml_hash($out_hash, "error_string", "hostId contains no or more than one values");
877         &add_content2xml_hash($out_hash, "error", "hostId");
878         &main::daemon_log("$session_id ERROR: hostId contains no or more than one values: $msg", 1); 
879     }
881     if (not $error) {
883     # Get hostId
884         $hostId = @{$msg_hash->{'hostId'}}[0];
885         &add_content2xml_hash($out_hash, "hostId", "$hostId");
886         &add_content2xml_hash($out_hash, "xxx", "");
887     }
889     $xml_msg= &create_xml_string($out_hash);
891     if (not $error) {
893     # JSON Query
894         my $callobj = {
895             method  => 'getSoftwareInformation_hash',
896             params  => [ $hostId ],
897             id  => 1,
898         };
900         my $res = $main::opsi_client->call($main::opsi_url, $callobj);
901         if (not &check_opsi_res($res)){
902             my $result= $res->result;
903         }
905         $xml_msg=~ s/<xxx><\/xxx>//;
907     }
909     # Return message
910         &main::daemon_log("0 DEBUG: time to process gosa-si message '$header' : ".sprintf("%.4f", (Time::HiRes::time - $startTime))." seconds", 1034);
911     return ( $xml_msg );
914 ################################   
915 # @brief Reports product for given hostId or globally.
916 # @param msg - STRING - xml message with optional tag hostId
917 # @param msg_hash - HASHREF - message information parsed into a hash
918 # @param session_id - INTEGER - POE session id of the processing of this message
919 # @return out_msg - STRING - feedback to GOsa in success and error case
920 sub opsi_get_local_products {
921     my $startTime = Time::HiRes::time;
922     my ($msg, $msg_hash, $session_id) = @_;
923     my $header = @{$msg_hash->{'header'}}[0];
924     my $source = @{$msg_hash->{'source'}}[0];
925     my $target = @{$msg_hash->{'target'}}[0];
926     my $forward_to_gosa = @{$msg_hash->{'forward_to_gosa'}}[0];
927     my $hostId;
929     # Build return message with twisted target and source
930     my $out_hash = &main::create_xml_hash("answer_$header", $main::server_address, $source);
931     if (defined $forward_to_gosa) {
932         &add_content2xml_hash($out_hash, "forward_to_gosa", $forward_to_gosa);
933     }
934     &add_content2xml_hash($out_hash, "xxx", "");
936     # Get hostId if defined
937     if ((exists $msg_hash->{'hostId'}) && (@{$msg_hash->{'hostId'}} == 1))  {
938         $hostId = @{$msg_hash->{'hostId'}}[0];
939         &add_content2xml_hash($out_hash, "hostId", $hostId);
940     }
942     # Move to XML string
943     my $xml_msg= &create_xml_string($out_hash);
945     my $callobj;
946     # Check if we need to get host or global information
947     if (defined $hostId){
948       $callobj = {
949           method  => 'getProductHostInformation_list',
950           params  => [ $hostId ],
951           id  => 1,
952       };
954       my $res = $main::opsi_client->call($main::opsi_url, $callobj);
955       if (not &check_opsi_res($res)){
956           foreach my $product (@{$res->result}){
957                my $replace= "<item><productId>".xml_quote($product->{'productId'})."<\/productId><name>".xml_quote($product->{'name'})."<\/name><description>".xml_quote($product->{'description'})."<\/description><state>".xml_quote($product->{'installationStatus'})."</state><action>".xml_quote($product->{'actionRequest'})."</action><\/item><xxx><\/xxx>";
958                $xml_msg=~ s/<xxx><\/xxx>/\n$replace/;
959           }
960       }
962     } else {
964       # For hosts, only return the products that are or get installed
965       $callobj = {
966           method  => 'getLocalBootProductInformation_list',
967           params  => [ ],
968           id  => 1,
969       };
971       my $res = $main::opsi_client->call($main::opsi_url, $callobj);
972       if (not &check_opsi_res($res)){
973           foreach my $product (@{$res->result}) {
974                my $replace= "<item><productId>".xml_quote($product->{'productId'})."<\/productId><name>".xml_quote($product->{'name'})."<\/name><description>".xml_quote($product->{'description'})."<\/description><\/item><xxx><\/xxx>";
975                $xml_msg=~ s/<xxx><\/xxx>/\n$replace/;
976           }
977       }
978     }
980     $xml_msg=~ s/<xxx><\/xxx>//;
982     # Retrun Message
983         &main::daemon_log("0 DEBUG: time to process gosa-si message '$header' : ".sprintf("%.4f", (Time::HiRes::time - $startTime))." seconds", 1034);
984     return ( $xml_msg );
987 ################################   
988 # @brief Deletes a client from Opsi.
989 # @param msg - STRING - xml message with tag hostId
990 # @param msg_hash - HASHREF - message information parsed into a hash
991 # @param session_id - INTEGER - POE session id of the processing of this message
992 # @return out_msg - STRING - feedback to GOsa in success and error case
993 sub opsi_del_client {
994         my $startTime = Time::HiRes::time;
995     my ($msg, $msg_hash, $session_id) = @_;
996     my $header = @{$msg_hash->{'header'}}[0];
997     my $source = @{$msg_hash->{'source'}}[0];
998     my $target = @{$msg_hash->{'target'}}[0];
999     my $forward_to_gosa = @{$msg_hash->{'forward_to_gosa'}}[0];
1000     my $hostId;
1001     my $error = 0;
1003     # Build return message with twisted target and source
1004     my $out_hash = &main::create_xml_hash("answer_$header", $main::server_address, $source);
1005     if (defined $forward_to_gosa) {
1006       &add_content2xml_hash($out_hash, "forward_to_gosa", $forward_to_gosa);
1007     }
1009     # Sanity check of needed parameter
1010     if ((exists $msg_hash->{'hostId'}) && (@{$msg_hash->{'hostId'}} != 1) || (@{$msg_hash->{'hostId'}}[0] eq ref 'HASH'))  {
1011         $error++;
1012         &add_content2xml_hash($out_hash, "error_string", "hostId contains no or more than one values");
1013         &add_content2xml_hash($out_hash, "error", "hostId");
1014         &main::daemon_log("$session_id ERROR: hostId contains no or more than one values: $msg", 1); 
1015     }
1017     if (not $error) {
1019     # Get hostId
1020         $hostId = @{$msg_hash->{'hostId'}}[0];
1021         &add_content2xml_hash($out_hash, "hostId", "$hostId");
1023     # JSON Query
1024         my $callobj = {
1025             method  => 'deleteClient',
1026             params  => [ $hostId ],
1027             id  => 1,
1028         };
1029         my $res = $main::opsi_client->call($main::opsi_url, $callobj);
1030     }
1032     # Move to XML string
1033     my $xml_msg= &create_xml_string($out_hash);
1035     # Return message
1036         &main::daemon_log("0 DEBUG: time to process gosa-si message '$header' : ".sprintf("%.4f", (Time::HiRes::time - $startTime))." seconds", 1034);
1037     return ( $xml_msg );
1040 ################################   
1041 # @brief Set a client in Opsi to install and trigger a wake on lan message (WOL).  
1042 # @param msg - STRING - xml message with tags hostId, macaddress
1043 # @param msg_hash - HASHREF - message information parsed into a hash
1044 # @param session_id - INTEGER - POE session id of the processing of this message
1045 # @return out_msg - STRING - feedback to GOsa in success and error case
1046 sub opsi_install_client {
1047         my $startTime = Time::HiRes::time;
1048     my ($msg, $msg_hash, $session_id) = @_;
1049     my $header = @{$msg_hash->{'header'}}[0];
1050     my $source = @{$msg_hash->{'source'}}[0];
1051     my $target = @{$msg_hash->{'target'}}[0];
1052     my $forward_to_gosa = @{$msg_hash->{'forward_to_gosa'}}[0];
1053     my ($hostId, $macaddress);
1054     my $error = 0;
1055     my @out_msg_l;
1057     # Build return message with twisted target and source
1058     my $out_hash = &main::create_xml_hash("answer_$header", $main::server_address, $source);
1059     if (defined $forward_to_gosa) {
1060         &add_content2xml_hash($out_hash, "forward_to_gosa", $forward_to_gosa);
1061     }
1063     # Sanity check of needed parameter
1064     if ((not exists $msg_hash->{'hostId'}) || (@{$msg_hash->{'hostId'}} != 1) || (@{$msg_hash->{'hostId'}}[0] eq ref 'HASH'))  {
1065         $error++;
1066         &add_content2xml_hash($out_hash, "error_string", "no hostId specified or hostId tag invalid");
1067         &add_content2xml_hash($out_hash, "error", "hostId");
1068         &main::daemon_log("$session_id ERROR: no hostId specified or hostId tag invalid: $msg", 1); 
1069     }
1070     if ((not exists $msg_hash->{'macaddress'}) || (@{$msg_hash->{'macaddress'}} != 1) || (@{$msg_hash->{'macaddress'}}[0] eq ref 'HASH') )  {
1071         $error++;
1072         &add_content2xml_hash($out_hash, "error_string", "no macaddress specified or macaddress tag invalid");
1073         &add_content2xml_hash($out_hash, "error", "macaddress");
1074         &main::daemon_log("$session_id ERROR: no macaddress specified or macaddress tag invalid: $msg", 1); 
1075     } else {
1076         if ((exists $msg_hash->{'macaddress'}) && 
1077                 ($msg_hash->{'macaddress'}[0] =~ /^([0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2})$/i)) {  
1078             $macaddress = $1; 
1079         } else { 
1080             $error ++; 
1081             &add_content2xml_hash($out_hash, "error_string", "given mac address is not correct");
1082             &add_content2xml_hash($out_hash, "error", "macaddress");
1083             &main::daemon_log("$session_id ERROR: given mac address is not correct: $msg", 1); 
1084         }
1085     }
1087     if (not $error) {
1089     # Get hostId
1090         $hostId = @{$msg_hash->{'hostId'}}[0];
1091         &add_content2xml_hash($out_hash, "hostId", "$hostId");
1093         # Load all products for this host with status != "not_installed" or actionRequest != "none"
1094         my $callobj = {
1095             method  => 'getProductStates_hash',
1096             params  => [ $hostId ],
1097             id  => 1,
1098         };
1100         my $hres = $main::opsi_client->call($main::opsi_url, $callobj);
1101         if (not &check_opsi_res($hres)){
1102             my $htmp= $hres->result->{$hostId};
1104             # check state != not_installed or action == setup -> load and add
1105             foreach my $product (@{$htmp}){
1106                 # Now we've a couple of hashes...
1107                 if ($product->{'installationStatus'} ne "not_installed" or
1108                         $product->{'actionRequest'} ne "none"){
1110                     # Do an action request for all these -> "setup".
1111                     $callobj = {
1112                         method  => 'setProductActionRequest',
1113                         params  => [ $product->{'productId'}, $hostId, "setup" ],
1114                         id  => 1,
1115                     };
1116                     my $res = $main::opsi_client->call($main::opsi_url, $callobj);
1117                     my ($res_err, $res_err_string) = &check_opsi_res($res);
1118                     if ($res_err){
1119                         &main::daemon_log("$session_id ERROR: cannot set product action request for '$hostId': ".$product->{'productId'}, 1);
1120                     } else {
1121                         &main::daemon_log("$session_id INFO: requesting 'setup' for '".$product->{'productId'}."' on $hostId", 1);
1122                     }
1123                 }
1124             }
1125         }
1126         push(@out_msg_l, &create_xml_string($out_hash));
1127     
1129     # Build wakeup message for client
1130         if (not $error) {
1131             my $wakeup_hash = &create_xml_hash("trigger_wake", "GOSA", "KNOWN_SERVER");
1132             &add_content2xml_hash($wakeup_hash, 'macaddress', $macaddress);
1133             my $wakeup_msg = &create_xml_string($wakeup_hash);
1134             push(@out_msg_l, $wakeup_msg);
1136             # invoke trigger wake for this gosa-si-server
1137             &main::server_server_com::trigger_wake($wakeup_msg, $wakeup_hash, $session_id);
1138         }
1139     }
1140     
1141     # Return messages
1142         &main::daemon_log("0 DEBUG: time to process gosa-si message '$header' : ".sprintf("%.4f", (Time::HiRes::time - $startTime))." seconds", 1034);
1143     return @out_msg_l;
1146 ################################
1147 # @brief Set action for an Opsi client
1148 # @param product - STRING - Opsi product
1149 # @param action - STRING - action
1150 # @param hostId - STRING - Opsi hostId
1151 sub _set_action {
1152   my $product= shift;
1153   my $action = shift;
1154   my $hostId = shift;
1155   my $callobj;
1157   $callobj = {
1158     method  => 'setProductActionRequest',
1159     params  => [ $product, $hostId, $action],
1160     id  => 1,
1161   };
1163   $main::opsi_client->call($main::opsi_url, $callobj);
1166 ################################
1167 # @brief Set state for an Opsi client
1168 # @param product - STRING - Opsi product
1169 # @param action - STRING - state
1170 # @param hostId - STRING - Opsi hostId
1171 sub _set_state {
1172   my $product = shift;
1173   my $state = shift;
1174   my $hostId = shift;
1175   my $callobj;
1177   $callobj = {
1178     method  => 'setProductState',
1179     params  => [ $product, $hostId, $state ],
1180     id  => 1,
1181   };
1183   $main::opsi_client->call($main::opsi_url, $callobj);
1186 ################################
1187 # @brief Create a license pool at Opsi server.
1188 # @param licensePoolId The name of the pool (optional). 
1189 # @param description The description of the pool (optional).
1190 # @param productIds A list of assigned porducts of the pool (optional). 
1191 # @param windowsSoftwareIds A list of windows software IDs associated to the pool (optional). 
1192 sub opsi_createLicensePool {
1193         my $startTime = Time::HiRes::time;
1194     my ($msg, $msg_hash, $session_id) = @_;
1195     my $header = @{$msg_hash->{'header'}}[0];
1196     my $source = @{$msg_hash->{'source'}}[0];
1197     my $target = @{$msg_hash->{'target'}}[0];
1198         my $out_hash;
1199         my $licensePoolId = defined $msg_hash->{'licensePoolId'} ? @{$msg_hash->{'licensePoolId'}}[0] : undef;
1200         my $description = defined $msg_hash->{'description'} ? @{$msg_hash->{'description'}}[0] : undef;
1201         my @productIds = defined $msg_hash->{'productIds'} ? $msg_hash->{'productIds'} : undef;
1202         my @windowsSoftwareIds = defined $msg_hash->{'windowsSoftwareIds'} ? $msg_hash->{'windowsSoftwareIds'} : undef;
1204         # Create license Pool
1205     my $callobj = {
1206         method  => 'createLicensePool',
1207         params  => [ $licensePoolId, $description, @productIds, @windowsSoftwareIds],
1208         id  => 1,
1209     };
1210     my $res = $main::opsi_client->call($main::opsi_url, $callobj);
1212         # Check Opsi error
1213         my ($res_error, $res_error_str) = &check_opsi_res($res);
1214         if ($res_error){
1215                 # Create error message
1216                 &main::daemon_log("$session_id ERROR: cannot create license pool at Opsi server: ".$res_error_str, 1);
1217                 $out_hash = &main::create_xml_hash("error_$header", $main::server_address, $source, $res_error_str);
1218                 return ( &create_xml_string($out_hash) );
1219         }
1221         # Create function result message
1222         $out_hash = &main::create_xml_hash("answer_$header", $main::server_address, $source, $res->result);
1223         if (exists $msg_hash->{forward_to_gosa}) { &add_content2xml_hash($out_hash, "forward_to_gosa", @{$msg_hash->{'forward_to_gosa'}}[0]); }
1225         my $endTime = Time::HiRes::time;
1226         my $elapsedTime = sprintf("%.4f", ($endTime - $startTime));
1227         &main::daemon_log("0 DEBUG: time to process gosa-si message '$header' : $elapsedTime seconds", 1034);
1228         return ( &create_xml_string($out_hash) );
1231 ################################
1232 # @brief Return licensePoolId, description, productIds and windowsSoftwareIds for all found license pools.
1233 sub opsi_getLicensePools_listOfHashes {
1234         my $startTime = Time::HiRes::time;
1235     my ($msg, $msg_hash, $session_id) = @_;
1236     my $header = @{$msg_hash->{'header'}}[0];
1237     my $source = @{$msg_hash->{'source'}}[0];
1238         my $out_hash;
1240         # Fetch infos from Opsi server
1241     my $callobj = {
1242         method  => 'getLicensePools_listOfHashes',
1243         params  => [ ],
1244         id  => 1,
1245     };
1246     my $res = $main::opsi_client->call($main::opsi_url, $callobj);
1248         # Check Opsi error
1249         my ($res_error, $res_error_str) = &check_opsi_res($res);
1250         if ($res_error){
1251                 # Create error message
1252                 &main::daemon_log("$session_id ERROR: cannot get license pool ID list from Opsi server: ".$res_error_str, 1);
1253                 $out_hash = &main::create_xml_hash("error_$header", $main::server_address, $source, $res_error_str);
1254                 return ( &create_xml_string($out_hash) );
1255         }
1257         # Create function result message
1258         my $res_hash = { 'hit'=> [] };
1259         foreach my $licensePool ( @{$res->result}) {
1260                 my $licensePool_hash = { 'licensePoolId' => [$licensePool->{'licensePoolId'}],
1261                         'description' => [$licensePool->{'description'}],
1262                         'productIds' => $licensePool->{'productIds'},
1263                         'windowsSoftwareIds' => $licensePool->{'windowsSoftwareIds'},
1264                         };
1265                 push( @{$res_hash->{hit}}, $licensePool_hash );
1266         }
1268         # Create function result message
1269         $out_hash = &main::create_xml_hash("answer_$header", $main::server_address, $source);
1270         if (exists $msg_hash->{forward_to_gosa}) { &add_content2xml_hash($out_hash, "forward_to_gosa", @{$msg_hash->{'forward_to_gosa'}}[0]); }
1271         $out_hash->{result} = [$res_hash];
1273         my $endTime = Time::HiRes::time;
1274         my $elapsedTime = sprintf("%.4f", ($endTime - $startTime));
1275         &main::daemon_log("0 DEBUG: time to process gosa-si message '$header' : $elapsedTime seconds", 1034);
1276         return ( &create_xml_string($out_hash) );
1279 ################################
1280 # @brief Return productIds, windowsSoftwareIds and description for a given licensePoolId
1281 # @param licensePoolId The name of the pool. 
1282 sub opsi_getLicensePool_hash {
1283         my $startTime = Time::HiRes::time;
1284     my ($msg, $msg_hash, $session_id) = @_;
1285     my $header = @{$msg_hash->{'header'}}[0];
1286     my $source = @{$msg_hash->{'source'}}[0];
1287     my $target = @{$msg_hash->{'target'}}[0];
1288     my $licensePoolId;
1289         my $out_hash;
1291         # Check input sanity
1292         if (&_check_xml_tag_is_ok ($msg_hash, 'licensePoolId')) {
1293                 $licensePoolId = @{$msg_hash->{'licensePoolId'}}[0];
1294         } else {
1295                 return &_giveErrorFeedback($msg_hash, "", $session_id, $_);
1296         }
1298         # Fetch infos from Opsi server
1299     my $callobj = {
1300         method  => 'getLicensePool_hash',
1301         params  => [ $licensePoolId ],
1302         id  => 1,
1303     };
1304     my $res = $main::opsi_client->call($main::opsi_url, $callobj);
1306         # Check Opsi error
1307         my ($res_error, $res_error_str) = &check_opsi_res($res);
1308         if ($res_error){
1309                 # Create error message
1310                 &main::daemon_log("$session_id ERROR: cannot get license pool from Opsi server: ".$res_error_str, 1);
1311                 $out_hash = &main::create_xml_hash("error_$header", $main::server_address, $source);
1312                 &add_content2xml_hash($out_hash, "error", $res_error_str);
1313                 return ( &create_xml_string($out_hash) );
1314         }
1316         # Create function result message
1317         $out_hash = &main::create_xml_hash("answer_$header", $main::server_address, $source);
1318         if (exists $msg_hash->{forward_to_gosa}) { &add_content2xml_hash($out_hash, "forward_to_gosa", @{$msg_hash->{'forward_to_gosa'}}[0]); }
1319         &add_content2xml_hash($out_hash, "licensePoolId", $res->result->{'licensePoolId'});
1320         &add_content2xml_hash($out_hash, "description", $res->result->{'description'});
1321         map(&add_content2xml_hash($out_hash, "productIds", "$_"), @{ $res->result->{'productIds'} });
1322         map(&add_content2xml_hash($out_hash, "windowsSoftwareIds", "$_"), @{ $res->result->{'windowsSoftwareIds'} });
1324         my $endTime = Time::HiRes::time;
1325         my $elapsedTime = sprintf("%.4f", ($endTime - $startTime));
1326         &main::daemon_log("0 DEBUG: time to process gosa-si message '$header' : $elapsedTime seconds", 1034);
1327         return ( &create_xml_string($out_hash) );
1330 sub _parse_getSoftwareLicenseUsages {
1331         my $res = shift;
1333         # Parse Opsi result
1334         my $tmp_licensePool_cache = {};
1335         my $res_hash = { 'hit'=> [] };
1336         foreach my $license ( @{$res}) {
1337                 my $tmp_licensePool = $license->{'licensePoolId'};
1338                 if (not exists $tmp_licensePool_cache->{$tmp_licensePool}) {
1339                         # Fetch missing informations from Opsi and cache the results for a possible later usage
1340                         my ($res, $err) = &_getLicensePool_hash('licensePoolId'=>$tmp_licensePool);
1341                         if (not $err) {
1342                                 $tmp_licensePool_cache->{$tmp_licensePool} = $res;
1343                         }
1344                 }
1345                 my $license_hash = { 'softwareLicenseId' => [$license->{'softwareLicenseId'}],
1346                         'notes' => [$license->{'notes'}],
1347                         'licenseKey' => [$license->{'licenseKey'}],
1348                         'hostId' => [$license->{'hostId'}],
1349                         'licensePoolId' => [$tmp_licensePool],
1350                         };
1351                 if (exists $tmp_licensePool_cache->{$tmp_licensePool}) {
1352                         $license_hash->{$tmp_licensePool} = {'productIds'=>[], 'windowsSoftwareIds'=>[]};
1353                         map (push (@{$license_hash->{$tmp_licensePool}->{productIds}}, $_), @{$tmp_licensePool_cache->{$tmp_licensePool}->{productIds}});
1354                         map (push (@{$license_hash->{$tmp_licensePool}->{windowsSoftwareIds}}, $_), @{$tmp_licensePool_cache->{$tmp_licensePool}->{windowsSoftwareIds}});
1355                 }
1356                 push( @{$res_hash->{hit}}, $license_hash );
1357         }
1359         return $res_hash;
1362 ################################
1363 # @brief Returns softwareLicenseId, notes, licenseKey, hostId and licensePoolId for optional given licensePoolId and hostId
1364 # @param hostid Something like client_1.intranet.mydomain.de (optional).
1365 # @param licensePoolId The name of the pool (optional). 
1366 sub opsi_getSoftwareLicenseUsages {
1367         my $startTime = Time::HiRes::time;
1368         my ($msg, $msg_hash, $session_id) = @_;
1369         my $header = @{$msg_hash->{'header'}}[0];
1370         my $source = @{$msg_hash->{'source'}}[0];
1371         my $target = @{$msg_hash->{'target'}}[0];
1372         my $licensePoolId = defined $msg_hash->{'licensePoolId'} ? @{$msg_hash->{'licensePoolId'}}[0] : undef;
1373         my $hostId = defined $msg_hash->{'hostId'} ? @{$msg_hash->{'hostId'}}[0] : undef;
1374         my $out_hash;
1376         my ($res, $err) = &_getSoftwareLicenseUsages_listOfHashes('licensePoolId'=>$licensePoolId, 'hostId'=>$hostId);
1377         if ($err){
1378                 return &_giveErrorFeedback($msg_hash, "cannot fetch software licenses from license pool : ".$res, $session_id);
1379         }
1381         # Parse Opsi result
1382         my $res_hash = &_parse_getSoftwareLicenseUsages($res);
1384         # Create function result message
1385         $out_hash = &main::create_xml_hash("answer_$header", $main::server_address, $source);
1386         if (exists $msg_hash->{forward_to_gosa}) { &add_content2xml_hash($out_hash, "forward_to_gosa", @{$msg_hash->{'forward_to_gosa'}}[0]); }
1387         $out_hash->{result} = [$res_hash];
1389         my $endTime = Time::HiRes::time;
1390         my $elapsedTime = sprintf("%.4f", ($endTime - $startTime));
1391         &main::daemon_log("0 DEBUG: time to process gosa-si message '$header' : $elapsedTime seconds", 1034);
1392         return ( &create_xml_string($out_hash) );
1395 ################################
1396 # @brief Returns softwareLicenseId, notes, licenseKey, hostId and licensePoolId. Function return is identical to opsi_getSoftwareLicenseUsages
1397 # @param productId Something like 'firefox', 'python' or anything else .
1398 sub opsi_getSoftwareLicenseUsagesForProductId {
1399         my $startTime = Time::HiRes::time;
1400         my ($msg, $msg_hash, $session_id) = @_;
1401         my $header = @{$msg_hash->{'header'}}[0];
1402         my $source = @{$msg_hash->{'source'}}[0];
1404         # Check input sanity
1405         my $productId;
1406         if (&_check_xml_tag_is_ok ($msg_hash, 'productId')) {
1407                 $productId= @{$msg_hash->{'productId'}}[0];
1408         } else {
1409                 return &_giveErrorFeedback($msg_hash, $_, $session_id);
1410         }
1412         # Fetch licensePoolId for productId
1413         my ($res, $err) = &_getLicensePoolId('productId'=>$productId);
1414         if ($err){
1415                 return &_giveErrorFeedback($msg_hash, "cannot fetch licensePoolId for given productId : ".$res, $session_id);
1416         }
1418         my $licensePoolId;
1420         # Fetch softwareLiceceUsages for licensePoolId
1421         ($res, $err) = &_getSoftwareLicenseUsages_listOfHashes('licensePoolId'=>$licensePoolId);
1422         if ($err){
1423                 return &_giveErrorFeedback($msg_hash, "cannot fetch software licenses from license pool : ".$res, $session_id);
1424         }
1426         # Parse Opsi result
1427         my $res_hash = &_parse_getSoftwareLicenseUsages($res);
1429         # Create function result message
1430         my $out_hash = &create_xml_hash("answer_$header", $main::server_address, $source);
1431         if (exists $msg_hash->{forward_to_gosa}) { &add_content2xml_hash($out_hash, "forward_to_gosa", @{$msg_hash->{'forward_to_gosa'}}[0]); }
1432         $out_hash->{result} = [$res_hash];
1434         &main::daemon_log("0 DEBUG: time to process gosa-si message '$header' : ".sprintf("%.4f", (Time::HiRes::time - $startTime))." seconds", 1034);
1435         return ( &create_xml_string($out_hash) );
1438 ################################
1439 # @brief Returns expirationDate, boundToHost, maxInstallation, licenseTyp, licensePoolIds and licenseKeys for a given softwareLicense ID.
1440 # @param softwareLicenseId Identificator of a license.
1441 sub opsi_getSoftwareLicense_hash {
1442         my $startTime = Time::HiRes::time;
1443         my ($msg, $msg_hash, $session_id) = @_;
1444         my $header = @{$msg_hash->{'header'}}[0];
1445         my $source = @{$msg_hash->{'source'}}[0];
1446         my $target = @{$msg_hash->{'target'}}[0];
1447         my $softwareLicenseId;
1448         my $out_hash;
1450         # Check input sanity
1451         if (&_check_xml_tag_is_ok ($msg_hash, 'softwareLicenseId')) {
1452                 $softwareLicenseId = @{$msg_hash->{'softwareLicenseId'}}[0];
1453         } else {
1454                 return &_giveErrorFeedback($msg_hash, $_, $session_id);
1455         }
1457         my $callobj = {
1458                 method  => 'getSoftwareLicense_hash',
1459                 params  => [ $softwareLicenseId ],
1460                 id  => 1,
1461         };
1462         my $res = $main::opsi_client->call($main::opsi_url, $callobj);
1464         # Check Opsi error
1465         my ($res_error, $res_error_str) = &check_opsi_res($res);
1466         if ($res_error){
1467                 # Create error message
1468                 &main::daemon_log("$session_id ERROR: cannot fetch information for license '$softwareLicenseId': ".$res_error_str, 1);
1469                 $out_hash = &main::create_xml_hash("error_$header", $main::server_address, $source, $res_error_str);
1470                 return ( &create_xml_string($out_hash) );
1471         }
1472         
1473         # Create function result message
1474         $out_hash = &main::create_xml_hash("answer_$header", $main::server_address, $source);
1475         if (exists $msg_hash->{forward_to_gosa}) { &add_content2xml_hash($out_hash, "forward_to_gosa", @{$msg_hash->{'forward_to_gosa'}}[0]); }
1476         &add_content2xml_hash($out_hash, "expirationDate", $res->result->{'expirationDate'});
1477         &add_content2xml_hash($out_hash, "boundToHost", $res->result->{'boundToHost'});
1478         &add_content2xml_hash($out_hash, "maxInstallations", $res->result->{'maxInstallations'});
1479         &add_content2xml_hash($out_hash, "licenseTyp", $res->result->{'licenseTyp'});
1480         foreach my $licensePoolId ( @{$res->result->{'licensePoolIds'}}) {
1481                 &add_content2xml_hash($out_hash, "licensePoolId", $licensePoolId);
1482                 &add_content2xml_hash($out_hash, $licensePoolId, $res->result->{'licenseKeys'}->{$licensePoolId});
1483         }
1485         my $endTime = Time::HiRes::time;
1486         my $elapsedTime = sprintf("%.4f", ($endTime - $startTime));
1487         &main::daemon_log("0 DEBUG: time to process gosa-si message '$header' : $elapsedTime seconds", 1034);
1488         return ( &create_xml_string($out_hash) );
1491 ################################
1492 # @brief Delete licnese pool by license pool ID. A pool can only be deleted if there are no software licenses bound to the pool. 
1493 # The fixed parameter deleteLicenses=True specifies that all software licenses bound to the pool are being deleted. 
1494 # @param licensePoolId The name of the pool. 
1495 sub opsi_deleteLicensePool {
1496         my $startTime = Time::HiRes::time;
1497         my ($msg, $msg_hash, $session_id) = @_;
1498     my $header = @{$msg_hash->{'header'}}[0];
1499     my $source = @{$msg_hash->{'source'}}[0];
1500     my $target = @{$msg_hash->{'target'}}[0];
1501     my $licensePoolId;
1502         my $out_hash;
1504         # Check input sanity
1505         if (&_check_xml_tag_is_ok ($msg_hash, 'licensePoolId')) {
1506                 $licensePoolId = @{$msg_hash->{'licensePoolId'}}[0];
1507         } else {
1508                 return &_giveErrorFeedback($msg_hash, $_, $session_id);
1509         }
1511         # Fetch softwareLicenseIds used in license pool
1512         # This has to be done because function deleteLicensePool deletes the pool and the corresponding software licenses
1513         # but not the license contracts of the software licenses. In our case each software license has exactly one license contract. 
1514         my $callobj = {
1515                 method  => 'getSoftwareLicenses_listOfHashes',
1516                 params  => [ ],
1517                 id  => 1,
1518         };
1519         my $res = $main::opsi_client->call($main::opsi_url, $callobj);
1521         # Keep list of licenseContractIds in mind to delete it after the deletion of the software licenses
1522         my @lCI_toBeDeleted;
1523         foreach my $softwareLicenseHash ( @{$res->result} ) {
1524                 if ((@{$softwareLicenseHash->{'licensePoolIds'}} == 0) || (@{$softwareLicenseHash->{'licensePoolIds'}}[0] ne $licensePoolId)) { 
1525                         next; 
1526                 }  
1527                 push (@lCI_toBeDeleted, $softwareLicenseHash->{'licenseContractId'});
1528         }
1530         # Delete license pool at Opsi server
1531     $callobj = {
1532         method  => 'deleteLicensePool',
1533         params  => [ $licensePoolId, 'deleteLicenses=True'  ],
1534         id  => 1,
1535     };
1536     $res = $main::opsi_client->call($main::opsi_url, $callobj);
1537         my ($res_error, $res_error_str) = &check_opsi_res($res);
1538         if ($res_error){
1539                 # Create error message
1540                 &main::daemon_log("$session_id ERROR: cannot delete license pool at Opsi server: ".$res_error_str, 1);
1541                 $out_hash = &main::create_xml_hash("error_$header", $main::server_address, $source, $res_error_str);
1542                 return ( &create_xml_string($out_hash) );
1543         } 
1545         # Delete each license contract connected with the license pool
1546         foreach my $licenseContractId ( @lCI_toBeDeleted ) {
1547                 my $callobj = {
1548                         method  => 'deleteLicenseContract',
1549                         params  => [ $licenseContractId ],
1550                         id  => 1,
1551                 };
1552                 my $res = $main::opsi_client->call($main::opsi_url, $callobj);
1553                 my ($res_error, $res_error_str) = &check_opsi_res($res);
1554                 if ($res_error){
1555                         # Create error message
1556                         &main::daemon_log("$session_id ERROR: cannot delete license contract '$licenseContractId' connected with license pool '$licensePoolId' at Opsi server: ".$res_error_str, 1);
1557                         $out_hash = &main::create_xml_hash("error_$header", $main::server_address, $source, $res_error_str);
1558                         return ( &create_xml_string($out_hash) );
1559                 }
1560         }
1562         # Create function result message
1563         $out_hash = &main::create_xml_hash("answer_$header", $main::server_address, $source);
1564         if (exists $msg_hash->{forward_to_gosa}) { &add_content2xml_hash($out_hash, "forward_to_gosa", @{$msg_hash->{'forward_to_gosa'}}[0]); }
1566         my $endTime = Time::HiRes::time;
1567         my $elapsedTime = sprintf("%.4f", ($endTime - $startTime));
1568         &main::daemon_log("0 DEBUG: time to process gosa-si message '$header' : $elapsedTime seconds", 1034);
1569         return ( &create_xml_string($out_hash) );
1572 ################################
1573 # @brief Create a license contract, create a software license and add the software license to the license pool
1574 # @param licensePoolId The name of the pool the license should be assigned.
1575 # @param licenseKey The license key.
1576 # @param partner Name of the license partner (optional).
1577 # @param conclusionDate Date of conclusion of license contract (optional)
1578 # @param notificationDate Date of notification that license is running out soon (optional).
1579 # @param notes This is the place for some notes (optional)
1580 # @param softwareLicenseId Identificator of a license (optional).
1581 # @param licenseTyp Typ of a licnese, either "OEM", "VOLUME" or "RETAIL" (optional).
1582 # @param maxInstallations The number of clients use this license (optional). 
1583 # @param boundToHost The name of the client the license is bound to (optional).
1584 # @param expirationDate The date when the license is running down (optional). 
1585 sub opsi_createLicense {
1586         my $startTime = Time::HiRes::time;
1587         my ($msg, $msg_hash, $session_id) = @_;
1588     my $header = @{$msg_hash->{'header'}}[0];
1589     my $source = @{$msg_hash->{'source'}}[0];
1590     my $target = @{$msg_hash->{'target'}}[0];
1591         my $partner = defined $msg_hash->{'partner'} ? @{$msg_hash->{'partner'}}[0] : undef;
1592         my $conclusionDate = defined $msg_hash->{'conclusionDate'} ? @{$msg_hash->{'conclusionDate'}}[0] : undef;
1593         my $notificationDate = defined $msg_hash->{'notificationDate'} ? @{$msg_hash->{'notificationDate'}}[0] : undef;
1594         my $notes = defined $msg_hash->{'notes'} ? @{$msg_hash->{'notes'}}[0] : undef;
1595         my $licenseContractId = undef;
1596         my $softwareLicenseId = defined $msg_hash->{'softwareLicenseId'} ? @{$msg_hash->{'softwareLicenseId'}}[0] : undef;
1597         my $licenseType = defined $msg_hash->{'licenseType'} ? @{$msg_hash->{'licenseType'}}[0] : undef;
1598         my $maxInstallations = defined $msg_hash->{'maxInstallations'} ? @{$msg_hash->{'maxInstallations'}}[0] : undef;
1599         my $boundToHost = defined $msg_hash->{'boundToHost'} ? @{$msg_hash->{'boundToHost'}}[0] : undef;
1600         my $expirationDate = defined $msg_hash->{'expirationDate'} ? @{$msg_hash->{'expirationDate'}}[0] : undef;
1601         my $licensePoolId;
1602         my $licenseKey;
1603         my $out_hash;
1605         # Check input sanity
1606         if (&_check_xml_tag_is_ok ($msg_hash, 'licenseKey')) {
1607                 $licenseKey = @{$msg_hash->{'licenseKey'}}[0];
1608         } else {
1609                 return &_giveErrorFeedback($msg_hash, $_, $session_id);
1610         }
1611         if (&_check_xml_tag_is_ok ($msg_hash, 'licensePoolId')) {
1612                 $licensePoolId = @{$msg_hash->{'licensePoolId'}}[0];
1613         } else {
1614                 return &_giveErrorFeedback($msg_hash, $_, $session_id);
1615         }
1616         if ((defined $licenseType) && (not exists $licenseTyp_hash->{$licenseType})) {
1617                 return &_giveErrorFeedback($msg_hash, "The typ of a license can be either 'OEM', 'VOLUME' or 'RETAIL'.", $session_id);
1618         }
1619         
1620         # Automatically define licenseContractId if ID is not given
1621         if (defined $softwareLicenseId) { 
1622                 $licenseContractId = "c_".$softwareLicenseId;
1623         }
1625         # Create license contract at Opsi server
1626     my $callobj = {
1627         method  => 'createLicenseContract',
1628         params  => [ $licenseContractId, $partner, $conclusionDate, $notificationDate, undef, $notes ],
1629         id  => 1,
1630     };
1631     my $res = $main::opsi_client->call($main::opsi_url, $callobj);
1633         # Check Opsi error
1634         my ($res_error, $res_error_str) = &check_opsi_res($res);
1635         if ($res_error){
1636                 # Create error message
1637                 &main::daemon_log("$session_id ERROR: cannot create license contract at Opsi server: ".$res_error_str, 1);
1638                 $out_hash = &main::create_xml_hash("error_$header", $main::server_address, $source, $res_error_str);
1639                 return ( &create_xml_string($out_hash) );
1640         }
1641         
1642         $licenseContractId = $res->result;
1644         # Create software license at Opsi server
1645     $callobj = {
1646         method  => 'createSoftwareLicense',
1647         params  => [ $softwareLicenseId, $licenseContractId, $licenseType, $maxInstallations, $boundToHost, $expirationDate ],
1648         id  => 1,
1649     };
1650     $res = $main::opsi_client->call($main::opsi_url, $callobj);
1652         # Check Opsi error
1653         ($res_error, $res_error_str) = &check_opsi_res($res);
1654         if ($res_error){
1655                 # Create error message
1656                 &main::daemon_log("$session_id ERROR: cannot create software license at Opsi server: ".$res_error_str, 1);
1657                 $out_hash = &main::create_xml_hash("error_$header", $main::server_address, $source, $res_error_str);
1658                 return ( &create_xml_string($out_hash) );
1659         }
1661         $softwareLicenseId = $res->result;
1663         # Add software license to license pool
1664         $callobj = {
1665         method  => 'addSoftwareLicenseToLicensePool',
1666         params  => [ $softwareLicenseId, $licensePoolId, $licenseKey ],
1667         id  => 1,
1668     };
1669     $res = $main::opsi_client->call($main::opsi_url, $callobj);
1671         # Check Opsi error
1672         ($res_error, $res_error_str) = &check_opsi_res($res);
1673         if ($res_error){
1674                 # Create error message
1675                 &main::daemon_log("$session_id ERROR: cannot add software license to license pool at Opsi server: ".$res_error_str, 1);
1676                 $out_hash = &main::create_xml_hash("error_$header", $main::server_address, $source, $res_error_str);
1677                 return ( &create_xml_string($out_hash) );
1678         }
1680         # Create function result message
1681         $out_hash = &main::create_xml_hash("answer_$header", $main::server_address, $source);
1682         if (exists $msg_hash->{forward_to_gosa}) { &add_content2xml_hash($out_hash, "forward_to_gosa", @{$msg_hash->{'forward_to_gosa'}}[0]); }
1683         
1684         my $endTime = Time::HiRes::time;
1685         my $elapsedTime = sprintf("%.4f", ($endTime - $startTime));
1686         &main::daemon_log("0 DEBUG: time to process gosa-si message '$header' : $elapsedTime seconds", 1034);
1687         return ( &create_xml_string($out_hash) );
1690 ################################
1691 # @brief Assign a software license to a host
1692 # @param hostid Something like client_1.intranet.mydomain.de
1693 # @param licensePoolId The name of the pool.
1694 sub opsi_assignSoftwareLicenseToHost {
1695         my $startTime = Time::HiRes::time;
1696         my ($msg, $msg_hash, $session_id) = @_;
1697     my $header = @{$msg_hash->{'header'}}[0];
1698     my $source = @{$msg_hash->{'source'}}[0];
1699     my $target = @{$msg_hash->{'target'}}[0];
1700         my $hostId;
1701         my $licensePoolId;
1703         # Check input sanity
1704         if (&_check_xml_tag_is_ok ($msg_hash, 'hostId')) {
1705                 $hostId = @{$msg_hash->{'hostId'}}[0];
1706         } else {
1707                 return &_giveErrorFeedback($msg_hash, $_, $session_id);
1708         }
1709         if (&_check_xml_tag_is_ok ($msg_hash, 'licensePoolId')) {
1710                 $licensePoolId = @{$msg_hash->{'licensePoolId'}}[0];
1711         } else {
1712                 return &_giveErrorFeedback($msg_hash, $_, $session_id);
1713         }
1715         # Assign a software license to a host
1716         my $callobj = {
1717         method  => 'getAndAssignSoftwareLicenseKey',
1718         params  => [ $hostId, $licensePoolId ],
1719         id  => 1,
1720     };
1721     my $res = $main::opsi_client->call($main::opsi_url, $callobj);
1723         # Check Opsi error
1724         my ($res_error, $res_error_str) = &check_opsi_res($res);
1725         if ($res_error){
1726                 # Create error message
1727                 &main::daemon_log("$session_id ERROR: cannot assign a software license to a host at Opsi server: ".$res_error_str, 1);
1728                 my $out_hash = &main::create_xml_hash("error_$header", $main::server_address, $source, $res_error_str);
1729                 return ( &create_xml_string($out_hash) );
1730         }
1732         # Create function result message
1733         my $out_hash = &main::create_xml_hash("answer_$header", $main::server_address, $source);
1734         if (exists $msg_hash->{forward_to_gosa}) { &add_content2xml_hash($out_hash, "forward_to_gosa", @{$msg_hash->{'forward_to_gosa'}}[0]); }
1735         
1736         my $endTime = Time::HiRes::time;
1737         my $elapsedTime = sprintf("%.4f", ($endTime - $startTime));
1738         &main::daemon_log("0 DEBUG: time to process gosa-si message '$header' : $elapsedTime seconds", 1034);
1739         return ( &create_xml_string($out_hash) );
1742 ################################
1743 # @brief Unassign a software license from a host.
1744 # @param hostid Something like client_1.intranet.mydomain.de
1745 # @param licensePoolId The name of the pool.
1746 sub opsi_unassignSoftwareLicenseFromHost {
1747         my $startTime = Time::HiRes::time;
1748         my ($msg, $msg_hash, $session_id) = @_;
1749     my $header = @{$msg_hash->{'header'}}[0];
1750     my $source = @{$msg_hash->{'source'}}[0];
1751     my $target = @{$msg_hash->{'target'}}[0];
1752         my $hostId;
1753         my $licensePoolId;
1755         # Check input sanity
1756         if (&_check_xml_tag_is_ok ($msg_hash, 'hostId')) {
1757                 $hostId = @{$msg_hash->{'hostId'}}[0];
1758         } else {
1759                 return &_giveErrorFeedback($msg_hash, $_, $session_id);
1760         }
1761         if (&_check_xml_tag_is_ok ($msg_hash, 'licensePoolId')) {
1762                 $licensePoolId = @{$msg_hash->{'licensePoolId'}}[0];
1763         } else {
1764                 return &_giveErrorFeedback($msg_hash, $_, $session_id);
1765         }
1767         # Unassign a software license from a host
1768         my $callobj = {
1769         method  => 'deleteSoftwareLicenseUsage',
1770         params  => [ $hostId, '', $licensePoolId ],
1771         id  => 1,
1772     };
1773     my $res = $main::opsi_client->call($main::opsi_url, $callobj);
1775         # Check Opsi error
1776         my ($res_error, $res_error_str) = &check_opsi_res($res);
1777         if ($res_error){
1778                 # Create error message
1779                 &main::daemon_log("$session_id ERROR: cannot unassign a software license from a host at Opsi server: ".$res_error_str, 1);
1780                 my $out_hash = &main::create_xml_hash("error_$header", $main::server_address, $source, $res_error_str);
1781                 return ( &create_xml_string($out_hash) );
1782         }
1784         # Create function result message
1785         my $out_hash = &main::create_xml_hash("answer_$header", $main::server_address, $source);
1786         if (exists $msg_hash->{forward_to_gosa}) { &add_content2xml_hash($out_hash, "forward_to_gosa", @{$msg_hash->{'forward_to_gosa'}}[0]); }
1787         
1788         my $endTime = Time::HiRes::time;
1789         my $elapsedTime = sprintf("%.4f", ($endTime - $startTime));
1790         &main::daemon_log("0 DEBUG: time to process gosa-si message '$header' : $elapsedTime seconds", 1034);
1791         return ( &create_xml_string($out_hash) );
1794 ################################
1795 # @brief Unassign all software licenses from a host
1796 # @param hostid Something like client_1.intranet.mydomain.de
1797 sub opsi_unassignAllSoftwareLicensesFromHost {
1798         my $startTime = Time::HiRes::time;
1799         my ($msg, $msg_hash, $session_id) = @_;
1800     my $header = @{$msg_hash->{'header'}}[0];
1801     my $source = @{$msg_hash->{'source'}}[0];
1802     my $target = @{$msg_hash->{'target'}}[0];
1803         my $hostId;
1805         # Check input sanity
1806         if (&_check_xml_tag_is_ok ($msg_hash, 'hostId')) {
1807                 $hostId = @{$msg_hash->{'hostId'}}[0];
1808         } else {
1809                 return &_giveErrorFeedback($msg_hash, $_, $session_id);
1810         }
1812         # Unassign all software licenses from a host
1813         my $callobj = {
1814         method  => 'deleteAllSoftwareLicenseUsages',
1815         params  => [ $hostId ],
1816         id  => 1,
1817     };
1818     my $res = $main::opsi_client->call($main::opsi_url, $callobj);
1820         # Check Opsi error
1821         my ($res_error, $res_error_str) = &check_opsi_res($res);
1822         if ($res_error){
1823                 # Create error message
1824                 &main::daemon_log("$session_id ERROR: cannot unassign a software license from a host at Opsi server: ".$res_error_str, 1);
1825                 my $out_hash = &main::create_xml_hash("error_$header", $main::server_address, $source, $res_error_str);
1826                 return ( &create_xml_string($out_hash) );
1827         }
1829         # Create function result message
1830         my $out_hash = &main::create_xml_hash("answer_$header", $main::server_address, $source);
1831         if (exists $msg_hash->{forward_to_gosa}) { &add_content2xml_hash($out_hash, "forward_to_gosa", @{$msg_hash->{'forward_to_gosa'}}[0]); }
1832         
1833         my $endTime = Time::HiRes::time;
1834         my $elapsedTime = sprintf("%.4f", ($endTime - $startTime));
1835         &main::daemon_log("0 DEBUG: time to process gosa-si message '$header' : $elapsedTime seconds", 1034);
1836         return ( &create_xml_string($out_hash) );
1840 ################################
1841 # @brief Returns the assigned licensePoolId and licenses, how often the product is installed and at which host
1842 # and the number of max and remaining installations for a given OPSI product.
1843 # @param productId Identificator of an OPSI product.
1844 sub opsi_getLicenseInformationForProduct {
1845         my $startTime = Time::HiRes::time;
1846     my ($msg, $msg_hash, $session_id) = @_;
1847     my $header = @{$msg_hash->{'header'}}[0];
1848     my $source = @{$msg_hash->{'source'}}[0];
1849         my $productId;
1850         my $out_hash;
1852         # Check input sanity
1853         if (&_check_xml_tag_is_ok ($msg_hash, 'productId')) {
1854                 $productId = @{$msg_hash->{'productId'}}[0];
1855         } else {
1856                 return &_giveErrorFeedback($msg_hash, $_, $session_id);
1857         }
1859         # Fetch infos from Opsi server
1860     my $callobj = {
1861         method  => 'getLicensePoolId',
1862         params  => [ $productId ],
1863         id  => 1,
1864     };
1865     #my $res = $main::opsi_client->call($main::opsi_url, $callobj);
1866     my $res = $opsi_client->call($opsi_url, $callobj);
1868         # Check Opsi error
1869         my ($res_error, $res_error_str) = &check_opsi_res($res);
1870         if ($res_error){
1871                 return &_giveErrorFeedback($msg_hash, "cannot get license pool for product '$productId' : ".$res_error_str, $session_id);
1872         } 
1873         
1874         my $licensePoolId = $res->result;
1876         # Fetch statistic information for given pool ID
1877         $callobj = {
1878                 method  => 'getLicenseStatistics_hash',
1879                 params  => [ ],
1880                 id  => 1,
1881         };
1882         $res = $opsi_client->call($opsi_url, $callobj);
1884         # Check Opsi error
1885         ($res_error, $res_error_str) = &check_opsi_res($res);
1886         if ($res_error){
1887                 # Create error message
1888                 &main::daemon_log("$session_id ERROR: cannot get statistic informations for license pools : ".$res_error_str, 1);
1889                 $out_hash = &main::create_xml_hash("error_$header", $main::server_address, $source, $res_error_str);
1890                 return ( &create_xml_string($out_hash) );
1891         }
1893         # Create function result message
1894         $out_hash = &main::create_xml_hash("answer_$header", $main::server_address, $source);
1895         if (exists $msg_hash->{forward_to_gosa}) { &add_content2xml_hash($out_hash, "forward_to_gosa", @{$msg_hash->{'forward_to_gosa'}}[0]); }
1896         &add_content2xml_hash($out_hash, "licensePoolId", $licensePoolId);
1897         &add_content2xml_hash($out_hash, "licenses", $res->result->{$licensePoolId}->{'licenses'});
1898         &add_content2xml_hash($out_hash, "usageCount", $res->result->{$licensePoolId}->{'usageCount'});
1899         &add_content2xml_hash($out_hash, "maxInstallations", $res->result->{$licensePoolId}->{'maxInstallations'});
1900         &add_content2xml_hash($out_hash, "remainingInstallations", $res->result->{$licensePoolId}->{'remainingInstallations'});
1901         map(&add_content2xml_hash($out_hash, "usedBy", "$_"), @{ $res->result->{$licensePoolId}->{'usedBy'}});
1903         &main::daemon_log("0 DEBUG: time to process gosa-si message '$header' : ".sprintf("%.4f", (Time::HiRes::time - $startTime))." seconds", 1034);
1904         return ( &create_xml_string($out_hash) );
1908 ################################
1909 # @brief Returns licensePoolId, description, a list of productIds, al list of windowsSoftwareIds and a list of licenses for a given licensePoolId. 
1910 # Each license contains softwareLicenseId, maxInstallations, licenseType, licensePoolIds, licenseKeys, hostIds, expirationDate, boundToHost and licenseContractId.
1911 # The licenseContract contains conclusionDate, expirationDate, notes, notificationDate and partner. 
1912 # @param licensePoolId The name of the pool.
1913 sub opsi_getPool {
1914         my $startTime = Time::HiRes::time;
1915     my ($msg, $msg_hash, $session_id) = @_;
1916     my $header = @{$msg_hash->{'header'}}[0];
1917     my $source = @{$msg_hash->{'source'}}[0];
1919         # Check input sanity
1920         my $licensePoolId;
1921         if (&_check_xml_tag_is_ok ($msg_hash, 'licensePoolId')) {
1922                 $licensePoolId = @{$msg_hash->{'licensePoolId'}}[0];
1923         } else {
1924                 return &_giveErrorFeedback($msg_hash, $_, $session_id);
1925         }
1927         # Create hash for the answer
1928         my $out_hash = &main::create_xml_hash("answer_$header", $main::server_address, $source);
1929         if (exists $msg_hash->{forward_to_gosa}) { &add_content2xml_hash($out_hash, "forward_to_gosa", @{$msg_hash->{'forward_to_gosa'}}[0]); }
1931         # Call Opsi
1932         my ($res, $err) = &_getLicensePool_hash( 'licensePoolId'=> $licensePoolId );
1933         if ($err){
1934                 return &_giveErrorFeedback($msg_hash, "cannot get license pool from Opsi server: ".$res, $session_id);
1935         }
1936         # Add data to outgoing hash
1937         &add_content2xml_hash($out_hash, "licensePoolId", $res->{'licensePoolId'});
1938         &add_content2xml_hash($out_hash, "description", $res->{'description'});
1939         map(&add_content2xml_hash($out_hash, "productIds", "$_"), @{ $res->{'productIds'} });
1940         map(&add_content2xml_hash($out_hash, "windowsSoftwareIds", "$_"), @{ $res->{'windowsSoftwareIds'} });
1943         # Call Opsi two times
1944         my ($usages_res, $usages_err) = &_getSoftwareLicenseUsages_listOfHashes('licensePoolId'=>$licensePoolId);
1945         if ($usages_err){
1946                 return &_giveErrorFeedback($msg_hash, "cannot get software license usage information from Opsi server: ".$usages_res, $session_id);
1947         }
1948         my ($licenses_res, $licenses_err) = &_getSoftwareLicenses_listOfHashes();
1949         if ($licenses_err){
1950                 return &_giveErrorFeedback($msg_hash, "cannot get software license information from Opsi server: ".$licenses_res, $session_id);
1951         }
1953         # Add data to outgoing hash
1954         # Parse through all software licenses and select those associated to the pool
1955         my $res_hash = { 'hit'=> [] };
1956         foreach my $license ( @$licenses_res) {
1957                 # Each license hash has a list of licensePoolIds so go through this list and search for matching licensePoolIds
1958                 my $found = 0;
1959                 my @licensePoolIds_list = @{$license->{licensePoolIds}};
1960                 foreach my $lPI ( @licensePoolIds_list) {
1961                         if ($lPI eq $licensePoolId) { $found++ }
1962                 }
1963                 if (not $found ) { next; };
1964                 # Found matching licensePoolId
1965                 my $license_hash = { 'softwareLicenseId' => [$license->{'softwareLicenseId'}],
1966                         'licenseKeys' => {},
1967                         'expirationDate' => [$license->{'expirationDate'}],
1968                         'boundToHost' => [$license->{'boundToHost'}],
1969                         'maxInstallations' => [$license->{'maxInstallations'}],
1970                         'licenseType' => [$license->{'licenseType'}],
1971                         'licenseContractId' => [$license->{'licenseContractId'}],
1972                         'licensePoolIds' => [],
1973                         'hostIds' => [],
1974                         };
1975                 foreach my $licensePoolId (@{ $license->{'licensePoolIds'}}) {
1976                         push( @{$license_hash->{'licensePoolIds'}}, $licensePoolId);
1977                         $license_hash->{licenseKeys}->{$licensePoolId} =  [ $license->{'licenseKeys'}->{$licensePoolId} ];
1978                 }
1979                 foreach my $usage (@$usages_res) {
1980                         # Search for hostIds with matching softwareLicenseId
1981                         if ($license->{'softwareLicenseId'} eq $usage->{'softwareLicenseId'}) {
1982                                 push( @{ $license_hash->{hostIds}}, $usage->{hostId});
1983                         }
1984                 }
1986                 # Each softwareLicenseId has one licenseContractId, fetch contract details for each licenseContractId
1987                 my ($lContract_res, $lContract_err) = &_getLicenseContract_hash('licenseContractId'=>$license->{licenseContractId});
1988                 if ($lContract_err){
1989                         return &_giveErrorFeedback($msg_hash, "cannot get software license contract information from Opsi server: ".$licenses_res, $session_id);
1990                 }
1991                 $license_hash->{$license->{'licenseContractId'}} = [];
1992                 my $licenseContract_hash = { 'conclusionDate' => [$lContract_res->{conclusionDate}],
1993                         'notificationDate' => [$lContract_res->{notificationDate}],
1994                         'notes' => [$lContract_res->{notes}],
1995                         'exirationDate' => [$lContract_res->{expirationDate}],
1996                         'partner' => [$lContract_res->{partner}],
1997                 };
1998                 push( @{$license_hash->{licenseContractData}}, $licenseContract_hash );
2000                 push( @{$res_hash->{hit}}, $license_hash );
2001         }
2002         $out_hash->{licenses} = [$res_hash];
2004         my $endTime = Time::HiRes::time;
2005         my $elapsedTime = sprintf("%.4f", ($endTime - $startTime));
2006         &main::daemon_log("0 DEBUG: time to process gosa-si message '$header' : $elapsedTime seconds", 1034);
2007     return ( &create_xml_string($out_hash) );
2011 ################################
2012 # @brief Removes at first the software license from license pool and than deletes the software license. 
2013 # Attention, the software license has to exists otherwise it will lead to an Opsi internal server error.
2014 # @param softwareLicenseId Identificator of a license.
2015 # @param licensePoolId The name of the pool.
2016 sub opsi_removeLicense {
2017         my $startTime = Time::HiRes::time;
2018     my ($msg, $msg_hash, $session_id) = @_;
2019     my $header = @{$msg_hash->{'header'}}[0];
2020     my $source = @{$msg_hash->{'source'}}[0];
2022         # Check input sanity
2023         my $softwareLicenseId;
2024         if (&_check_xml_tag_is_ok ($msg_hash, 'softwareLicenseId')) {
2025                 $softwareLicenseId = @{$msg_hash->{'softwareLicenseId'}}[0];
2026         } else {
2027                 return &_giveErrorFeedback($msg_hash, $_, $session_id);
2028         }
2029         my $licensePoolId;
2030         if (&_check_xml_tag_is_ok ($msg_hash, 'licensePoolId')) {
2031                 $licensePoolId = @{$msg_hash->{'licensePoolId'}}[0];
2032         } else {
2033                 return &_giveErrorFeedback($msg_hash, $_, $session_id);
2034         }
2035         
2036         # Call Opsi
2037         my ($res, $err) = &_removeSoftwareLicenseFromLicensePool( 'licensePoolId' => $licensePoolId, 'softwareLicenseId' => $softwareLicenseId );
2038         if ($err){
2039                 return &_giveErrorFeedback($msg_hash, "cannot delete software license from pool: ".$res, $session_id);
2040         }
2042         # Call Opsi
2043         ($res, $err) = &_deleteSoftwareLicense( 'softwareLicenseId'=>$softwareLicenseId );
2044         if ($err){
2045                 return &_giveErrorFeedback($msg_hash, "cannot delete software license from Opsi server: ".$res, $session_id);
2046         }
2048         # Create hash for the answer
2049         my $out_hash = &main::create_xml_hash("answer_$header", $main::server_address, $source);
2050         if (exists $msg_hash->{forward_to_gosa}) { &add_content2xml_hash($out_hash, "forward_to_gosa", @{$msg_hash->{'forward_to_gosa'}}[0]); }
2051         my $endTime = Time::HiRes::time;
2052         my $elapsedTime = sprintf("%.4f", ($endTime - $startTime));
2053         &main::daemon_log("0 DEBUG: time to process gosa-si message '$header' : $elapsedTime seconds", 1034);
2054         return ( &create_xml_string($out_hash) );
2058 ################################
2059 # @brief Return softwareLicenseId, maxInstallations, licenseType, licensePoolIds, licenseContractId, expirationDate, boundToHost and a list of productIds.
2060 # @param hostId Something like client_1.intranet.mydomain.de
2061 sub opsi_getReservedLicenses {
2062         my $startTime = Time::HiRes::time;
2063         my ($msg, $msg_hash, $session_id) = @_;
2064         my $header = @{$msg_hash->{'header'}}[0];
2065         my $source = @{$msg_hash->{'source'}}[0];
2067         # Check input sanity
2068         my $hostId;
2069         if (&_check_xml_tag_is_ok ($msg_hash, 'hostId')) {
2070                 $hostId = @{$msg_hash->{'hostId'}}[0];
2071         } else {
2072                 return &_giveErrorFeedback($msg_hash, $_, $session_id);
2073         }
2075         # Fetch informations from Opsi server
2076         my ($license_res, $license_err) = &_getSoftwareLicenses_listOfHashes();
2077         if ($license_err){
2078                 return &_giveErrorFeedback($msg_hash, "cannot get software license information from Opsi server: ".$license_res, $session_id);
2079         }
2081         # Parse result
2082         my $res_hash = { 'hit'=> [] };
2083         foreach my $license ( @$license_res) {
2084                 if ($license->{boundToHost} ne $hostId) { next; }
2086                 my $license_hash = { 'softwareLicenseId' => [$license->{'softwareLicenseId'}],
2087                         'maxInstallations' => [$license->{'maxInstallations'}],
2088                         'boundToHost' => [$license->{'boundToHost'}],
2089                         'expirationDate' => [$license->{'expirationDate'}],
2090                         'licenseContractId' => [$license->{'licenseContractId'}],
2091                         'licenseType' => [$license->{'licenseType'}],
2092                         'licensePoolIds' => [],
2093                         };
2094                 
2095                 foreach my $licensePoolId (@{$license->{'licensePoolIds'}}) {
2096                         # Fetch information for license pools containing a software license which is bound to given host
2097                         my ($pool_res, $pool_err) = &_getLicensePool_hash( 'licensePoolId'=>$licensePoolId );
2098                         if ($pool_err){
2099                                 return &_giveErrorFeedback($msg_hash, "cannot get license pool from Opsi server: ".$pool_res, $session_id);
2100                         }
2102                         # Add licensePool information to result hash
2103                         push (@{$license_hash->{licensePoolIds}}, $licensePoolId);
2104                         $license_hash->{$licensePoolId} = {'productIds'=>[], 'windowsSoftwareIds'=>[]};
2105                         map (push (@{$license_hash->{$licensePoolId}->{productIds}}, $_), @{$pool_res->{productIds}});
2106                         map (push (@{$license_hash->{$licensePoolId}->{windowsSoftwareIds}}, $_), @{$pool_res->{windowsSoftwareIds}});
2107                 }
2108                 push( @{$res_hash->{hit}}, $license_hash );
2109         }
2110         my $out_hash = &main::create_xml_hash("answer_$header", $main::server_address, $source);
2111         if (exists $msg_hash->{forward_to_gosa}) { &add_content2xml_hash($out_hash, "forward_to_gosa", @{$msg_hash->{'forward_to_gosa'}}[0]); }
2112         $out_hash->{licenses} = [$res_hash];
2114         my $endTime = Time::HiRes::time;
2115         my $elapsedTime = sprintf("%.4f", ($endTime - $startTime));
2116         &main::daemon_log("0 DEBUG: time to process gosa-si message '$header' : $elapsedTime seconds", 1034);
2117     return ( &create_xml_string($out_hash) );
2120 ################################
2121 # @brief Bound the given softwareLicenseId to the given host.
2122 # @param hostId Opsi hostId
2123 # @param softwareLicenseId Identificator of a license (optional).
2124 sub opsi_boundHostToLicense {
2125         my $startTime = Time::HiRes::time;
2126         my ($msg, $msg_hash, $session_id) = @_;
2127         my $header = @{$msg_hash->{'header'}}[0];
2128         my $source = @{$msg_hash->{'source'}}[0];
2130         # Check input sanity
2131         my $hostId;
2132         if (&_check_xml_tag_is_ok ($msg_hash, 'hostId')) {
2133                 $hostId = @{$msg_hash->{'hostId'}}[0];
2134         } else {
2135                 return &_giveErrorFeedback($msg_hash, $_, $session_id);
2136         }
2137         my $softwareLicenseId;
2138         if (&_check_xml_tag_is_ok ($msg_hash, 'softwareLicenseId')) {
2139                 $softwareLicenseId = @{$msg_hash->{'softwareLicenseId'}}[0];
2140         } else {
2141                 return &_giveErrorFeedback($msg_hash, $_, $session_id);
2142         }
2144         # Fetch informations from Opsi server
2145         my ($license_res, $license_err) = &_getSoftwareLicenses_listOfHashes();
2146         if ($license_err){
2147                 return &_giveErrorFeedback($msg_hash, "cannot get software license information from Opsi server: ".$license_res, $session_id);
2148         }
2150         # Memorize parameter for given softwareLicenseId
2151         my $licenseContractId;
2152         my $licenseType;
2153         my $maxInstallations;
2154         my $boundToHost;
2155         my $expirationDate = "";
2156         my $found;
2157         foreach my $license (@$license_res) {
2158                 if ($license->{softwareLicenseId} ne $softwareLicenseId) { next; }
2159                 $licenseContractId = $license->{licenseContractId};
2160                 $licenseType = $license->{licenseType};
2161                 $maxInstallations = $license->{maxInstallations};
2162                 $expirationDate = $license->{expirationDate};
2163                 $found++;
2164         }
2166         if (not $found) {
2167                 return &_giveErrorFeedback($msg_hash, "no softwarelicenseId found with name '".$softwareLicenseId."'", $session_id);
2168         }
2170         # Set boundToHost option for a given software license
2171         my ($bound_res, $bound_err) = &_createSoftwareLicense('softwareLicenseId'=>$softwareLicenseId, 
2172                         'licenseContractId' => $licenseContractId, 
2173                         'licenseType' => $licenseType, 
2174                         'maxInstallations' => $maxInstallations, 
2175                         'boundToHost' => $hostId, 
2176                         'expirationDate' => $expirationDate);
2177         if ($bound_err) {
2178                 return &_giveErrorFeedback($msg_hash, "cannot set boundToHost for given softwareLicenseId and hostId: ".$bound_res, $session_id);
2179         }
2181         my $out_hash = &main::create_xml_hash("answer_$header", $main::server_address, $source);
2182         if (exists $msg_hash->{forward_to_gosa}) { &add_content2xml_hash($out_hash, "forward_to_gosa", @{$msg_hash->{'forward_to_gosa'}}[0]); }
2184         my $endTime = Time::HiRes::time;
2185         my $elapsedTime = sprintf("%.4f", ($endTime - $startTime));
2186         &main::daemon_log("0 DEBUG: time to process gosa-si message '$header' : $elapsedTime seconds", 1034);
2187     return ( &create_xml_string($out_hash) );
2190 ################################
2191 # @brief Release a software license formerly bound to a host.
2192 # @param softwareLicenseId Identificator of a license.
2193 sub opsi_unboundHostFromLicense {
2194         # This is really mad! Opsi is not able to unbound a lincense from a host. To provide the functionality for GOsa
2195         # 4 rpc calls to Opsi are necessary. First, fetch all data for the given softwareLicenseId, then all details for the associated
2196         # licenseContractId, then delete the softwareLicense and finally recreate the softwareLicense without the boundToHost option. NASTY!
2197         my $startTime = Time::HiRes::time;
2198         my ($msg, $msg_hash, $session_id) = @_;
2199         my $header = @{$msg_hash->{'header'}}[0];
2200         my $source = @{$msg_hash->{'source'}}[0];
2202         # Check input sanity
2203         my $softwareLicenseId;
2204         if (&_check_xml_tag_is_ok ($msg_hash, 'softwareLicenseId')) {
2205                 $softwareLicenseId = @{$msg_hash->{'softwareLicenseId'}}[0];
2206         } else {
2207                 return &_giveErrorFeedback($msg_hash, $_, $session_id);
2208         }
2209         
2210         # Memorize parameter witch are required for this procedure
2211         my $licenseContractId;
2212         my $licenseType;
2213         my $maxInstallations;
2214         my $expirationDate;
2215         my $partner;
2216         my $conclusionDate;
2217         my $notificationDate;
2218         my $notes;
2219         my $licensePoolId;
2220         my $licenseKey;
2222         # Fetch license informations from Opsi server
2223         my ($license_res, $license_err) = &_getSoftwareLicenses_listOfHashes();
2224         if ($license_err){
2225                 return &_giveErrorFeedback($msg_hash, "cannot get software license information from Opsi server, required to unbound license from host: ".$license_res, $session_id);
2226         }
2227         my $found = 0;
2228         foreach my $license (@$license_res) {
2229                 if (($found > 0) || ($license->{softwareLicenseId} ne $softwareLicenseId)) { next; }
2230                 $licenseContractId = $license->{licenseContractId};
2231                 $licenseType = $license->{licenseType};
2232                 $maxInstallations = $license->{maxInstallations};
2233                 $expirationDate = $license->{expirationDate};
2234                 $licensePoolId = @{$license->{licensePoolIds}}[0];
2235                 $licenseKey = $license->{licenseKeys}->{$licensePoolId};
2236                 $found++;
2237         }
2238         
2239         # Fetch contract informations from Opsi server
2240         my ($contract_res, $contract_err) = &_getLicenseContract_hash('licenseContractId'=>$licenseContractId);
2241         if ($contract_err){
2242                 return &_giveErrorFeedback($msg_hash, "cannot get contract license information from Opsi server, required to unbound license from host: ".$license_res, $session_id);
2243         }
2244         $partner = $contract_res->{partner};
2245         $conclusionDate = $contract_res->{conclusionDate};
2246         $notificationDate = $contract_res->{notificationDate};
2247         $expirationDate = $contract_res->{expirationDate};
2248         $notes = $contract_res->{notes};
2250         # Delete software license
2251         my ($res, $err) = &_deleteSoftwareLicense( 'softwareLicenseId' => $softwareLicenseId, 'removeFromPools'=> "true" );
2252         if ($err) {
2253                 return &_giveErrorFeedback($msg_hash, "cannot delet license from Opsi server, required to unbound license from host : ".$res, $session_id);
2254         }
2256         # Recreate software license without boundToHost
2257         ($res, $err) = &_createLicenseContract( 'licenseContractId' => $licenseContractId, 'partner' => $partner, 'conclusionDate' => $conclusionDate, 
2258                         'notificationDate' => $notificationDate, 'expirationDate' => $expirationDate, 'notes' => $notes );
2259         if ($err) {
2260                 return &_giveErrorFeedback($msg_hash, "cannot create license contract at Opsi server, required to unbound license from host : ".$res, $session_id);
2261         }
2262         ($res, $err) = &_createSoftwareLicense( 'softwareLicenseId' => $softwareLicenseId, 'licenseContractId' => $licenseContractId, 'licenseType' => $licenseType, 
2263                         'maxInstallations' => $maxInstallations, 'boundToHost' => "", 'expirationDate' => $expirationDate       );
2264         if ($err) {
2265                 return &_giveErrorFeedback($msg_hash, "cannot create software license at Opsi server, required to unbound license from host : ".$res, $session_id);
2266         }
2267         ($res, $err) = &_addSoftwareLicenseToLicensePool( 'softwareLicenseId' => $softwareLicenseId, 'licensePoolId' => $licensePoolId, 'licenseKey' => $licenseKey );
2268         if ($err) {
2269                 return &_giveErrorFeedback($msg_hash, "cannot add software license to license pool at Opsi server, required to unbound license from host : ".$res, $session_id);
2270         }
2272         my $out_hash = &main::create_xml_hash("answer_$header", $main::server_address, $source);
2273         if (exists $msg_hash->{forward_to_gosa}) { &add_content2xml_hash($out_hash, "forward_to_gosa", @{$msg_hash->{'forward_to_gosa'}}[0]); }
2275         &main::daemon_log("0 DEBUG: time to process gosa-si message '$header' : ".sprintf("%.4f", (Time::HiRes::time - $startTime))." seconds", 1034);
2276     return ( &create_xml_string($out_hash) );
2279 ################################
2280 # @brief Returns a list of licenses with softwaerLicenseId, maxInstallations, boundToHost, expirationDate, licenseContractId, licenseType, a list of licensePoolIds with associated licenseKeys
2281 sub opsi_getAllSoftwareLicenses {
2282         my $startTime = Time::HiRes::time;
2283         my ($msg, $msg_hash, $session_id) = @_;
2284         my $header = @{$msg_hash->{'header'}}[0];
2285         my $source = @{$msg_hash->{'source'}}[0];
2287         my ($res, $err) = &_getSoftwareLicenses_listOfHashes();
2288         if ($err) {
2289                 return &_giveErrorFeedback($msg_hash, "cannot fetch software licenses from Opsi server : ".$res, $session_id);
2290         }
2292         # Parse result
2293         my $res_hash = { 'hit'=> [] };
2294         foreach my $license ( @$res) {
2295                 my $license_hash = { 'softwareLicenseId' => [$license->{'softwareLicenseId'}],
2296                         'maxInstallations' => [$license->{'maxInstallations'}],
2297                         'boundToHost' => [$license->{'boundToHost'}],
2298                         'expirationDate' => [$license->{'expirationDate'}],
2299                         'licenseContractId' => [$license->{'licenseContractId'}],
2300                         'licenseType' => [$license->{'licenseType'}],
2301                         'licensePoolIds' => [],
2302                         'licenseKeys'=> {}
2303                         };
2304                 foreach my $licensePoolId (@{$license->{'licensePoolIds'}}) {
2305                         push( @{$license_hash->{'licensePoolIds'}}, $licensePoolId);
2306                         $license_hash->{licenseKeys}->{$licensePoolId} =  [ $license->{'licenseKeys'}->{$licensePoolId} ];
2307                 }
2308                 push( @{$res_hash->{hit}}, $license_hash );
2309         }
2310         
2311         my $out_hash = &main::create_xml_hash("answer_$header", $main::server_address, $source);
2312         $out_hash->{licenses} = [$res_hash];
2313         if (exists $msg_hash->{forward_to_gosa}) { &add_content2xml_hash($out_hash, "forward_to_gosa", @{$msg_hash->{'forward_to_gosa'}}[0]); }
2315         &main::daemon_log("0 DEBUG: time to process gosa-si message '$header' : ".sprintf("%.4f", (Time::HiRes::time - $startTime))." seconds", 1034);
2316     return ( &create_xml_string($out_hash) );
2319 sub opsi_test {
2320     my ($msg, $msg_hash, $session_id) = @_;
2321     my $header = @{$msg_hash->{'header'}}[0];
2322     my $source = @{$msg_hash->{'source'}}[0];
2323         my $pram1 = @{$msg_hash->{'productId'}}[0];
2325 print STDERR Dumper $pram1;
2327         # Fetch infos from Opsi server
2328     my $callobj = {
2329         method  => 'getLicensePoolId',
2330         params  => [ $pram1 ],
2331         id  => 1,
2332     };
2333     my $res = $main::opsi_client->call($main::opsi_url, $callobj);
2335         print STDERR Dumper $res;
2336         return ();
2340 # ----------------------------------------------------------------------------
2341 #  internal methods handling the comunication with Opsi
2342 # ----------------------------------------------------------------------------
2344 ################################
2345 # @brief Checks if there is a specified tag and if the the tag has a content.
2346 sub _check_xml_tag_is_ok {
2347         my ($msg_hash,$tag) = @_;
2348         if (not defined $msg_hash->{$tag}) {
2349                 $_ = "message contains no tag '$tag'";
2350                 return 0;
2351         }
2352         if (ref @{$msg_hash->{$tag}}[0] eq 'HASH') {
2353                 $_ = "message tag '$tag' has no content";
2354                 return  0;
2355         }
2356         return 1;
2359 ################################
2360 # @brief Writes the log line and returns the error message for GOsa.
2361 sub _giveErrorFeedback {
2362         my ($msg_hash, $err_string, $session_id) = @_;
2363         &main::daemon_log("$session_id ERROR: $err_string", 1);
2364         my $out_hash = &main::create_xml_hash("error", $main::server_address, @{$msg_hash->{source}}[0], $err_string);
2365     if (exists $msg_hash->{forward_to_gosa}) {
2366         &add_content2xml_hash($out_hash, "forward_to_gosa", @{$msg_hash->{'forward_to_gosa'}}[0]);
2367     }
2368         return ( &create_xml_string($out_hash) );
2372 ################################
2373 # @brief Perform the call to the Opsi server and measure the time for the call
2374 sub _callOpsi {
2375         my %arg = ('method'=>undef, 'params'=>[], 'id'=>1, @_);
2377         my $callObject = {
2378                 method => $arg{method},
2379                 params => $arg{params},
2380                 id => $arg{id},
2381         };
2383         my $startTime = Time::HiRes::time;
2384         my $opsiResult = $opsi_client->call($opsi_url, $callObject);
2385         my $endTime = Time::HiRes::time;
2386         my $elapsedTime = sprintf("%.4f", ($endTime - $startTime));
2388         &main::daemon_log("0 DEBUG: time to process opsi call '$arg{method}' : $elapsedTime seconds", 1034); 
2390         return $opsiResult;
2393 sub _getLicensePool_hash {
2394         my %arg = ( 'licensePoolId' => undef, @_ );
2396         if (not defined $arg{licensePoolId} ) { 
2397                 return ("function requires licensePoolId as parameter", 1);
2398         }
2400         my $res = &_callOpsi( method  => 'getLicensePool_hash', params =>[$arg{licensePoolId}], id  => 1 );
2401         my ($res_error, $res_error_str) = &check_opsi_res($res);
2402         if ($res_error){ return ( (caller(0))[3]." : ".$res_error_str, 1 ); }
2404         return ($res->result, 0);
2407 sub _getSoftwareLicenses_listOfHashes {
2408         
2409         my $res = &_callOpsi( method  => 'getSoftwareLicenses_listOfHashes' );
2410         my ($res_error, $res_error_str) = &check_opsi_res($res);
2411         if ($res_error){ return ( (caller(0))[3]." : ".$res_error_str, 1 ); }
2413         return ($res->result, 0);
2416 sub _getSoftwareLicenseUsages_listOfHashes {
2417         my %arg = ( 'hostId' => "", 'licensePoolId' => "", @_ );
2419         my $res = &_callOpsi( method=>'getSoftwareLicenseUsages_listOfHashes', params=>[ $arg{hostId}, $arg{licensePoolId} ] );
2420         my ($res_error, $res_error_str) = &check_opsi_res($res);
2421         if ($res_error){ return ( (caller(0))[3]." : ".$res_error_str, 1 ); }
2423         return ($res->result, 0);
2426 sub _removeSoftwareLicenseFromLicensePool {
2427         my %arg = ( 'softwareLicenseId' => undef, 'licensePoolId' => undef, @_ );
2429         if (not defined $arg{softwareLicenseId} ) { 
2430                 return ("function requires softwareLicenseId as parameter", 1);
2431                 }
2432                 if (not defined $arg{licensePoolId} ) { 
2433                 return ("function requires licensePoolId as parameter", 1);
2434         }
2436         my $res = &_callOpsi( method=>'removeSoftwareLicenseFromLicensePool', params=>[ $arg{softwareLicenseId}, $arg{licensePoolId} ] );
2437         my ($res_error, $res_error_str) = &check_opsi_res($res);
2438         if ($res_error){ return ( (caller(0))[3]." : ".$res_error_str, 1 ); }
2440         return ($res->result, 0);
2443 sub _deleteSoftwareLicense {
2444         my %arg = ( 'softwareLicenseId' => undef, 'removeFromPools' => "false", @_ );
2446         if (not defined $arg{softwareLicenseId} ) { 
2447                 return ("function requires softwareLicenseId as parameter", 1);
2448         }
2449         my $removeFromPools = "";
2450         if ((defined $arg{removeFromPools}) && ($arg{removeFromPools} eq "true")) { 
2451                 $removeFromPools = "removeFromPools";
2452         }
2454         my $res = &_callOpsi( method=>'deleteSoftwareLicense', params=>[ $arg{softwareLicenseId}, $removeFromPools ] );
2455         my ($res_error, $res_error_str) = &check_opsi_res($res);
2456         if ($res_error){ return ( (caller(0))[3]." : ".$res_error_str, 1 ); }
2458         return ($res->result, 0);
2461 sub _getLicensePoolId {
2462         my %arg = ( 'productId' => undef, @_ );
2463         
2464         if (not defined $arg{productId} ) {
2465                 return ("function requires productId as parameter", 1);
2466         }
2468     my $res = &_callOpsi( method  => 'getLicensePoolId', params  => [ $arg{productId} ] );
2469         my ($res_error, $res_error_str) = &check_opsi_res($res);
2470         if ($res_error){ return ( (caller(0))[3]." : ".$res_error_str, 1 ); }
2472         return ($res->result, 0);
2475 sub _getLicenseContract_hash {
2476         my %arg = ( 'licenseContractId' => undef, @_ );
2477         
2478         if (not defined $arg{licenseContractId} ) {
2479                 return ("function requires licenseContractId as parameter", 1);
2480         }
2482     my $res = &_callOpsi( method  => 'getLicenseContract_hash', params  => [ $arg{licenseContractId} ] );
2483         my ($res_error, $res_error_str) = &check_opsi_res($res);
2484         if ($res_error){ return ( (caller(0))[3]." : ".$res_error_str, 1 ); }
2486         return ($res->result, 0);
2489 sub _createLicenseContract {
2490         my %arg = (
2491                         'licenseContractId' => undef,
2492                         'partner' => undef,
2493                         'conclusionDate' => undef,
2494                         'notificationDate' => undef,
2495                         'expirationDate' => undef,
2496                         'notes' => undef,
2497                         @_ );
2499         my $res = &_callOpsi( method  => 'createLicenseContract', 
2500                         params  => [ $arg{licenseContractId}, $arg{partner}, $arg{conclusionDate}, $arg{notificationDate}, $arg{expirationDate}, $arg{notes} ],
2501                         );
2502         my ($res_error, $res_error_str) = &check_opsi_res($res);
2503         if ($res_error){ return ( (caller(0))[3]." : ".$res_error_str, 1 ); }
2505         return ($res->result, 0);
2508 sub _createSoftwareLicense {
2509         my %arg = (
2510                         'softwareLicenseId' => undef,
2511                         'licenseContractId' => undef,
2512                         'licenseType' => undef,
2513                         'maxInstallations' => undef,
2514                         'boundToHost' => undef,
2515                         'expirationDate' => undef,
2516                         @_ );
2518     my $res = &_callOpsi( method  => 'createSoftwareLicense',
2519         params  => [ $arg{softwareLicenseId}, $arg{licenseContractId}, $arg{licenseType}, $arg{maxInstallations}, $arg{boundToHost}, $arg{expirationDate} ],
2520                 );
2521         my ($res_error, $res_error_str) = &check_opsi_res($res);
2522         if ($res_error){ return ( (caller(0))[3]." : ".$res_error_str, 1 ); }
2524         return ($res->result, 0);
2527 sub _addSoftwareLicenseToLicensePool {
2528         my %arg = (
2529             'softwareLicenseId' => undef,
2530             'licensePoolId' => undef,
2531             'licenseKey' => undef,
2532             @_ );
2534         if (not defined $arg{softwareLicenseId} ) {
2535                 return ("function requires softwareLicenseId as parameter", 1);
2536         }
2537         if (not defined $arg{licensePoolId} ) {
2538                 return ("function requires licensePoolId as parameter", 1);
2539         }
2541         my $res = &_callOpsi( method  => 'addSoftwareLicenseToLicensePool', params  => [ $arg{softwareLicenseId}, $arg{licensePoolId}, $arg{licenseKey} ] );
2542         my ($res_error, $res_error_str) = &check_opsi_res($res);
2543         if ($res_error){ return ( (caller(0))[3]." : ".$res_error_str, 1 ); }
2545         return ($res->result, 0);
2548 sub _getProductStates_hash {
2549         my %arg = (     'hostId' => undef, @_ );
2551         if (not defined $arg{hostId} ) {
2552                 return ("function requires hostId as parameter", 1);
2553         }
2555         my $res = &_callOpsi( method => 'getProductStates_hash', params => [$arg{hostId}]);
2556         my ($res_error, $res_error_str) = &check_opsi_res($res);
2557         if ($res_error){ return ( (caller(0))[3]." : ".$res_error_str, 1 ); }
2559         return ($res->result, 0);
2562 1;