Code

Updated some sieve templates
[gosa.git] / gosa-si / server / events / gosaTriggered.pm
1 ## @file
2 # @details A GOsa-SI event module containing all functions common used by GOsa
3 # @brief Implementation of a GOsa-SI event module. 
5 package gosaTriggered;
7 use strict;
8 use warnings;
10 use Exporter;
11 use GOSA::GosaSupportDaemon;
12 use Crypt::SmbHash;
13 use Net::ARP;
14 use Net::Ping;
15 use Socket;
16 use Time::HiRes qw( usleep);
17 use MIME::Base64;
18 use Data::Dumper;
20 our @ISA = qw(Exporter);
22 my @events = (
23     "get_events", 
24     "get_login_usr_for_client",
25     "get_client_for_login_usr",
26     "gen_smb_hash",
27     "trigger_reload_syslog_config",
28     "trigger_reload_ntp_config",
29     "trigger_reload_ldap_config",
30     "network_completition",
31     "set_activated_for_installation",
32     "new_key_for_client",
33     "detect_hardware",
34     "trigger_action_localboot",
35     "trigger_action_faireboot",
36     "trigger_action_reboot",
37     "trigger_action_activate",
38     "trigger_action_lock",
39     "trigger_action_halt",
40     "trigger_action_update", 
41     "trigger_action_reinstall",
42     "trigger_action_sysinfo",
43     "trigger_action_instant_update",
44     "trigger_action_rescan",
45     "trigger_action_wake",
46     "recreate_fai_server_db",
47     "recreate_fai_release_db",
48     "recreate_packages_list_db",
49     "send_user_msg", 
50     "get_available_kernel",
51   "trigger_activate_new",
52     "get_hosts_with_module",    
53     );
54     
55 our @EXPORT = @events;
57 BEGIN {}
59 END {}
61 ### Start ######################################################################
63 ## @method get_events()
64 # A brief function returning a list of functions which are exported by importing the module.
65 # @return List of all provided functions
66 sub get_events {
67     return \@events;
68 }
70 ## @method send_usr_msg($msg, $msg_hash, $session_id)
71 # This function accepts usr messages from GOsa, split mulitple target messages to mulitiple single target messages and put all messages into messaging_db
72 # @param msg - STRING - xml message
73 # @param msg_hash - HASHREF - message information parsed into a hash
74 # @param session_id - INTEGER - POE session id of the processing of this message
75 # @return (out_msg)  - ARRAY - Array containing the answer message from client
76 sub send_user_msg {
77     my ($msg, $msg_hash, $session_id) = @_ ;
78     my $header = @{$msg_hash->{'header'}}[0];
79     my $source = @{$msg_hash->{'source'}}[0];
80     my $target = @{$msg_hash->{'target'}}[0];
82     my $subject = @{$msg_hash->{'subject'}}[0];
83     my $from = @{$msg_hash->{'from'}}[0];
84     my @users = exists $msg_hash->{'user'} ? @{$msg_hash->{'user'}} : () ;
85         my @groups = exists $msg_hash->{'group'} ? @{$msg_hash->{'group'}} : ();
86     my $delivery_time = @{$msg_hash->{'delivery_time'}}[0];
87     my $message = @{$msg_hash->{'message'}}[0];
88     
89 #    # keep job queue uptodate if necessary 
90 #    my $jobdb_id = @{$msg_hash->{'jobdb_id'}}[0];
91 #    if( defined $jobdb_id) {
92 #        my $sql_statement = "UPDATE $main::job_queue_tn SET status='processed' WHERE ((id=$jobdb_id) AND (NOT status='done'))";
93 #        &main::daemon_log("$session_id DEBUG: $sql_statement", 7); 
94 #        my $res = $main::job_db->exec_statement($sql_statement);
95 #    }
97     # error handling
98     if (not $delivery_time =~ /^\d{14}$/) {
99         my $error_string = "delivery_time '$delivery_time' is not a valid timestamp, please use format 'yyyymmddhhmmss'";
100         &main::daemon_log("$session_id ERROR: $error_string", 1);
101         return &create_xml_string(&create_xml_hash($header, $target, $source, $error_string));
102     }
104     # determine new message id
105     my $new_msg_id = 1;
106         my $new_msg_id_sql = "SELECT MAX(id) FROM $main::messaging_tn";
107     my $new_msg_id_res = $main::messaging_db->exec_statement($new_msg_id_sql);
108     if (defined @{@{$new_msg_id_res}[0]}[0] ) {
109         $new_msg_id = int(@{@{$new_msg_id_res}[0]}[0]);
110         $new_msg_id += 1;
111     }
113         # highlight user name and group name
114         my @receiver_l;
115         @users = map(push(@receiver_l, "u_$_"), @users);
116         @groups = map(push(@receiver_l, "g_$_"), @groups);
118     # Sanitiy check of receivers list
119     if (@receiver_l == 0) {
120         &main::daemon_log("$session_id ERROR: 'send_usr_msg'-message contains neither a 'usr' nor a 'group' tag. No receiver specified.", 1); 
121         return;
122     }
124     # add incoming message to messaging_db
125     my $func_dic = {table=>$main::messaging_tn,
126         primkey=>[],
127         id=>$new_msg_id,
128         subject=>$subject,
129         message_from=>$from,
130         message_to=>join(",", @receiver_l),
131         flag=>"n",
132         direction=>"in",
133         delivery_time=>$delivery_time,
134         message=>$message,
135         timestamp=>&get_time(),
136     };
137     my $res = $main::messaging_db->add_dbentry($func_dic);
138     if (not $res == 0) {
139         &main::daemon_log("$session_id ERROR: gosaTriggered.pm: cannot add message to message_db: $res", 1);
140     } else {
141         &main::daemon_log("$session_id INFO: gosaTriggered.pm: message with subject '".&decode_base64($subject)."' successfully added to message_db", 5);
142     }
144     return;
148 sub recreate_fai_server_db {
149     my ($msg, $msg_hash, $session_id) = @_ ;
150     my $out_msg;
152 #    my $jobdb_id = @{$msg_hash->{'jobdb_id'}}[0];
153 #    if( defined $jobdb_id) {
154 #        my $sql_statement = "UPDATE $main::job_queue_tn SET status='processed' WHERE ((id=$jobdb_id) AND (NOT status='done'))";
155 #        &main::daemon_log("$session_id DEBUG: $sql_statement", 7); 
156 #        my $res = $main::job_db->exec_statement($sql_statement);
157 #    }
159     $main::fai_server_db->create_table("new_fai_server", \@main::fai_server_col_names);
160     &main::create_fai_server_db("new_fai_server",undef,"dont", $session_id);
161     $main::fai_server_db->move_table("new_fai_server", $main::fai_server_tn);
162     
163     my @out_msg_l = ( $out_msg );
164     return @out_msg_l;
168 sub recreate_fai_release_db {
169     my ($msg, $msg_hash, $session_id) = @_ ;
170     my $out_msg;
172 #    my $jobdb_id = @{$msg_hash->{'jobdb_id'}}[0];
173 #    if( defined $jobdb_id) {
174 #        my $sql_statement = "UPDATE $main::job_queue_tn SET status='processed' WHERE ((id=$jobdb_id) AND (NOT status='done'))";
175 #        &main::daemon_log("$session_id DEBUG: $sql_statement", 7);
176 #        my $res = $main::job_db->exec_statement($sql_statement);
177 #    }
179     $main::fai_release_db->create_table("new_fai_release", \@main::fai_release_col_names);
180     &main::create_fai_release_db("new_fai_release", $session_id);
181     $main::fai_release_db->move_table("new_fai_release", $main::fai_release_tn);
183     my @out_msg_l = ( $out_msg );
184     return @out_msg_l;
188 sub recreate_packages_list_db {
189         my ($msg, $msg_hash, $session_id) = @_ ;
190         my $out_msg;
192 #       my $jobdb_id = @{$msg_hash->{'jobdb_id'}}[0];
193 #       if( defined $jobdb_id) {
194 #               my $sql_statement = "UPDATE $main::job_queue_tn SET status='processed' WHERE ((id=$jobdb_id) AND (NOT status='done'))";
195 #               &main::daemon_log("$session_id DEBUG: $sql_statement", 7);
196 #               my $res = $main::job_db->exec_statement($sql_statement);
197 #       }
199         &main::create_packages_list_db;
201         my @out_msg_l = ( $out_msg );
202         return @out_msg_l;
206 sub get_login_usr_for_client {
207     my ($msg, $msg_hash, $session_id) = @_ ;
208     my $header = @{$msg_hash->{'header'}}[0];
209     $header =~ s/^gosa_//;
210     my $source = @{$msg_hash->{'source'}}[0];
211     my $target = @{$msg_hash->{'target'}}[0];
212     my $client = @{$msg_hash->{'client'}}[0];
214 #    # Set job status
215 #    my $jobdb_id = @{$msg_hash->{'jobdb_id'}}[0];
216 #    if( defined $jobdb_id) {
217 #        my $sql_statement = "UPDATE $main::job_queue_tn SET status='processed' WHERE ((id=$jobdb_id) AND (NOT status='done'))";
218 #        &main::daemon_log("$session_id DEBUG: $sql_statement", 7);
219 #        my $res = $main::job_db->exec_statement($sql_statement);
220 #    }
222     # If $client is a mac address
223     if ($client =~ /^\w\w:\w\w:\w\w:\w\w:\w\w:\w\w$/i)
224     {
225         # Search for hostname of $client within known_clients_db
226         my $sql = "SELECT * FROM $main::known_clients_tn WHERE macaddress LIKE '$client'";
227         my $res = $main::known_clients_db->select_dbentry($sql);
228         my $found = 0;
229         if (keys(%$res) == 1)
230         {
231             $found = 1;
232             $client = $res->{1}->{hostname};
233         }
234         if (not $found)  # Do only if the first search results in nothing
235         {
236             # Search for hostname of $client within known_foreign_db
237             $sql = "SELECT * FROM $main::foreign_clients_tn WHERE macaddress LIKE '$client'";
238             $res = $main::foreign_clients_db->select_dbentry($sql);
239             if (keys(%$res) == 1)
240             {
241                 $client = $res->{1}->{hostname};
242             }
243         }
244     }
246     # Search for logged in users at hostname
247     my $sql_statement = "SELECT * FROM $main::login_users_tn WHERE client LIKE '$client'";
248     my $res = $main::login_users_db->select_dbentry($sql_statement);
250     # Create answer message for GOsa
251     my $out_msg;
252     if (keys(%$res) == 0)
253     {
254         my $info = "INFO: No hits found in login_users_db for client '$client'";
255         $out_msg = &create_xml_string(&create_xml_hash($header, $target, $source, $info));
256         &main::daemon_log("$session_id ".$info, 5);
257     }
258     else
259     {
260         $out_msg = "<xml><header>$header</header><source>$target</source><target>$source</target>";
261         $out_msg .= &db_res2xml($res);
262         $out_msg .= "</xml>";
263     }
265     return ($out_msg);
269 sub get_client_for_login_usr {
270     my ($msg, $msg_hash, $session_id) = @_ ;
271     my $header = @{$msg_hash->{'header'}}[0];
272     $header =~ s/^gosa_//;
273     my $source = @{$msg_hash->{'source'}}[0];
274     my $target = @{$msg_hash->{'target'}}[0];
275     my $usr = @{$msg_hash->{'usr'}}[0];
277 #    # Set job status
278 #    my $jobdb_id = @{$msg_hash->{'jobdb_id'}}[0];
279 #    if( defined $jobdb_id) {
280 #        my $sql_statement = "UPDATE $main::job_queue_tn SET status='processed' WHERE ((id=$jobdb_id) AND (NOT status='done'))";
281 #        &main::daemon_log("$session_id DEBUG: $sql_statement", 7);
282 #        my $res = $main::job_db->exec_statement($sql_statement);
283 #    }
285     # Search for clients where $usr is logged in
286     my $sql_statement = "SELECT * FROM $main::login_users_tn WHERE user LIKE '%$usr%'";
287     my $res = $main::login_users_db->select_dbentry($sql_statement);
289     # Create answer message for GOsa
290     my $out_msg = "<xml><header>$header</header><source>$target</source><target>$source</target>";
291     $out_msg .= &db_res2xml($res);
292     $out_msg .= "</xml>";
294     return ( $out_msg );
298 sub gen_smb_hash {
299      my ($msg, $msg_hash, $session_id) = @_ ;
300      my $source = @{$msg_hash->{source}}[0];
301      my $target = @{$msg_hash->{target}}[0];
302      my $password = @{$msg_hash->{password}}[0];
304      my %data= ('hash' => join(q[:], ntlmgen $password));
305      my $out_msg = &build_msg("gen_smb_hash", $target, $source, \%data );
306      my $forward_to_gosa = @{$msg_hash->{'forward_to_gosa'}}[0];
307      if (defined $forward_to_gosa) {
308          $out_msg =~s/<\/xml>/<forward_to_gosa>$forward_to_gosa<\/forward_to_gosa><\/xml>/;
309      }
311      return ( $out_msg );
315 sub network_completition {
316      my ($msg, $msg_hash, $session_id) = @_ ;
317      my $source = @{$msg_hash->{source}}[0];
318      my $target = @{$msg_hash->{target}}[0];
319      my $name = @{$msg_hash->{hostname}}[0];
321      # Can we resolv the name?
322      my %data;
323      if (inet_aton($name)){
324          my $tmp = (inet_aton($name));
325              my $address = inet_ntoa($tmp);
326              my $p = Net::Ping->new('tcp');
327              my $mac= "";
328              if ($p->ping($address, 1)){
329                $mac = Net::ARP::arp_lookup("", $address);
330              }
332              %data= ('ip' => $address, 'mac' => $mac);
333      } else {
334              %data= ('ip' => '', 'mac' => '');
335      }
337      my $out_msg = &build_msg("network_completition", $target, $source, \%data );
338      my $forward_to_gosa = @{$msg_hash->{'forward_to_gosa'}}[0];
339      if (defined $forward_to_gosa) {
340          $out_msg =~s/<\/xml>/<forward_to_gosa>$forward_to_gosa<\/forward_to_gosa><\/xml>/;
341      }
343      return ( $out_msg );
347 sub detect_hardware {
348     my ($msg, $msg_hash, $session_id) = @_ ;
349     # just forward msg to client, but dont forget to split off 'gosa_' in header
350     my $source = @{$msg_hash->{source}}[0];
351     my $mac = @{$msg_hash->{macaddress}}[0];
352     my $jobdb_id = @{$msg_hash->{'jobdb_id'}}[0];
353     if( defined $jobdb_id) {
354         my $sql_statement = "UPDATE $main::job_queue_tn SET status='processed' WHERE ((id=$jobdb_id) AND (NOT status='done'))";
355         &main::daemon_log("$session_id DEBUG: $sql_statement", 7); 
356         my $res = $main::job_db->exec_statement($sql_statement);
357     }
359     my $out_hash = &create_xml_hash("detect_hardware", $source, $mac);
360     if( defined $jobdb_id ) { 
361         &add_content2xml_hash($out_hash, 'jobdb_id', $jobdb_id); 
362     }
363     my $out_msg = &create_xml_string($out_hash);
365     my @out_msg_l = ( $out_msg );
366     return @out_msg_l;
370 sub trigger_reload_syslog_config {
371     my ($msg, $msg_hash, $session_id) = @_ ;
373     # Sanity check of macaddress
374     # TODO
376     my $macaddress = @{$msg_hash->{macaddress}}[0];
378 #    my $jobdb_id = @{$msg_hash->{'jobdb_id'}}[0];
379 #    if( defined $jobdb_id) {
380 #        my $sql_statement = "UPDATE $main::job_queue_tn SET status='processed' WHERE ((id=$jobdb_id) AND (NOT status='done'))";
381 #        &main::daemon_log("$session_id DEBUG: $sql_statement", 7); 
382 #        my $res = $main::job_db->exec_statement($sql_statement);
383 #    }
385         my $out_msg = &ClientPackages::new_syslog_config($macaddress, $session_id);
386         my @out_msg_l = ( $out_msg );
388     return @out_msg_l;
390    
393 sub trigger_reload_ntp_config {
394     my ($msg, $msg_hash, $session_id) = @_ ;
396     # Sanity check of macaddress
397     # TODO
399     my $macaddress = @{$msg_hash->{macaddress}}[0];
401 #    my $jobdb_id = @{$msg_hash->{'jobdb_id'}}[0];
402 #    if( defined $jobdb_id) {
403 #        my $sql_statement = "UPDATE $main::job_queue_tn SET status='processed' WHERE ((id=$jobdb_id) AND (NOT status='done'))";
404 #        &main::daemon_log("$session_id DEBUG: $sql_statement", 7); 
405 #        my $res = $main::job_db->exec_statement($sql_statement);
406 #    }
408         my $out_msg = &ClientPackages::new_ntp_config($macaddress, $session_id);
409         my @out_msg_l = ( $out_msg );
411     return @out_msg_l;
415 sub trigger_reload_ldap_config {
416     my ($msg, $msg_hash, $session_id) = @_ ;
417     my $mac = @{$msg_hash->{macaddress}}[0];
419 #    my $jobdb_id = @{$msg_hash->{'jobdb_id'}}[0];
420 #    if( defined $jobdb_id) {
421 #        my $sql_statement = "UPDATE $main::job_queue_tn SET status='processed' WHERE ((id=$jobdb_id) AND (NOT status='done'))";
422 #        &main::daemon_log("$session_id DEBUG: $sql_statement", 7); 
423 #        my $res = $main::job_db->exec_statement($sql_statement);
424 #    }
426         my $out_msg = &ClientPackages::new_ldap_config($mac, $session_id);
427         my @out_msg_l = ( $out_msg );
429     return @out_msg_l;
433 sub set_activated_for_installation {
434     my ($msg, $msg_hash, $session_id) = @_;
435     my $header = @{$msg_hash->{header}}[0];
436     my $source = @{$msg_hash->{source}}[0];
437         my $mac= (defined($msg_hash->{'macaddress'}))?@{$msg_hash->{'macaddress'}}[0]:undef;
438     my $jobdb_id = @{$msg_hash->{'jobdb_id'}}[0];
439         my @out_msg_l;
441     # TODO Sanity check macAddress defined
443 #       # update status of job 
444 #    if( defined $jobdb_id) {
445 #        my $sql_statement = "UPDATE $main::job_queue_tn SET status='processed' WHERE ((id=$jobdb_id) AND (NOT status='done'))";
446 #        &main::daemon_log("$session_id DEBUG: $sql_statement", 7); 
447 #        my $res = $main::job_db->exec_statement($sql_statement);
448 #    }
450     # If a client gets a 'set_activated_for_installation' msg, always deliver a fresh 'new_ldap_config'
451     # just for backup and robustness purposes
452     my $ldap_out_msg = &ClientPackages::new_ldap_config($mac, $session_id);
453     push(@out_msg_l, $ldap_out_msg);
455         # create set_activated_for_installation message for delivery
456     my $out_hash = &create_xml_hash("set_activated_for_installation", $source, $mac);
457     if( defined $jobdb_id ) { 
458         &add_content2xml_hash($out_hash, 'jobdb_id', $jobdb_id); 
459     }
460     my $out_msg = &create_xml_string($out_hash);
461         push(@out_msg_l, $out_msg); 
463     return @out_msg_l;
467 sub trigger_action_faireboot {
468     my ($msg, $msg_hash, $session_id) = @_;
469     my $mac = @{$msg_hash->{macaddress}}[0];
470     my $source = @{$msg_hash->{source}}[0];
472     # Create message for client
473     my $out_msg = &main::create_xml_string(&main::create_xml_hash("trigger_action_faireboot", $source, $mac));
475     # Set LDAP states
476     &main::change_goto_state('locked', \@{$msg_hash->{macaddress}}, $session_id);
477         &main::change_fai_state('install', \@{$msg_hash->{macaddress}}, $session_id); 
479 #    # Set job to status 'done', job will be deleted automatically
480 #    my $sql_statement = "UPDATE $main::job_queue_tn ".
481 #        "SET status='done', modified='1'".
482 #        "WHERE (macaddress LIKE '$mac' AND status='processing')";
483 #    &main::daemon_log("$session_id DEBUG: $sql_statement", 7);
484 #    my $res = $main::job_db->update_dbentry( $sql_statement );
486     return ( $out_msg ); 
490 sub trigger_action_lock {
491     my ($msg, $msg_hash, $session_id) = @_;
493     # Set LDAP state
494     &main::change_goto_state('locked', \@{$msg_hash->{macaddress}}, $session_id);
496 #    # Set job status
497 #    my $jobdb_id = @{$msg_hash->{'jobdb_id'}}[0];
498 #    if( defined $jobdb_id) {
499 #        my $sql_statement = "UPDATE $main::job_queue_tn SET status='processed' WHERE ((id=$jobdb_id) AND (NOT status='done'))";
500 #        &main::daemon_log("$session_id DEBUG: $sql_statement", 7); 
501 #        my $res = $main::job_db->exec_statement($sql_statement);
502 #    }
503                                              
504     return;
508 sub trigger_action_activate {
509     my ($msg, $msg_hash, $session_id) = @_;
510     my $mac = @{$msg_hash->{macaddress}}[0];
511     my $source = @{$msg_hash->{source}}[0];
513     # Set LDAP state
514     &main::change_goto_state('active', \@{$msg_hash->{macaddress}}, $session_id);
516 #    # Set job status
517 #    my $jobdb_id = @{$msg_hash->{'jobdb_id'}}[0];
518 #    if( defined $jobdb_id) {
519 #        my $sql_statement = "UPDATE $main::job_queue_tn SET status='processed' WHERE ((id=$jobdb_id) AND (NOT status='done'))";
520 #        &main::daemon_log("$session_id DEBUG: $sql_statement", 7); 
521 #        my $res = $main::job_db->exec_statement($sql_statement);
522 #    }
523      
524     # Create message for client
525     my $out_hash = &create_xml_hash("set_activated_for_installation", $source, $mac);
526     if( exists $msg_hash->{'jobdb_id'} ) { 
527         &add_content2xml_hash($out_hash, 'jobdb_id', @{$msg_hash->{'jobdb_id'}}[0]); 
528     }
529     my $out_msg = &create_xml_string($out_hash);
531     return ( $out_msg );
536 sub trigger_action_localboot {
537     my ($msg, $msg_hash, $session_id) = @_;
538     my $source = $msg_hash->{source}[0];
539     my $mac = $msg_hash->{macaddress}[0];
540     my $target = $msg_hash->{target}[0];
541     my @out_msg_l;
543     # Create message for client
544     my $out_msg = &main::create_xml_string(&main::create_xml_hash("trigger_action_localboot", $source, $mac));
545     push(@out_msg_l, $out_msg);  
547     # Check for running jobs. In that case return a message to GOsa that running jobs have to be deleted/aborted
548     # befor trigger_action_localboot could be effective. Running jobs usually sets FAIstate and GOtomode to
549     # what they need again and again and overwrite the 'trigger_action_localboot' setting
550     my $job_sql= "SELECT * FROM $main::job_queue_tn WHERE macaddress LIKE '$mac'";
551     my $job_res = $main::job_db->select_dbentry($job_sql);
552     my $job_res_count = keys(%$job_res);
553     if ($job_res_count) {
554         push(@out_msg_l, "<xml><header>answer</header><source>$target</source><target>GOSA</target><answer1>existing_job_in_queue</answer1></xml>");
555     }
557     # Set LDAP state
558     &main::change_fai_state('localboot', \@{$msg_hash->{macaddress}}, $session_id);
560 #    # Set job status
561 #    my $jobdb_id = @{$msg_hash->{'jobdb_id'}}[0];
562 #    if( defined $jobdb_id) {
563 #        my $sql_statement = "UPDATE $main::job_queue_tn SET status='processed' WHERE ((id=$jobdb_id) AND (NOT status='done'))";
564 #        &main::daemon_log("$session_id DEBUG: $sql_statement", 7); 
565 #        my $res = $main::job_db->exec_statement($sql_statement);
566 #    }
568     return @out_msg_l;
572 sub trigger_action_halt {
573     my ($msg, $msg_hash, $session_id) = @_;
574     my $source = $msg_hash->{source}[0];
575     my $mac = $msg_hash->{macaddress}[0];
577     # Create message for client
578     my $out_msg = &main::create_xml_string(&main::create_xml_hash("trigger_action_halt", $source, $mac));
579  
580 #    # Set job status
581 #    my $jobdb_id = @{$msg_hash->{'jobdb_id'}}[0];
582 #    if( defined $jobdb_id) {
583 #        my $sql_statement = "UPDATE $main::job_queue_tn SET status='processed' WHERE ((id=$jobdb_id) AND (NOT status='done'))";
584 #        &main::daemon_log("$session_id DEBUG: $sql_statement", 7); 
585 #        my $res = $main::job_db->exec_statement($sql_statement);
586 #    }
588     return ($out_msg);
592 sub trigger_action_reboot {
593     my ($msg, $msg_hash, $session_id) = @_;
594     my $source = $msg_hash->{source}[0];
595     my $mac = $msg_hash->{macaddress}[0];
597     # Create message for client
598     my $out_msg = &main::create_xml_string(&main::create_xml_hash("trigger_action_reboot", $source, $mac));
600     # Set LDAP state
601     &main::change_fai_state('reboot', \@{$msg_hash->{macaddress}}, $session_id);
603     return ($out_msg);
607 sub trigger_action_reinstall {
608     my ($msg, $msg_hash, $session_id) = @_;
609     my $source = $msg_hash->{source}[0];
610     my $mac = $msg_hash->{macaddress}[0];
612     # Create message for client
613     my $out_msg = &main::create_xml_string(&main::create_xml_hash("trigger_action_reinstall", $source, $mac));
615     # Set LDAP state
616     &main::change_fai_state('reinstall', \@{$msg_hash->{macaddress}}, $session_id);
618     # Create wakeup message for all foreign server
619     my %data = ( 'macaddress'  => \@{$msg_hash->{macaddress}} );
620     my $wake_msg = &build_msg("trigger_wake", "GOSA", "KNOWN_SERVER", \%data);
622     # Invoke trigger wake for this gosa-si-server
623     &main::server_server_com::trigger_wake($msg, $msg_hash, $session_id);
625     return ($wake_msg, $msg);  
629 sub trigger_action_update {
630     my ($msg, $msg_hash, $session_id) = @_;
631     my $source = $msg_hash->{source}[0];
632     my $mac = $msg_hash->{macaddress}[0];
634     # Create message for client
635     my $out_msg = &main::create_xml_string(&main::create_xml_hash("trigger_action_update", $source, $mac));
637     # Set LDAP state
638     &main::change_fai_state('update', \@{$msg_hash->{macaddress}}, $session_id);
640     # Create wakeup message for all foreign server
641     my %data = ( 'macaddress'  => \@{$msg_hash->{macaddress}} );
642     my $wake_msg = &build_msg("trigger_wake", "GOSA", "KNOWN_SERVER", \%data);
644     # Invoke trigger wake for this gosa-si-server
645     &main::server_server_com::trigger_wake($msg, $msg_hash, $session_id);
647     return ($wake_msg, $msg);  
651 sub trigger_action_instant_update {
652     my ($msg, $msg_hash, $session_id) = @_;
653     my $source = $msg_hash->{source}[0];
654     my $mac = $msg_hash->{macaddress}[0];
656     # Create message for client
657     my $out_msg = &main::create_xml_string(&main::create_xml_hash("trigger_action_instant_update", $source, $mac));
659     # Set LDAP state
660     &main::change_fai_state('update', \@{$msg_hash->{macaddress}}, $session_id);
662 #    # Set job status
663 #    my $jobdb_id = @{$msg_hash->{'jobdb_id'}}[0];
664 #    if( defined $jobdb_id) {
665 #        my $sql_statement = "UPDATE $main::job_queue_tn SET status='processed' WHERE ((id=$jobdb_id) AND (NOT status='done'))";
666 #        &main::daemon_log("$session_id DEBUG: $sql_statement", 7); 
667 #        my $res = $main::job_db->exec_statement($sql_statement);
668 #    }
670     # Create wakeup message for all foreign server
671     my %data = ( 'macaddress'  => \@{$msg_hash->{macaddress}} );
672     my $wake_msg = &build_msg("trigger_wake", "GOSA", "KNOWN_SERVER", \%data);
674     # Invoke trigger wake for this gosa-si-server
675     &main::server_server_com::trigger_wake($msg, $msg_hash, $session_id);
677     return ($wake_msg, $msg);  
681 sub new_key_for_client {
682     my ($msg, $msg_hash, $session_id) = @_;
683     my $source = $msg_hash->{source}[0];
684     my $mac = $msg_hash->{macaddress}[0];
686     # Create message for client
687     my $out_msg = &main::create_xml_string(&main::create_xml_hash("new_key", $source, $mac));
689 #    # Set job status
690 #    my $jobdb_id = @{$msg_hash->{'jobdb_id'}}[0];
691 #    if( defined $jobdb_id) {
692 #        my $sql_statement = "UPDATE $main::job_queue_tn SET status='processed' WHERE ((id=$jobdb_id) AND (NOT status='done'))";
693 #        &main::daemon_log("$session_id DEBUG: $sql_statement", 7); 
694 #        my $res = $main::job_db->exec_statement($sql_statement);
695 #    }
697     return ($out_msg);
701 sub trigger_action_rescan {
702     my ($msg, $msg_hash, $session_id) = @_;
703     my $source = $msg_hash->{source}[0];
704     my $mac = $msg_hash->{macaddress}[0];
706     # Create message for client
707     my $out_msg = &main::create_xml_string(&main::create_xml_hash("detect_hardware", $source, $mac));
709 #    # Set job status
710 #    my $jobdb_id = @{$msg_hash->{'jobdb_id'}}[0];
711 #    if( defined $jobdb_id) {
712 #        my $sql_statement = "UPDATE $main::job_queue_tn SET status='processed' WHERE ((id=$jobdb_id) AND (NOT status='done'))";
713 #        &main::daemon_log("$session_id DEBUG: $sql_statement", 7); 
714 #        my $res = $main::job_db->exec_statement($sql_statement);
715 #    }
717     return ( $out_msg );
721 sub trigger_action_wake {
722     my ($msg, $msg_hash, $session_id) = @_;
723     my $jobdb_id = @{$msg_hash->{'jobdb_id'}}[0];
724  
725 #    # Set job status
726 #    if( defined $jobdb_id) {
727 #        my $sql_statement = "UPDATE $main::job_queue_tn SET status='processed' WHERE ((id=$jobdb_id) AND (NOT status='done'))";
728 #        &main::daemon_log("$session_id DEBUG: $sql_statement", 7); 
729 #        my $res = $main::job_db->exec_statement($sql_statement);
730 #    }
732     # Create wakeup message for all foreign server
733     my $out_hash = &create_xml_hash("trigger_wake", "GOSA", "KNOWN_SERVER");
734     foreach (@{$msg_hash->{'macaddress'}}) {
735         &add_content2xml_hash($out_hash, 'macaddress', $_);
736     }
737     if (defined $jobdb_id){
738         &add_content2xml_hash($out_hash, 'jobdb_id', $jobdb_id);
739     }
740     my $out_msg = &create_xml_string($out_hash);
741     
742     # Invoke trigger wake for this gosa-si-server
743     &main::server_server_com::trigger_wake($out_msg, $out_hash, $session_id);
745     return ( $out_msg );
749 sub get_available_kernel {
750   my ($msg, $msg_hash, $session_id) = @_;
751   my $source = @{$msg_hash->{'source'}}[0];
752   my $target = @{$msg_hash->{'target'}}[0];
753   my $fai_release= @{$msg_hash->{'fai_release'}}[0];
754   my @kernel;
756   # Get Kernel packages for release
757   my $sql_statement = "SELECT * FROM $main::packages_list_tn WHERE distribution='$fai_release' AND package LIKE 'linux\-image\-%'";
758   my $res_hash = $main::packages_list_db->select_dbentry($sql_statement);
759   my %data;
760   my $i=1;
761   foreach my $package (keys %{$res_hash}) {
762     $data{"answer".$i++}= $data{"answer".$i++}= ${$res_hash}{$package}->{'package'};
763   }
764   $data{"answer".$i++}= "default";
766   # Create answer for GOsa  
767   my $out_msg = &build_msg("get_available_kernel", $target, $source, \%data);
768   my $forward_to_gosa = @{$msg_hash->{'forward_to_gosa'}}[0];
769   if (defined $forward_to_gosa) {
770     $out_msg =~s/<\/xml>/<forward_to_gosa>$forward_to_gosa<\/forward_to_gosa><\/xml>/;
771   }
773   return ( $out_msg );
776 sub trigger_activate_new {
777         my ($msg, $msg_hash, $session_id) = @_;
778         my $source = @{$msg_hash->{'source'}}[0];
779         my $target = @{$msg_hash->{'target'}}[0];
780         my $header= @{$msg_hash->{'header'}}[0];
781         my $mac= (defined($msg_hash->{'mac'}))?@{$msg_hash->{'mac'}}[0]:undef;
782         my $ogroup= (defined($msg_hash->{'ogroup'}))?@{$msg_hash->{'ogroup'}}[0]:undef;
783         my $timestamp= (defined($msg_hash->{'timestamp'}))?@{$msg_hash->{'timestamp'}}[0]:undef;
784         my $base= (defined($msg_hash->{'base'}))?@{$msg_hash->{'base'}}[0]:undef;
785         my $hostname= (defined($msg_hash->{'fqdn'}))?@{$msg_hash->{'fqdn'}}[0]:undef;
786         my $ip_address= (defined($msg_hash->{'ip'}))?@{$msg_hash->{'ip'}}[0]:undef;
787         my $dhcp_statement= (defined($msg_hash->{'dhcp'}))?@{$msg_hash->{'dhcp'}}[0]:undef;
788         my $jobdb_id= (defined($msg_hash->{'jobdb_id'}))?@{$msg_hash->{'jobdb_id'}}[0]:undef;
790     # Sanity check for base
791     if (ref($base) eq "HASH") {
792         # Incoming msg has a xml tag 'base' but no content
793         $base = undef;
794     }
796     # In case that the client is sleeping, wake it up
797     my %data = ( 'macaddress'  => $mac );
798     my $wake_msg = &build_msg("trigger_wake", "GOSA", "KNOWN_SERVER", \%data);
799     &main::server_server_com::trigger_wake($msg, $msg_hash, $session_id);
800     my $sql_statement= "SELECT * FROM $main::known_server_tn";
801     my $query_res = $main::known_server_db->select_dbentry( $sql_statement ); 
802     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
803         my $host_name = $hit->{hostname};
804         my $host_key = $hit->{hostkey};
805         $wake_msg =~ s/<target>\S+<\/target>/<target>$host_name<\/target>/g;
806         my $error = &main::send_msg_to_target($wake_msg, $host_name, $host_key, $header, $session_id);
807     }
809         my $ldap_entry;
810         my $ogroup_entry;
811         my $changed_attributes_counter = 0;
813     my $activate_client = 0;
814     my $ldap_handle=&main::get_ldap_handle();
815         
816     if(defined($ogroup)) {
817       my $ldap_mesg= $ldap_handle->search(
818         base => $main::ldap_base,
819         scope => 'sub',
820         filter => "(&(objectClass=gosaGroupOfnames)(cn=$ogroup))",
821       );
822       if($ldap_mesg->count == 1) {
823         $ogroup_entry= $ldap_mesg->pop_entry();
824         &main::daemon_log("$session_id DEBUG: A GosaGroupOfNames with cn '$ogroup' was found in base '".$main::ldap_base."'!", 5);
825       } elsif ($ldap_mesg->count == 0) {
826         &main::daemon_log("$session_id ERROR: A GosaGroupOfNames with cn '$ogroup' was not found in base '".$main::ldap_base."'!", 1);
827         $main::job_db->exec_statement("UPDATE ".$main::job_queue_tn." SET status = 'waiting', timestamp = '".(&calc_timestamp(&get_time(), 'plus', 60))."' WHERE id = $jobdb_id");
828         &main::release_ldap_handle($ldap_handle);
829         return undef;
830       } else {
831         &main::daemon_log("$session_id ERROR: More than one ObjectGroups with cn '$ogroup' was found in base '".$main::ldap_base."'!", 1);
832         $main::job_db->exec_statement("UPDATE ".$main::job_queue_tn." SET status = 'waiting', timestamp = '".(&calc_timestamp(&get_time(), 'plus', 60))."' WHERE id = $jobdb_id");
833         &main::release_ldap_handle($ldap_handle);
834         return undef;
835       }
837       # build the base, use optional base parameter or take it from ogroup
838       if(!(defined($base) && (length($base) > 0))) {
839         # Subtract the ObjectGroup cn
840         $base = $1 if $ogroup_entry->dn =~ /cn=$ogroup,ou=groups,(.*)$/;
841         &main::daemon_log("$session_id DEBUG: New base for system with mac address '$mac' is '$base'", 5);
842       }
843     }
845     # prepend ou=systems (configurable through config)
846     $base = $main::new_systems_ou.",".$base;
848     # Search for an existing entry (should be in ou=incoming)
849     my $ldap_mesg= $ldap_handle->search(
850       base => $main::ldap_base,
851       scope => 'sub',
852       filter => "(&(objectClass=GOhard)(|(macAddress=$mac)(dhcpHWaddress=$mac)))",
853     );
855     # TODO: Find a way to guess an ip address for hosts with no ldap entry (MAC->ARP->IP)
856     if($ldap_mesg->count == 1) {
857       &main::daemon_log("$session_id DEBUG: One system with mac address '$mac' was found in base '".$main::ldap_base."'!", 5);
858       # Get the entry from LDAP
859       $ldap_entry= $ldap_mesg->pop_entry();
861       if(!($ldap_entry->dn() eq "cn=".$ldap_entry->get_value('cn').",$base")) {
862         # Move the entry to the new ou
863         $ldap_entry->changetype('moddn');
864         $ldap_entry->add(
865           newrdn => "cn=".$ldap_entry->get_value('cn'),
866           deleteoldrdn => 1,
867           newsuperior => $base,
868         );
869         # To prevent replication problems just re-queue the job with 10 seconds in the future
870         my $moddn_result = $ldap_entry->update($ldap_handle);
871         if ($moddn_result->code() != 0) {
872           my $error_string = "Moving the system with mac address '$mac' to new base '$base' failed (code '".$moddn_result->code()."') with '".$moddn_result->{'errorMessage'}."'!";
873           &main::daemon_log("$session_id ERROR: $error_string", 1);
874           my $sql = "UPDATE $main::job_queue_tn SET status='error', result='$error_string' WHERE id=$jobdb_id";
875           &main::release_ldap_handle($ldap_handle);
876           return undef;
877         } else {
878           &main::daemon_log("$session_id INFO: System with mac address '$mac' was moved to base '".$main::ldap_base."'! Re-queuing job.", 4);
879           $main::job_db->exec_statement("UPDATE ".$main::job_queue_tn." SET status = 'waiting', timestamp = '".(&calc_timestamp(&get_time(), 'plus', 10))."' WHERE id = $jobdb_id");
880           &main::release_ldap_handle($ldap_handle);
881           return undef;
882         }
883       }
885     } elsif ($ldap_mesg->count == 0) {
886       &main::daemon_log("$session_id WARNING: No System with mac address '$mac' was found in base '".$main::ldap_base."'! Re-queuing job.", 4);
887       my $sql_statement = "UPDATE ".$main::job_queue_tn.
888               " SET status='waiting', timestamp = '".(&calc_timestamp(&get_time(), 'plus', 60))."' ".
889               " WHERE id = $jobdb_id";
890       $main::job_db->exec_statement($sql_statement);
891           &main::release_ldap_handle($ldap_handle);
892       return undef;
893     }
895     $ldap_mesg= $ldap_handle->search(
896       base => $main::ldap_base,
897       scope => 'sub',
898       filter => "(&(objectClass=GOhard)(|(macAddress=$mac)(dhcpHWaddress=$mac)))",
899     );
901     # TODO: Find a way to guess an ip address for hosts with no ldap entry (MAC->ARP->IP)
902     if($ldap_mesg->count == 1) {
903       $ldap_entry= $ldap_mesg->pop_entry();
904       # Check for needed objectClasses
905       my $oclasses = $ldap_entry->get_value('objectClass', asref => 1);
906       foreach my $oclass ("FAIobject", "GOhard", "gotoWorkstation") {
907         if(!(scalar grep $_ eq $oclass, map {$_ => 1} @$oclasses)) {
908           &main::daemon_log("$session_id INFO: Adding objectClass '$oclass' to system entry with mac adress '$mac'", 1);
909           $ldap_entry->add(
910             objectClass => $oclass,
911           );
912           my $oclass_result = $ldap_entry->update($ldap_handle);
913           if ($oclass_result->code() != 0) {
914             &main::daemon_log("$session_id ERROR: Adding the ObjectClass '$oclass' failed (code '".$oclass_result->code()."') with '".$oclass_result->{'errorMessage'}."'!", 1);
915           } else {
916             &main::daemon_log("$session_id DEBUG: Adding the ObjectClass '$oclass' to '".($ldap_entry->dn())."' succeeded!", 5);
917           }
918         }
919       }
921       # Set FAIstate
922       if(defined($ldap_entry->get_value('FAIstate'))) {
923         if(!($ldap_entry->get_value('FAIstate') eq 'install')) {
924           $ldap_entry->replace(
925             'FAIstate' => 'install'
926           );
927           my $replace_result = $ldap_entry->update($ldap_handle);
928           if ($replace_result->code() != 0) {
929             &main::daemon_log("$session_id ERROR: Setting the FAIstate to install failed with code '".$replace_result->code()."') and message '".$replace_result->{'errorMessage'}."'!", 1);
930           } else {
931             &main::daemon_log("$session_id DEBUG: Setting the FAIstate to install for '".($ldap_entry->dn())."' succeeded!", 5);
932           }
933         }
934       } else {
935         $ldap_entry->add(
936           'FAIstate' => 'install'
937         );
938         my $add_result = $ldap_entry->update($ldap_handle);
939         if ($add_result->code() != 0) {
940           &main::daemon_log("$session_id ERROR: Setting the FAIstate to install failed with code '".$add_result->code()."') and message '".$add_result->{'errorMessage'}."'!", 1);
941         } else {
942           &main::daemon_log("$session_id DEBUG: Setting the FAIstate to install for '".($ldap_entry->dn())."' succeeded!", 5);
943         }
944       }
947     } elsif ($ldap_mesg->count == 0) {
948       # TODO: Create a new entry
949       # $ldap_entry = Net::LDAP::Entry->new();
950       # $ldap_entry->dn("cn=$mac,$base");
951       &main::daemon_log("$session_id WARNING: No System with mac address '$mac' was found in base '".$main::ldap_base."'! Re-queuing job.", 4);
952       $main::job_db->exec_statement("UPDATE ".$main::job_queue_tn." SET status = 'waiting', timestamp = '".(&calc_timestamp(&get_time(), 'plus', 60))."' WHERE id = $jobdb_id");
953       &main::release_ldap_handle($ldap_handle);
954       return undef;
955     } else {
956       &main::daemon_log("$session_id ERROR: More than one system with mac address '$mac' was found in base '".$main::ldap_base."'!", 1);
957     }
959     # Add to ObjectGroup
960     my $ogroup_member = $ogroup_entry->get_value('member', asref => 1);
961     if( (!defined($ogroup_member)) ||
962         (!defined($ldap_entry)) ||
963         (!defined($ldap_entry->dn)) ||
964         (!(scalar grep $_ eq $ldap_entry->dn, @{$ogroup_member}))) {
965       $ogroup_entry->add (
966         'member' => $ldap_entry->dn(),
967       );
968       my $ogroup_result = $ogroup_entry->update($ldap_handle);
969       if ($ogroup_result->code() != 0) {
970         &main::daemon_log("$session_id ERROR: Updating the ObjectGroup '$ogroup' failed (code '".$ogroup_result->code()."') with '".$ogroup_result->{'errorMessage'}."'!", 1);
971       } else {
972         &main::daemon_log("$session_id DEBUG: Updating the ObjectGroup '$ogroup' for member '".($ldap_entry->dn())."' succeeded!", 5);
973       }
974     } else {
975       &main::daemon_log("$session_id DEBUG: System with mac address '$mac' is already a member of ObjectGroup '$ogroup'.", 5);
976     }
978     # Finally set gotoMode to active
979     if(defined($ldap_entry->get_value('gotoMode'))) {
980       if(!($ldap_entry->get_value('gotoMode') eq 'active')) {
981         $ldap_entry->replace(
982           'gotoMode' => 'active'
983         );
984         my $activate_result = $ldap_entry->update($ldap_handle);
985         if ($activate_result->code() != 0) {
986           &main::daemon_log("$session_id ERROR: Activating system '".$ldap_entry->dn()."' failed (code '".$activate_result->code()."') with '".$activate_result->{'errorMessage'}."'!", 1);
987         } else {
988           &main::daemon_log("$session_id DEBUG: Activating system '".$ldap_entry->dn()."' succeeded!", 5);
989           $activate_client = 1;
990         }
991       } else {
992           $activate_client = 1;
993       }
994     } else {
995       $ldap_entry->add(
996         'gotoMode' => 'active'
997       );
998       my $activate_result = $ldap_entry->update($ldap_handle);
999       if ($activate_result->code() != 0) {
1000         &main::daemon_log("$session_id ERROR: Activating system '".$ldap_entry->dn()."' failed (code '".$activate_result->code()."') with '".$activate_result->{'errorMessage'}."'!", 1);
1001       } else {
1002         &main::daemon_log("$session_id DEBUG: Activating system '".$ldap_entry->dn()."' succeeded!", 5);
1003         $activate_client = 1;
1004       }
1005     }
1007     if($activate_client == 1) {
1008         &main::daemon_log("$session_id DEBUG: Activating system with mac address '$mac'!", 5);
1010         # Create delivery list
1011         my @out_msg_l;
1013         # Set job to done
1014         $main::job_db->exec_statement("UPDATE jobs SET status = 'done' WHERE id = $jobdb_id");
1016         # create set_activated_for_installation message for delivery
1017         my $out_hash = &create_xml_hash("set_activated_for_installation", $source, $mac);
1018         my $out_msg = &create_xml_string($out_hash);
1019         push(@out_msg_l, $out_msg);
1021         # Return delivery list of messages
1022         &main::release_ldap_handle($ldap_handle);
1023         return @out_msg_l;
1025     } else {
1026       &main::daemon_log("$session_id WARNING: Activating system with mac address '$mac' failed! Re-queuing job.", 4);
1027       $main::job_db->exec_statement("UPDATE ".$main::job_queue_tn." SET status = 'waiting',  timestamp = '".(&calc_timestamp(&get_time(), 'plus', 60))."' WHERE id = $jobdb_id");
1028     }
1029     &main::release_ldap_handle($ldap_handle);
1030     return undef;
1034 ## @method get_hosts_with_module
1035 # Reports all GOsa-si-server providing the given module. 
1036 # @param msg - STRING - xml message with tag get_hosts_with_module
1037 # @param msg_hash - HASHREF - message information parsed into a hash
1038 # @param session_id - INTEGER - POE session id of the processing of this message
1039 # @return out_msg - STRING - feedback to GOsa in success and error case
1040 sub get_hosts_with_module {
1041     my ($msg, $msg_hash, $session_id) = @_;
1042     my $source = @{$msg_hash->{'source'}}[0];
1043     my $target = @{$msg_hash->{'target'}}[0];
1044     my $header= @{$msg_hash->{'header'}}[0];
1045     my $module_name = @{$msg_hash->{'module_name'}}[0];
1046     my $out_hash = &create_xml_hash($header, $target, $source);
1048     # Sanity check of module_name
1049     if ((not exists $msg_hash->{'module_name'}) || (@{$msg_hash->{'module_name'}} != 1))  {
1050         &add_content2xml_hash($out_hash, "error_string", "no module_name specified or module_name tag invalid");
1051         &add_content2xml_hash($out_hash, "error", "module_name");
1052         &main::daemon_log("$session_id ERROR: no module_name specified or module_name tag invalid: $msg", 1); 
1053         return (&create_xml_string($out_hash));
1054     }
1056     my $out_msg = &create_xml_string($out_hash);
1058     # Check localhost for module_name
1059     if (exists @{$main::known_modules->{'GosaPackages'}}[2]->{$module_name}) {
1060         my ($local_ip, $local_port) = split(/:/, $target);
1061         my $network_interface= &get_interface_for_ip($local_ip);
1062         my $local_mac = &get_mac_for_interface($network_interface);
1063         $out_msg =~ s/<\/xml>/<result>host0<\/result> <\/xml>/;
1064         my $host_infos = "<ip>$local_ip</ip>";
1065         $host_infos .= " <mac>$local_mac</mac>"; 
1066         $out_msg =~  s/<\/xml>/\n<answer0> $host_infos <\/answer0> \n <\/xml>/;
1067     }
1069     # Search for opsi hosts in server_db
1070     my $sql = "SELECT * FROM $main::known_server_tn WHERE loaded_modules LIKE '%$module_name%'"; 
1071     my $res = $main::known_server_db->select_dbentry($sql);
1072     while (my ($hit_id, $hit_hash) = each %$res) {
1073         $out_msg =~ s/<\/xml>/<result>host$hit_id<\/result> <\/xml>/;
1074         my $host_infos = "<ip>".$hit_hash->{'hostname'}."</ip>";
1075         $host_infos .= " <mac>".$hit_hash->{'macaddress'}."</mac>"; 
1076         $out_msg =~  s/<\/xml>/\n<answer$hit_id> $host_infos <\/answer$hit_id> \n <\/xml>/;
1077     }
1079     return $out_msg;
1082 # vim:ts=4:shiftwidth:expandtab
1083 1;