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 );
25 @EXPORT = @events;
27 use strict;
28 use warnings;
29 use GOSA::GosaSupportDaemon;
30 use Data::Dumper;
31 use XML::Quote qw(:all);
34 BEGIN {}
36 END {}
38 ## @method get_events()
39 # A brief function returning a list of functions which are exported by importing the module.
40 # @return List of all provided functions
41 sub get_events {
42 return \@events;
43 }
45 ## @method opsi_add_product_to_client
46 # Adds an Opsi product to an Opsi client.
47 # @param msg - STRING - xml message with tags hostId and productId
48 # @param msg_hash - HASHREF - message information parsed into a hash
49 # @param session_id - INTEGER - POE session id of the processing of this message
50 # @return out_msg - STRING - feedback to GOsa in success and error case
51 sub opsi_add_product_to_client {
52 my ($msg, $msg_hash, $session_id) = @_;
53 my $header = @{$msg_hash->{'header'}}[0];
54 my $source = @{$msg_hash->{'source'}}[0];
55 my $target = @{$msg_hash->{'target'}}[0];
56 my $forward_to_gosa = @{$msg_hash->{'forward_to_gosa'}}[0];
57 my ($hostId, $productId);
58 my $error = 0;
60 # Build return message
61 my $out_hash = &main::create_xml_hash("answer_$header", $main::server_address, $source);
62 if (defined $forward_to_gosa) {
63 &add_content2xml_hash($out_hash, "forward_to_gosa", $forward_to_gosa);
64 }
66 # Sanity check of needed parameter
67 if ((not exists $msg_hash->{'hostId'}) || (@{$msg_hash->{'hostId'}} != 1)) {
68 $error++;
69 &add_content2xml_hash($out_hash, "hostId_error", "no hostId specified or hostId tag invalid");
70 &add_content2xml_hash($out_hash, "error", "hostId");
71 &main::daemon_log("$session_id ERROR: no hostId specified or hostId tag invalid: $msg", 1);
73 }
74 if ((not exists $msg_hash->{'productId'}) || (@{$msg_hash->{'productId'}} != 1)) {
75 $error++;
76 &add_content2xml_hash($out_hash, "productId_error", "no productId specified or productId tag invalid");
77 &add_content2xml_hash($out_hash, "error", "productId");
78 &main::daemon_log("$session_id ERROR: no productId specified or procutId tag invalid: $msg", 1);
79 }
81 if (not $error) {
82 # Get hostID
83 $hostId = @{$msg_hash->{'hostId'}}[0];
84 &add_content2xml_hash($out_hash, "hostId", $hostId);
86 # Get productID
87 $productId = @{$msg_hash->{'productId'}}[0];
88 &add_content2xml_hash($out_hash, "productId", $productId);
90 # Do an action request for all these -> "setup".
91 my $callobj = {
92 method => 'setProductActionRequest',
93 params => [ $productId, $hostId, "setup" ],
94 id => 1, };
96 my $sres = $main::opsi_client->call($main::opsi_url, $callobj);
97 my ($sres_err, $sres_err_string) = &check_opsi_res($sres);
98 if ($sres_err){
99 &main::daemon_log("$session_id ERROR: cannot add product: ".$sres_err_string, 1);
100 &add_content2xml_hash($out_hash, "error", $sres_err_string);
101 }
102 }
104 # return message
105 return ( &create_xml_string($out_hash) );
106 }
108 ## @method opsi_del_product_from_client
109 # Deletes an Opsi-product from an Opsi-client.
110 # @param msg - STRING - xml message with tags hostId and productId
111 # @param msg_hash - HASHREF - message information parsed into a hash
112 # @param session_id - INTEGER - POE session id of the processing of this message
113 # @return out_msg - STRING - feedback to GOsa in success and error case
114 sub opsi_del_product_from_client {
115 my ($msg, $msg_hash, $session_id) = @_;
116 my $header = @{$msg_hash->{'header'}}[0];
117 my $source = @{$msg_hash->{'source'}}[0];
118 my $target = @{$msg_hash->{'target'}}[0];
119 my $forward_to_gosa = @{$msg_hash->{'forward_to_gosa'}}[0];
120 my ($hostId, $productId);
121 my $error = 0;
122 my ($sres, $sres_err, $sres_err_string);
124 # Build return message
125 my $out_hash = &main::create_xml_hash("answer_$header", $main::server_address, $source);
126 if (defined $forward_to_gosa) {
127 &add_content2xml_hash($out_hash, "forward_to_gosa", $forward_to_gosa);
128 }
130 # Sanity check of needed parameter
131 if ((not exists $msg_hash->{'hostId'}) || (@{$msg_hash->{'hostId'}} != 1)) {
132 $error++;
133 &add_content2xml_hash($out_hash, "hostId_error", "no hostId specified or hostId tag invalid");
134 &add_content2xml_hash($out_hash, "error", "hostId");
135 &main::daemon_log("$session_id ERROR: no hostId specified or hostId tag invalid: $msg", 1);
137 }
138 if ((not exists $msg_hash->{'productId'}) || (@{$msg_hash->{'productId'}} != 1)) {
139 $error++;
140 &add_content2xml_hash($out_hash, "productId_error", "no productId specified or productId tag invalid");
141 &add_content2xml_hash($out_hash, "error", "productId");
142 &main::daemon_log("$session_id ERROR: no productId specified or procutId tag invalid: $msg", 1);
143 }
145 # All parameter available
146 if (not $error) {
147 # Get hostID
148 $hostId = @{$msg_hash->{'hostId'}}[0];
149 &add_content2xml_hash($out_hash, "hostId", $hostId);
151 # Get productID
152 $productId = @{$msg_hash->{'productId'}}[0];
153 &add_content2xml_hash($out_hash, "productId", $productId);
156 #TODO: check the results for more than one entry which is currently installed
157 #$callobj = {
158 # method => 'getProductDependencies_listOfHashes',
159 # params => [ $productId ],
160 # id => 1, };
161 #
162 #my $sres = $main::opsi_client->call($main::opsi_url, $callobj);
163 #my ($sres_err, $sres_err_string) = &check_opsi_res($sres);
164 #if ($sres_err){
165 # &main::daemon_log("ERROR: cannot perform dependency check: ".$sres_err_string, 1);
166 # &add_content2xml_hash($out_hash, "error", $sres_err_string);
167 # return ( &create_xml_string($out_hash) );
168 #}
171 # Check to get product action list
172 my $callobj = {
173 method => 'getPossibleProductActions_list',
174 params => [ $productId ],
175 id => 1, };
176 $sres = $main::opsi_client->call($main::opsi_url, $callobj);
177 ($sres_err, $sres_err_string) = &check_opsi_res($sres);
178 if ($sres_err){
179 &main::daemon_log("$session_id ERROR: cannot get product action list: ".$sres_err_string, 1);
180 &add_content2xml_hash($out_hash, "error", $sres_err_string);
181 $error++;
182 }
183 }
185 # Check action uninstall of product
186 if (not $error) {
187 my $uninst_possible= 0;
188 foreach my $r (@{$sres->result}) {
189 if ($r eq 'uninstall') {
190 $uninst_possible= 1;
191 }
192 }
193 if (!$uninst_possible){
194 &main::daemon_log("$session_id ERROR: cannot uninstall product '$productId', product do not has the action 'uninstall'", 1);
195 &add_content2xml_hash($out_hash, "error", "cannot uninstall product '$productId', product do not has the action 'uninstall'");
196 $error++;
197 }
198 }
200 # Set product state to "none"
201 # Do an action request for all these -> "setup".
202 if (not $error) {
203 my $callobj = {
204 method => 'setProductActionRequest',
205 params => [ $productId, $hostId, "none" ],
206 id => 1,
207 };
208 $sres = $main::opsi_client->call($main::opsi_url, $callobj);
209 ($sres_err, $sres_err_string) = &check_opsi_res($sres);
210 if ($sres_err){
211 &main::daemon_log("$session_id ERROR: cannot delete product: ".$sres_err_string, 1);
212 &add_content2xml_hash($out_hash, "error", $sres_err_string);
213 }
214 }
216 # Return message
217 return ( &create_xml_string($out_hash) );
218 }
220 ## @method opsi_add_client
221 # Adds an Opsi client to Opsi.
222 # @param msg - STRING - xml message with tags hostId and macaddress
223 # @param msg_hash - HASHREF - message information parsed into a hash
224 # @param session_id - INTEGER - POE session id of the processing of this message
225 # @return out_msg - STRING - feedback to GOsa in success and error case
226 sub opsi_add_client {
227 my ($msg, $msg_hash, $session_id) = @_;
228 my $header = @{$msg_hash->{'header'}}[0];
229 my $source = @{$msg_hash->{'source'}}[0];
230 my $target = @{$msg_hash->{'target'}}[0];
231 my $forward_to_gosa = @{$msg_hash->{'forward_to_gosa'}}[0];
232 my ($hostId, $mac);
233 my $error = 0;
234 my ($sres, $sres_err, $sres_err_string);
236 # build return message with twisted target and source
237 my $out_hash = &main::create_xml_hash("answer_$header", $main::server_address, $source);
238 if (defined $forward_to_gosa) {
239 &add_content2xml_hash($out_hash, "forward_to_gosa", $forward_to_gosa);
240 }
242 # Sanity check of needed parameter
243 if ((not exists $msg_hash->{'hostId'}) || (@{$msg_hash->{'hostId'}} != 1)) {
244 $error++;
245 &add_content2xml_hash($out_hash, "hostId_error", "no hostId specified or hostId tag invalid");
246 &add_content2xml_hash($out_hash, "error", "hostId");
247 &main::daemon_log("$session_id ERROR: no hostId specified or hostId tag invalid: $msg", 1);
248 }
249 if ((not exists $msg_hash->{'macaddress'}) || (@{$msg_hash->{'macaddress'}} != 1)) {
250 $error++;
251 &add_content2xml_hash($out_hash, "macaddress_error", "no macaddress specified or macaddress tag invalid");
252 &add_content2xml_hash($out_hash, "error", "macaddress");
253 &main::daemon_log("$session_id ERROR: no macaddress specified or macaddress tag invalid: $msg", 1);
254 }
256 if (not $error) {
257 # Get hostID
258 $hostId = @{$msg_hash->{'hostId'}}[0];
259 &add_content2xml_hash($out_hash, "hostId", $hostId);
261 # Get macaddress
262 $mac = @{$msg_hash->{'macaddress'}}[0];
263 &add_content2xml_hash($out_hash, "macaddress", $mac);
265 my $name= $hostId;
266 $name=~ s/^([^.]+).*$/$1/;
267 my $domain= $hostId;
268 $domain=~ s/^[^.]+\.(.*)$/$1/;
269 my ($description, $notes, $ip);
271 if (defined @{$msg_hash->{'description'}}[0]){
272 $description = @{$msg_hash->{'description'}}[0];
273 }
274 if (defined @{$msg_hash->{'notes'}}[0]){
275 $notes = @{$msg_hash->{'notes'}}[0];
276 }
277 if (defined @{$msg_hash->{'ip'}}[0]){
278 $ip = @{$msg_hash->{'ip'}}[0];
279 }
281 my $callobj;
282 $callobj = {
283 method => 'createClient',
284 params => [ $name, $domain, $description, $notes, $ip, $mac ],
285 id => 1,
286 };
288 $sres = $main::opsi_client->call($main::opsi_url, $callobj);
289 ($sres_err, $sres_err_string) = &check_opsi_res($sres);
290 if ($sres_err){
291 &main::daemon_log("$session_id ERROR: cannot create client: ".$sres_err_string, 1);
292 &add_content2xml_hash($out_hash, "error", $sres_err_string);
293 }
294 }
296 # Return message
297 return ( &create_xml_string($out_hash) );
298 }
300 ## @method opsi_modify_client
301 # Modifies the parameters description, mac or notes for an Opsi client if the corresponding message tags are given.
302 # @param msg - STRING - xml message with tag hostId and optional description, mac or notes
303 # @param msg_hash - HASHREF - message information parsed into a hash
304 # @param session_id - INTEGER - POE session id of the processing of this message
305 # @return out_msg - STRING - feedback to GOsa in success and error case
306 sub opsi_modify_client {
307 my ($msg, $msg_hash, $session_id) = @_;
308 my $header = @{$msg_hash->{'header'}}[0];
309 my $source = @{$msg_hash->{'source'}}[0];
310 my $target = @{$msg_hash->{'target'}}[0];
311 my $forward_to_gosa = @{$msg_hash->{'forward_to_gosa'}}[0];
312 my $hostId;
313 my $error = 0;
314 my ($sres, $sres_err, $sres_err_string);
316 # Build return message with twisted target and source
317 my $out_hash = &main::create_xml_hash("answer_$header", $main::server_address, $source);
318 if (defined $forward_to_gosa) {
319 &add_content2xml_hash($out_hash, "forward_to_gosa", $forward_to_gosa);
320 }
322 # Sanity check of needed parameter
323 if ((not exists $msg_hash->{'hostId'}) || (@{$msg_hash->{'hostId'}} != 1)) {
324 $error++;
325 &add_content2xml_hash($out_hash, "hostId_error", "no hostId specified or hostId tag invalid");
326 &add_content2xml_hash($out_hash, "error", "hostId");
327 &main::daemon_log("$session_id ERROR: no hostId specified or hostId tag invalid: $msg", 1);
328 }
330 if (not $error) {
331 # Get hostID
332 $hostId = @{$msg_hash->{'hostId'}}[0];
333 &add_content2xml_hash($out_hash, "hostId", $hostId);
334 my $name= $hostId;
335 $name=~ s/^([^.]+).*$/$1/;
336 my $domain= $hostId;
337 $domain=~ s/^[^.]+(.*)$/$1/;
339 # Modify description, notes or mac if defined
340 my ($description, $notes, $mac);
341 my $callobj;
342 if ((exists $msg_hash->{'description'}) && (@{$msg_hash->{'description'}} == 1) ){
343 $description = @{$msg_hash->{'description'}}[0];
344 $callobj = {
345 method => 'setHostDescription',
346 params => [ $hostId, $description ],
347 id => 1,
348 };
349 my $sres = $main::opsi_client->call($main::opsi_url, $callobj);
350 my ($sres_err, $sres_err_string) = &check_opsi_res($sres);
351 if ($sres_err){
352 &main::daemon_log("ERROR: cannot set description: ".$sres_err_string, 1);
353 &add_content2xml_hash($out_hash, "error", $sres_err_string);
354 }
355 }
356 if ((exists $msg_hash->{'notes'}) && (@{$msg_hash->{'notes'}} == 1)) {
357 $notes = @{$msg_hash->{'notes'}}[0];
358 $callobj = {
359 method => 'setHostNotes',
360 params => [ $hostId, $notes ],
361 id => 1,
362 };
363 my $sres = $main::opsi_client->call($main::opsi_url, $callobj);
364 my ($sres_err, $sres_err_string) = &check_opsi_res($sres);
365 if ($sres_err){
366 &main::daemon_log("ERROR: cannot set notes: ".$sres_err_string, 1);
367 &add_content2xml_hash($out_hash, "error", $sres_err_string);
368 }
369 }
370 if ((exists $msg_hash->{'mac'}) && (@{$msg_hash->{'mac'}} == 1)){
371 $mac = @{$msg_hash->{'mac'}}[0];
372 $callobj = {
373 method => 'setMacAddress',
374 params => [ $hostId, $mac ],
375 id => 1,
376 };
377 my $sres = $main::opsi_client->call($main::opsi_url, $callobj);
378 my ($sres_err, $sres_err_string) = &check_opsi_res($sres);
379 if ($sres_err){
380 &main::daemon_log("ERROR: cannot set mac address: ".$sres_err_string, 1);
381 &add_content2xml_hash($out_hash, "error", $sres_err_string);
382 }
383 }
384 }
386 # Return message
387 return ( &create_xml_string($out_hash) );
388 }
391 ## @method opsi_get_netboot_products
392 # Get netboot products for specific host.
393 # @param msg - STRING - xml message with tag hostId
394 # @param msg_hash - HASHREF - message information parsed into a hash
395 # @param session_id - INTEGER - POE session id of the processing of this message
396 # @return out_msg - STRING - feedback to GOsa in success and error case
397 sub opsi_get_netboot_products {
398 my ($msg, $msg_hash, $session_id) = @_;
399 my $header = @{$msg_hash->{'header'}}[0];
400 my $source = @{$msg_hash->{'source'}}[0];
401 my $target = @{$msg_hash->{'target'}}[0];
402 my $forward_to_gosa = @{$msg_hash->{'forward_to_gosa'}}[0];
403 my $hostId;
404 my $error = 0;
405 my $xml_msg;
407 # Build return message with twisted target and source
408 my $out_hash = &main::create_xml_hash("answer_$header", $main::server_address, $source);
409 if (defined $forward_to_gosa) {
410 &add_content2xml_hash($out_hash, "forward_to_gosa", $forward_to_gosa);
411 }
413 # Sanity check of needed parameter
414 if ((not exists $msg_hash->{'hostId'}) || (@{$msg_hash->{'hostId'}} != 1)) {
415 $error++;
416 &add_content2xml_hash($out_hash, "hostId_error", "no hostId specified or hostId tag invalid");
417 &add_content2xml_hash($out_hash, "error", "hostId");
418 &main::daemon_log("$session_id ERROR: no hostId specified or hostId tag invalid: $msg", 1);
419 }
421 if (not $error) {
423 # Get hostID if defined
424 $hostId = @{$msg_hash->{'hostId'}}[0];
425 &add_content2xml_hash($out_hash, "hostId", $hostId);
427 &add_content2xml_hash($out_hash, "xxx", "");
428 $xml_msg= &create_xml_string($out_hash);
430 # For hosts, only return the products that are or get installed
431 my $callobj;
432 $callobj = {
433 method => 'getNetBootProductIds_list',
434 params => [ ],
435 id => 1,
436 };
438 my $res = $main::opsi_client->call($main::opsi_url, $callobj);
439 my %r = ();
440 for (@{$res->result}) { $r{$_} = 1 }
442 if (not &check_opsi_res($res)){
444 if (defined $hostId){
445 $callobj = {
446 method => 'getProductStates_hash',
447 params => [ $hostId ],
448 id => 1,
449 };
451 my $hres = $main::opsi_client->call($main::opsi_url, $callobj);
452 if (not &check_opsi_res($hres)){
453 my $htmp= $hres->result->{$hostId};
455 # check state != not_installed or action == setup -> load and add
456 foreach my $product (@{$htmp}){
458 if (!defined ($r{$product->{'productId'}})){
459 next;
460 }
462 # Now we've a couple of hashes...
463 if ($product->{'installationStatus'} ne "not_installed" or
464 $product->{'actionRequest'} eq "setup"){
465 my $state= "<state>".$product->{'installationStatus'}."</state><action>".$product->{'actionRequest'}."</action>";
467 $callobj = {
468 method => 'getProduct_hash',
469 params => [ $product->{'productId'} ],
470 id => 1,
471 };
473 my $sres = $main::opsi_client->call($main::opsi_url, $callobj);
474 if (not &check_opsi_res($sres)){
475 my $tres= $sres->result;
477 my $name= xml_quote($tres->{'name'});
478 my $r= $product->{'productId'};
479 my $description= xml_quote($tres->{'description'});
480 $name=~ s/\//\\\//;
481 $description=~ s/\//\\\//;
482 $xml_msg=~ s/<xxx><\/xxx>/<item><productId>$r<\/productId><name><\/name><description>$description<\/description><\/item>$state<xxx><\/xxx>/;
483 }
484 }
485 }
487 }
489 } else {
490 foreach my $r (@{$res->result}) {
491 $callobj = {
492 method => 'getProduct_hash',
493 params => [ $r ],
494 id => 1,
495 };
497 my $sres = $main::opsi_client->call($main::opsi_url, $callobj);
498 if (not &check_opsi_res($sres)){
499 my $tres= $sres->result;
501 my $name= xml_quote($tres->{'name'});
502 my $description= xml_quote($tres->{'description'});
503 $name=~ s/\//\\\//;
504 $description=~ s/\//\\\//;
505 $xml_msg=~ s/<xxx><\/xxx>/<item><productId>$r<\/productId><name><\/name><description>$description<\/description><\/item><xxx><\/xxx>/;
506 }
507 }
509 }
510 }
511 $xml_msg=~ s/<xxx><\/xxx>//;
512 }
514 # Return message
515 return ( $xml_msg );
516 }
519 ## @method opsi_get_product_properties
520 # Get product properties for a product and a specific host or gobally for a product.
521 # @param msg - STRING - xml message with tags productId and optional hostId
522 # @param msg_hash - HASHREF - message information parsed into a hash
523 # @param session_id - INTEGER - POE session id of the processing of this message
524 # @return out_msg - STRING - feedback to GOsa in success and error case
525 sub opsi_get_product_properties {
526 my ($msg, $msg_hash, $session_id) = @_;
527 my $header = @{$msg_hash->{'header'}}[0];
528 my $source = @{$msg_hash->{'source'}}[0];
529 my $target = @{$msg_hash->{'target'}}[0];
530 my $forward_to_gosa = @{$msg_hash->{'forward_to_gosa'}}[0];
531 my ($hostId, $productId);
532 my $error = 0;
533 my $xml_msg;
535 # build return message with twisted target and source
536 my $out_hash = &main::create_xml_hash("answer_$header", $main::server_address, $source);
537 if (defined $forward_to_gosa) {
538 &add_content2xml_hash($out_hash, "forward_to_gosa", $forward_to_gosa);
539 }
541 # Sanity check of needed parameter
542 if ((not exists $msg_hash->{'productId'}) || (@{$msg_hash->{'productId'}} != 1)) {
543 $error++;
544 &add_content2xml_hash($out_hash, "productId_error", "no productId specified or productId tag invalid");
545 &add_content2xml_hash($out_hash, "error", "productId");
546 &main::daemon_log("$session_id ERROR: no productId specified or productId tag invalid: $msg", 1);
547 }
549 if (not $error) {
551 # Get productid
552 $productId = @{$msg_hash->{'productId'}}[0];
553 &add_content2xml_hash($out_hash, "producId", "$productId");
556 $hostId = @{$msg_hash->{'hostId'}}[0];
557 &add_content2xml_hash($out_hash, "hostId", $hostId);
559 # Load actions
560 my $callobj = {
561 method => 'getPossibleProductActions_list',
562 params => [ $productId ],
563 id => 1,
564 };
565 my $res = $main::opsi_client->call($main::opsi_url, $callobj);
566 if (not &check_opsi_res($res)){
567 foreach my $action (@{$res->result}){
568 &add_content2xml_hash($out_hash, "action", $action);
569 }
570 }
572 # Add place holder
573 &add_content2xml_hash($out_hash, "xxx", "");
575 }
577 # Move to XML string
578 $xml_msg= &create_xml_string($out_hash);
580 if (not $error) {
582 # JSON Query
583 my $callobj = {
584 method => 'getProductProperties_hash',
585 params => [ $productId ],
586 id => 1,
587 };
588 my $res = $main::opsi_client->call($main::opsi_url, $callobj);
589 if (not &check_opsi_res($res)){
590 my $r= $res->result;
591 foreach my $key (keys %{$r}) {
592 my $item= "<item>";
593 my $value= $r->{$key};
594 if (UNIVERSAL::isa( $value, "ARRAY" )){
595 foreach my $subval (@{$value}){
596 $item.= "<$key>".xml_quote($subval)."</$key>";
597 }
598 } else {
599 $item.= "<$key>".xml_quote($value)."</$key>";
600 }
601 $item.= "</item>";
602 $xml_msg=~ s/<xxx><\/xxx>/$item<xxx><\/xxx>/;
603 }
604 }
606 $xml_msg=~ s/<xxx><\/xxx>//;
607 }
609 # Return message
610 return ( $xml_msg );
611 }
614 ## @method opsi_set_product_properties
615 # 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.
616 # @param msg - STRING - xml message with tags productId, action, state and optional hostId, action and state
617 # @param msg_hash - HASHREF - message information parsed into a hash
618 # @param session_id - INTEGER - POE session id of the processing of this message
619 # @return out_msg - STRING - feedback to GOsa in success and error case
620 sub opsi_set_product_properties {
621 my ($msg, $msg_hash, $session_id) = @_;
622 my $header = @{$msg_hash->{'header'}}[0];
623 my $source = @{$msg_hash->{'source'}}[0];
624 my $target = @{$msg_hash->{'target'}}[0];
625 my $forward_to_gosa = @{$msg_hash->{'forward_to_gosa'}}[0];
626 my ($productId, $hostId);
627 my $error = 0;
629 # Build return message with twisted target and source
630 my $out_hash = &main::create_xml_hash("answer_$header", $main::server_address, $source);
631 if (defined $forward_to_gosa) {
632 &add_content2xml_hash($out_hash, "forward_to_gosa", $forward_to_gosa);
633 }
635 # Sanity check of needed parameter
636 if ((not exists $msg_hash->{'productId'}) || (@{$msg_hash->{'productId'}} != 1)) {
637 $error++;
638 &add_content2xml_hash($out_hash, "productId_error", "no productId specified or productId tag invalid");
639 &add_content2xml_hash($out_hash, "error", "productId");
640 &main::daemon_log("$session_id ERROR: no productId specified or productId tag invalid: $msg", 1);
641 }
642 if ((not exists $msg_hash->{'item'}) || (@{$msg_hash->{'item'}}[0] != 1)) {
643 $error++;
644 &add_content2xml_hash($out_hash, "item_error", "message needs one xml-tag 'item' and within the xml-tags 'name' and 'value'");
645 &add_content2xml_hash($out_hash, "error", "item");
646 &main::daemon_log("$session_id ERROR: message needs one xml-tag 'item' and within the xml-tags 'name' and 'value': $msg", 1);
647 } else {
648 if ((not exists $msg_hash->{'item'}->{'name'}) || (@{$msg_hash->{'item'}->{'name'}}[0] != 1)) {
649 $error++;
650 &add_content2xml_hash($out_hash, "name_error", "message needs within the xml-tag 'item' one xml-tags 'name'");
651 &add_content2xml_hash($out_hash, "error", "name");
652 &main::daemon_log("$session_id ERROR: message needs within the xml-tag 'item' one xml-tags 'name': $msg", 1);
653 }
654 if ((not exists $msg_hash->{'item'}->{'value'}) || (@{$msg_hash->{'item'}->{'value'}}[0] != 1)) {
655 $error++;
656 &add_content2xml_hash($out_hash, "value_error", "message needs within the xml-tag 'item' one xml-tags 'value'");
657 &add_content2xml_hash($out_hash, "error", "value");
658 &main::daemon_log("$session_id ERROR: message needs within the xml-tag 'item' one xml-tags 'value': $msg", 1);
659 }
660 }
661 if ((exists $msg_hash->{'hostId'}) && (@{$msg_hash->{'hostId'}} != 1)) {
662 $error++;
663 &add_content2xml_hash($out_hash, "hostId_error", "hostId contains no or more than one values");
664 &add_content2xml_hash($out_hash, "error", "hostId");
665 &main::daemon_log("$session_id ERROR: hostId contains no or more than one values: $msg", 1);
666 }
668 if (not $error) {
670 # Get productId
671 $productId = @{$msg_hash->{'productId'}}[0];
672 &add_content2xml_hash($out_hash, "productId", $productId);
674 # Get hostID if defined
675 if (exists $msg_hash->{'hostId'}){
676 $hostId = @{$msg_hash->{'hostId'}}[0];
677 &add_content2xml_hash($out_hash, "hostId", $hostId);
678 }
680 # Set product states if requested
681 if (defined @{$msg_hash->{'action'}}[0]){
682 &_set_action($productId, @{$msg_hash->{'action'}}[0], $hostId);
683 }
684 if (defined @{$msg_hash->{'state'}}[0]){
685 &_set_state($productId, @{$msg_hash->{'state'}}[0], $hostId);
686 }
688 # Find properties
689 foreach my $item (@{$msg_hash->{'item'}}){
690 # JSON Query
691 my $callobj;
693 if (defined $hostId){
694 $callobj = {
695 method => 'setProductProperty',
696 params => [ $productId, $item->{'name'}[0], $item->{'value'}[0], $hostId ],
697 id => 1,
698 };
699 } else {
700 $callobj = {
701 method => 'setProductProperty',
702 params => [ $productId, $item->{'name'}[0], $item->{'value'}[0] ],
703 id => 1,
704 };
705 }
707 my $res = $main::opsi_client->call($main::opsi_url, $callobj);
708 my ($res_err, $res_err_string) = &check_opsi_res($res);
709 # TODO : This error message sounds strange
710 if ($res_err){
711 &man::daemon_log("$session_id ERROR: no communication failed while setting '".$item->{'name'}[0]."': ".$res_err_string, 1);
712 &add_content2xml_hash($out_hash, "error", $res_err_string);
713 }
714 }
716 }
718 # Return message
719 return ( &create_xml_string($out_hash) );
720 }
723 ## @method opsi_get_client_hardware
724 # Reports client hardware inventory.
725 # @param msg - STRING - xml message with tag hostId
726 # @param msg_hash - HASHREF - message information parsed into a hash
727 # @param session_id - INTEGER - POE session id of the processing of this message
728 # @return out_msg - STRING - feedback to GOsa in success and error case
729 sub opsi_get_client_hardware {
730 my ($msg, $msg_hash, $session_id) = @_;
731 my $header = @{$msg_hash->{'header'}}[0];
732 my $source = @{$msg_hash->{'source'}}[0];
733 my $target = @{$msg_hash->{'target'}}[0];
734 my $forward_to_gosa = @{$msg_hash->{'forward_to_gosa'}}[0];
735 my $hostId;
736 my $error = 0;
737 my $xml_msg;
739 # build return message with twisted target and source
740 my $out_hash = &main::create_xml_hash("answer_$header", $main::server_address, $source);
741 if (defined $forward_to_gosa) {
742 &add_content2xml_hash($out_hash, "forward_to_gosa", $forward_to_gosa);
743 }
745 # Sanity check of needed parameter
746 if ((exists $msg_hash->{'hostId'}) && (@{$msg_hash->{'hostId'}} != 1)) {
747 $error++;
748 &add_content2xml_hash($out_hash, "hostId_error", "hostId contains no or more than one values");
749 &add_content2xml_hash($out_hash, "error", "hostId");
750 &main::daemon_log("$session_id ERROR: hostId contains no or more than one values: $msg", 1);
751 }
753 if (not $error) {
755 # Get hostID
756 $hostId = @{$msg_hash->{'hostId'}}[0];
757 &add_content2xml_hash($out_hash, "hostId", "$hostId");
758 &add_content2xml_hash($out_hash, "xxx", "");
759 }
761 # Move to XML string
762 $xml_msg= &create_xml_string($out_hash);
764 if (not $error) {
766 # JSON Query
767 my $callobj = {
768 method => 'getHardwareInformation_hash',
769 params => [ $hostId ],
770 id => 1,
771 };
773 my $res = $main::opsi_client->call($main::opsi_url, $callobj);
774 if (not &check_opsi_res($res)){
775 my $result= $res->result;
776 foreach my $r (keys %{$result}){
777 my $item= "<item><id>".xml_quote($r)."</id>";
778 my $value= $result->{$r};
779 foreach my $sres (@{$value}){
781 foreach my $dres (keys %{$sres}){
782 if (defined $sres->{$dres}){
783 $item.= "<$dres>".xml_quote($sres->{$dres})."</$dres>";
784 }
785 }
787 }
788 $item.= "</item>";
789 $xml_msg=~ s%<xxx></xxx>%$item<xxx></xxx>%;
791 }
792 }
794 $xml_msg=~ s/<xxx><\/xxx>//;
796 }
798 # Return message
799 return ( $xml_msg );
800 }
803 ## @method opsi_list_clients
804 # Reports all Opsi clients.
805 # @param msg - STRING - xml message
806 # @param msg_hash - HASHREF - message information parsed into a hash
807 # @param session_id - INTEGER - POE session id of the processing of this message
808 # @return out_msg - STRING - feedback to GOsa in success and error case
809 sub opsi_list_clients {
810 my ($msg, $msg_hash, $session_id) = @_;
811 my $header = @{$msg_hash->{'header'}}[0];
812 my $source = @{$msg_hash->{'source'}}[0];
813 my $target = @{$msg_hash->{'target'}}[0];
814 my $forward_to_gosa = @{$msg_hash->{'forward_to_gosa'}}[0];
815 my $error = 0;
817 # build return message with twisted target and source
818 my $out_hash = &main::create_xml_hash("answer_$header", $main::server_address, $source);
819 if (defined $forward_to_gosa) {
820 &add_content2xml_hash($out_hash, "forward_to_gosa", $forward_to_gosa);
821 }
822 &add_content2xml_hash($out_hash, "xxx", "");
824 # Move to XML string
825 my $xml_msg= &create_xml_string($out_hash);
827 if (not $error) {
829 # JSON Query
830 my $callobj = {
831 method => 'getClients_listOfHashes',
832 params => [ ],
833 id => 1,
834 };
835 my $res = $main::opsi_client->call($main::opsi_url, $callobj);
836 if (not &check_opsi_res($res)){
837 foreach my $host (@{$res->result}){
838 my $item= "<item><name>".$host->{'hostId'}."</name>";
839 if (defined($host->{'description'})){
840 $item.= "<description>".xml_quote($host->{'description'})."</description>";
841 }
842 if (defined($host->{'ip'})){
843 $item.= "<ip>".xml_quote($host->{'ip'})."</ip>";
844 }
845 if (defined($host->{'mac'})){
846 $item.= "<mac>".xml_quote($host->{'mac'})."</mac>";
847 }
848 if (defined($host->{'notes'})){
849 $item.= "<notes>".xml_quote($host->{'notes'})."</notes>";
850 }
851 $item.= "</item>";
852 $xml_msg=~ s%<xxx></xxx>%$item<xxx></xxx>%;
853 }
854 }
855 }
857 $xml_msg=~ s/<xxx><\/xxx>//;
858 return ( $xml_msg );
859 }
862 ## @method opsi_get_client_software
863 # Reports client software inventory.
864 # @param msg - STRING - xml message with tag hostId
865 # @param msg_hash - HASHREF - message information parsed into a hash
866 # @param session_id - INTEGER - POE session id of the processing of this message
867 # @return out_msg - STRING - feedback to GOsa in success and error case
868 sub opsi_get_client_software {
869 my ($msg, $msg_hash, $session_id) = @_;
870 my $header = @{$msg_hash->{'header'}}[0];
871 my $source = @{$msg_hash->{'source'}}[0];
872 my $target = @{$msg_hash->{'target'}}[0];
873 my $forward_to_gosa = @{$msg_hash->{'forward_to_gosa'}}[0];
874 my $error = 0;
875 my $hostId;
876 my $xml_msg;
878 # build return message with twisted target and source
879 my $out_hash = &main::create_xml_hash("answer_$header", $main::server_address, $source);
880 if (defined $forward_to_gosa) {
881 &add_content2xml_hash($out_hash, "forward_to_gosa", $forward_to_gosa);
882 }
884 # Sanity check of needed parameter
885 if ((exists $msg_hash->{'hostId'}) && (@{$msg_hash->{'hostId'}} != 1)) {
886 $error++;
887 &add_content2xml_hash($out_hash, "hostId_error", "hostId contains no or more than one values");
888 &add_content2xml_hash($out_hash, "error", "hostId");
889 &main::daemon_log("$session_id ERROR: hostId contains no or more than one values: $msg", 1);
890 }
892 if (not $error) {
894 # Get hostID
895 $hostId = @{$msg_hash->{'hostId'}}[0];
896 &add_content2xml_hash($out_hash, "hostId", "$hostId");
897 &add_content2xml_hash($out_hash, "xxx", "");
898 }
900 $xml_msg= &create_xml_string($out_hash);
902 if (not $error) {
904 # JSON Query
905 my $callobj = {
906 method => 'getSoftwareInformation_hash',
907 params => [ $hostId ],
908 id => 1,
909 };
911 my $res = $main::opsi_client->call($main::opsi_url, $callobj);
912 if (not &check_opsi_res($res)){
913 my $result= $res->result;
914 # TODO : fertig???
915 }
917 $xml_msg=~ s/<xxx><\/xxx>//;
919 }
921 # Return message
922 return ( $xml_msg );
923 }
926 ## @method opsi_get_local_products
927 # Reports product for given hostId or globally.
928 # @param msg - STRING - xml message with optional tag hostId
929 # @param msg_hash - HASHREF - message information parsed into a hash
930 # @param session_id - INTEGER - POE session id of the processing of this message
931 # @return out_msg - STRING - feedback to GOsa in success and error case
932 sub opsi_get_local_products {
933 my ($msg, $msg_hash, $session_id) = @_;
934 my $header = @{$msg_hash->{'header'}}[0];
935 my $source = @{$msg_hash->{'source'}}[0];
936 my $target = @{$msg_hash->{'target'}}[0];
937 my $forward_to_gosa = @{$msg_hash->{'forward_to_gosa'}}[0];
938 my $hostId;
939 my $error = 0;
940 my $xml_msg;
942 # Build return message with twisted target and source
943 my $out_hash = &main::create_xml_hash("answer_$header", $main::server_address, $source);
944 if (defined $forward_to_gosa) {
945 &add_content2xml_hash($out_hash, "forward_to_gosa", $forward_to_gosa);
946 }
947 &add_content2xml_hash($out_hash, "xxx", "");
949 # Get hostID if defined
950 if ((exists $msg_hash->{'hostId'}) && (@{$msg_hash->{'hostId'}} == 1)) {
951 $hostId = @{$msg_hash->{'hostId'}}[0];
952 &add_content2xml_hash($out_hash, "hostId", $hostId);
953 }
955 # Move to XML string
956 my $xml_msg= &create_xml_string($out_hash);
958 # For hosts, only return the products that are or get installed
959 my $callobj;
960 $callobj = {
961 method => 'getLocalBootProductIds_list',
962 params => [ ],
963 id => 1,
964 };
966 my $res = $main::opsi_client->call($main::opsi_url, $callobj);
967 my %r = ();
968 for (@{$res->result}) { $r{$_} = 1 }
970 if (not &check_opsi_res($res)){
972 if (defined $hostId){
973 $callobj = {
974 method => 'getProductStates_hash',
975 params => [ $hostId ],
976 id => 1,
977 };
979 my $hres = $main::opsi_client->call($main::opsi_url, $callobj);
980 if (not &check_opsi_res($hres)){
981 my $htmp= $hres->result->{$hostId};
983 # Check state != not_installed or action == setup -> load and add
984 foreach my $product (@{$htmp}){
986 if (!defined ($r{$product->{'productId'}})){
987 next;
988 }
990 # Now we've a couple of hashes...
991 if ($product->{'installationStatus'} ne "not_installed" or
992 $product->{'actionRequest'} eq "setup"){
993 my $state= "<state>".$product->{'installationStatus'}."</state><action>".$product->{'actionRequest'}."</action>";
995 $callobj = {
996 method => 'getProduct_hash',
997 params => [ $product->{'productId'} ],
998 id => 1,
999 };
1001 my $sres = $main::opsi_client->call($main::opsi_url, $callobj);
1002 if (not &check_opsi_res($sres)){
1003 my $tres= $sres->result;
1005 my $name= xml_quote($tres->{'name'});
1006 my $r= $product->{'productId'};
1007 my $description= xml_quote($tres->{'description'});
1008 $name=~ s/\//\\\//;
1009 $description=~ s/\//\\\//;
1010 $xml_msg=~ s/<xxx><\/xxx>/<item><productId>$r<\/productId><name><\/name><description>$description<\/description><\/item>$state<xxx><\/xxx>/;
1011 }
1013 }
1014 }
1016 }
1018 } else {
1019 foreach my $r (@{$res->result}) {
1020 $callobj = {
1021 method => 'getProduct_hash',
1022 params => [ $r ],
1023 id => 1,
1024 };
1026 my $sres = $main::opsi_client->call($main::opsi_url, $callobj);
1027 if (not &check_opsi_res($sres)){
1028 my $tres= $sres->result;
1030 my $name= xml_quote($tres->{'name'});
1031 my $description= xml_quote($tres->{'description'});
1032 $name=~ s/\//\\\//;
1033 $description=~ s/\//\\\//;
1034 $xml_msg=~ s/<xxx><\/xxx>/<item><productId>$r<\/productId><name><\/name><description>$description<\/description><\/item><xxx><\/xxx>/;
1035 }
1037 }
1039 }
1040 }
1042 $xml_msg=~ s/<xxx><\/xxx>//;
1044 # Retrun Message
1045 return ( $xml_msg );
1046 }
1049 ## @method opsi_del_client
1050 # Deletes a client from Opsi.
1051 # @param msg - STRING - xml message with tag hostId
1052 # @param msg_hash - HASHREF - message information parsed into a hash
1053 # @param session_id - INTEGER - POE session id of the processing of this message
1054 # @return out_msg - STRING - feedback to GOsa in success and error case
1055 sub opsi_del_client {
1056 my ($msg, $msg_hash, $session_id) = @_;
1057 my $header = @{$msg_hash->{'header'}}[0];
1058 my $source = @{$msg_hash->{'source'}}[0];
1059 my $target = @{$msg_hash->{'target'}}[0];
1060 my $forward_to_gosa = @{$msg_hash->{'forward_to_gosa'}}[0];
1061 my $hostId;
1062 my $error = 0;
1064 # Build return message with twisted target and source
1065 my $out_hash = &main::create_xml_hash("answer_$header", $main::server_address, $source);
1066 if (defined $forward_to_gosa) {
1067 &add_content2xml_hash($out_hash, "forward_to_gosa", $forward_to_gosa);
1068 }
1070 # Sanity check of needed parameter
1071 if ((exists $msg_hash->{'hostId'}) && (@{$msg_hash->{'hostId'}} != 1)) {
1072 $error++;
1073 &add_content2xml_hash($out_hash, "hostId_error", "hostId contains no or more than one values");
1074 &add_content2xml_hash($out_hash, "error", "hostId");
1075 &main::daemon_log("$session_id ERROR: hostId contains no or more than one values: $msg", 1);
1076 }
1078 if (not $error) {
1080 # Get hostID
1081 $hostId = @{$msg_hash->{'hostId'}}[0];
1082 &add_content2xml_hash($out_hash, "hostId", "$hostId");
1084 # JSON Query
1085 my $callobj = {
1086 method => 'deleteClient',
1087 params => [ $hostId ],
1088 id => 1,
1089 };
1090 my $res = $main::opsi_client->call($main::opsi_url, $callobj);
1091 }
1093 # Move to XML string
1094 my $xml_msg= &create_xml_string($out_hash);
1096 # Return message
1097 return ( $xml_msg );
1098 }
1101 ## @method opsi_install_client
1102 # Set a client in Opsi to install and trigger a wake on lan message (WOL).
1103 # @param msg - STRING - xml message with tags hostId, macaddress
1104 # @param msg_hash - HASHREF - message information parsed into a hash
1105 # @param session_id - INTEGER - POE session id of the processing of this message
1106 # @return out_msg - STRING - feedback to GOsa in success and error case
1107 sub opsi_install_client {
1108 my ($msg, $msg_hash, $session_id) = @_;
1109 my $header = @{$msg_hash->{'header'}}[0];
1110 my $source = @{$msg_hash->{'source'}}[0];
1111 my $target = @{$msg_hash->{'target'}}[0];
1112 my $forward_to_gosa = @{$msg_hash->{'forward_to_gosa'}}[0];
1115 my ($hostId, $macaddress);
1117 my $error = 0;
1118 my @out_msg_l;
1120 # Build return message with twisted target and source
1121 my $out_hash = &main::create_xml_hash("answer_$header", $main::server_address, $source);
1122 if (defined $forward_to_gosa) {
1123 &add_content2xml_hash($out_hash, "forward_to_gosa", $forward_to_gosa);
1124 }
1126 # Sanity check of needed parameter
1127 if ((not exists $msg_hash->{'hostId'}) || (@{$msg_hash->{'hostId'}} != 1)) {
1128 $error++;
1129 &add_content2xml_hash($out_hash, "hostId_error", "no hostId specified or hostId tag invalid");
1130 &add_content2xml_hash($out_hash, "error", "hostId");
1131 &main::daemon_log("$session_id ERROR: no hostId specified or hostId tag invalid: $msg", 1);
1132 }
1133 if ((not exists $msg_hash->{'macaddress'}) || (@{$msg_hash->{'macaddress'}} != 1)) {
1134 $error++;
1135 &add_content2xml_hash($out_hash, "macaddress_error", "no macaddress specified or macaddress tag invalid");
1136 &add_content2xml_hash($out_hash, "error", "macaddress");
1137 &main::daemon_log("$session_id ERROR: no macaddress specified or macaddress tag invalid: $msg", 1);
1138 } else {
1139 if ((exists $msg_hash->{'macaddress'}) &&
1140 ($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)) {
1141 $macaddress = $1;
1142 } else {
1143 $error ++;
1144 &add_content2xml_hash($out_hash, "macaddress_error", "given mac address is not correct");
1145 &add_content2xml_hash($out_hash, "error", "macaddress");
1146 &main::daemon_log("$session_id ERROR: given mac address is not correct: $msg", 1);
1147 }
1148 }
1150 if (not $error) {
1152 # Get hostId
1153 $hostId = @{$msg_hash->{'hostId'}}[0];
1154 &add_content2xml_hash($out_hash, "hostId", "$hostId");
1156 # Load all products for this host with status != "not_installed" or actionRequest != "none"
1157 my $callobj = {
1158 method => 'getProductStates_hash',
1159 params => [ $hostId ],
1160 id => 1,
1161 };
1163 my $hres = $main::opsi_client->call($main::opsi_url, $callobj);
1164 if (not &check_opsi_res($hres)){
1165 my $htmp= $hres->result->{$hostId};
1167 # check state != not_installed or action == setup -> load and add
1168 foreach my $product (@{$htmp}){
1169 # Now we've a couple of hashes...
1170 if ($product->{'installationStatus'} ne "not_installed" or
1171 $product->{'actionRequest'} ne "none"){
1173 # Do an action request for all these -> "setup".
1174 $callobj = {
1175 method => 'setProductActionRequest',
1176 params => [ $product->{'productId'}, $hostId, "setup" ],
1177 id => 1,
1178 };
1179 my $res = $main::opsi_client->call($main::opsi_url, $callobj);
1180 my ($res_err, $res_err_string) = &check_opsi_res($res);
1181 if ($res_err){
1182 &main::daemon_log("$session_id ERROR: cannot set product action request for '$hostId': ".$product->{'productId'}, 1);
1183 } else {
1184 &main::daemon_log("$session_id INFO: requesting 'setup' for '".$product->{'productId'}."' on $hostId", 1);
1185 }
1186 }
1187 }
1188 }
1189 push(@out_msg_l, &create_xml_string($out_hash));
1192 # Build wakeup message for client
1193 if (not $error) {
1194 my $wakeup_hash = &create_xml_hash("trigger_wake", "GOSA", "KNOWN_SERVER");
1195 &add_content2xml_hash($wakeup_hash, 'macAddress', $macaddress);
1196 my $wakeup_msg = &create_xml_string($wakeup_hash);
1197 push(@out_msg_l, $wakeup_msg);
1199 # invoke trigger wake for this gosa-si-server
1200 &main::server_server_com::trigger_wake($wakeup_msg, $wakeup_hash, $session_id);
1201 }
1202 }
1204 # Return messages
1205 return @out_msg_l;
1206 }
1209 ## @method _set_action
1210 # Set action for an Opsi client
1211 # @param product - STRING - Opsi product
1212 # @param action - STRING - action
1213 # @param hostId - STRING - Opsi hostId
1214 sub _set_action {
1215 my $product= shift;
1216 my $action = shift;
1217 my $hostId = shift;
1218 my $callobj;
1220 $callobj = {
1221 method => 'setProductActionRequest',
1222 params => [ $product, $hostId, $action],
1223 id => 1,
1224 };
1226 $main::opsi_client->call($main::opsi_url, $callobj);
1227 }
1229 ## @method _set_state
1230 # Set state for an Opsi client
1231 # @param product - STRING - Opsi product
1232 # @param action - STRING - state
1233 # @param hostId - STRING - Opsi hostId
1234 sub _set_state {
1235 my $product = shift;
1236 my $state = shift;
1237 my $hostId = shift;
1238 my $callobj;
1240 $callobj = {
1241 method => 'setProductState',
1242 params => [ $product, $hostId, $state ],
1243 id => 1,
1244 };
1246 $main::opsi_client->call($main::opsi_url, $callobj);
1247 }
1249 1;