5ee945afe0e9cf7af2abbedaef1f1339cbec1171
1 #!/usr/bin/perl
2 #===============================================================================
3 #
4 # FILE: gosa-sd
5 #
6 # USAGE: ./gosa-sd
7 #
8 # DESCRIPTION:
9 #
10 # OPTIONS: ---
11 # REQUIREMENTS: libconfig-inifiles-perl libcrypt-rijndael-perl libxml-simple-perl
12 # libdata-dumper-simple-perl libdbd-sqlite3-perl libnet-ldap-perl
13 # libpoe-perl
14 # BUGS: ---
15 # NOTES:
16 # AUTHOR: (Andreas Rettenberger), <rettenberger@gonicus.de>
17 # COMPANY:
18 # VERSION: 1.0
19 # CREATED: 12.09.2007 08:54:41 CEST
20 # REVISION: ---
21 #===============================================================================
24 use strict;
25 use warnings;
26 use Getopt::Long;
27 use Config::IniFiles;
28 use POSIX;
30 use Fcntl;
31 use IO::Socket::INET;
32 use IO::Handle;
33 use IO::Select;
34 use Symbol qw(qualify_to_ref);
35 use Crypt::Rijndael;
36 use MIME::Base64;
37 use Digest::MD5 qw(md5 md5_hex md5_base64);
38 use XML::Simple;
39 use Data::Dumper;
40 use Sys::Syslog qw( :DEFAULT setlogsock);
41 use Cwd;
42 use File::Spec;
43 use File::Basename;
44 use File::Find;
45 use File::Copy;
46 use File::Path;
47 use GOSA::DBsqlite;
48 use GOSA::GosaSupportDaemon;
49 use POE qw(Component::Server::TCP Wheel::Run Filter::Reference);
50 use Net::LDAP;
51 use Net::LDAP::Util qw(:escape);
53 my $modules_path = "/usr/lib/gosa-si/modules";
54 use lib "/usr/lib/gosa-si/modules";
56 # TODO es gibt eine globale funktion get_ldap_handle
57 # - ist in einer session dieses ldap handle schon vorhanden, wird es zurückgegeben
58 # - ist es nicht vorhanden, wird es erzeugt, im heap für spätere ldap anfragen gespeichert und zurückgegeben
59 # - sessions die kein ldap handle brauchen, sollen auch keins haben
60 # - wird eine session geschlossen, muss das ldap verbindung vorher beendet werden
61 our $global_kernel;
63 my (%cfg_defaults, $foreground, $verbose, $ping_timeout);
64 my ($bus_activ, $bus, $msg_to_bus, $bus_cipher);
65 my ($server);
66 my ($gosa_server, $job_queue_timeout, $job_queue_loop_delay);
67 my ($messaging_db_loop_delay);
68 my ($known_modules);
69 my ($pid_file, $procid, $pid, $log_file);
70 my ($arp_activ, $arp_fifo);
71 my ($xml);
72 my $sources_list;
73 my $max_clients;
74 my %repo_files=();
75 my $repo_path;
76 my %repo_dirs=();
77 # variables declared in config file are always set to 'our'
78 our (%cfg_defaults, $log_file, $pid_file,
79 $server_ip, $server_port, $SIPackages_key,
80 $arp_activ, $gosa_unit_tag,
81 $GosaPackages_key, $gosa_ip, $gosa_port, $gosa_timeout,
82 );
84 # additional variable which should be globaly accessable
85 our $server_address;
86 our $server_mac_address;
87 our $bus_address;
88 our $gosa_address;
89 our $no_bus;
90 our $no_arp;
91 our $verbose;
92 our $forground;
93 our $cfg_file;
94 #our ($ldap_handle, $ldap_uri, $ldap_base, $ldap_admin_dn, $ldap_admin_password, $ldap_server_dn);
95 our ($ldap_uri, $ldap_base, $ldap_admin_dn, $ldap_admin_password, $ldap_server_dn);
98 # specifies the verbosity of the daemon_log
99 $verbose = 0 ;
101 # if foreground is not null, script will be not forked to background
102 $foreground = 0 ;
104 # specifies the timeout seconds while checking the online status of a registrating client
105 $ping_timeout = 5;
107 $no_bus = 0;
108 $bus_activ = "true";
110 $no_arp = 0;
112 our $prg= basename($0);
114 # holds all gosa jobs
115 our $job_db;
116 our $job_queue_tn = 'jobs';
117 my $job_queue_file_name;
118 my @job_queue_col_names = ("id INTEGER",
119 "timestamp",
120 "status DEFAULT 'none'",
121 "result DEFAULT 'none'",
122 "progress DEFAULT 'none'",
123 "headertag DEFAULT 'none'",
124 "targettag DEFAULT 'none'",
125 "xmlmessage DEFAULT 'none'",
126 "macaddress DEFAULT 'none'",
127 );
129 # holds all other gosa-sd as well as the gosa-sd-bus
130 our $known_server_db;
131 our $known_server_tn = "known_server";
132 my $known_server_file_name;
133 my @known_server_col_names = ('hostname', 'status', 'hostkey', 'timestamp');
135 # holds all registrated clients
136 our $known_clients_db;
137 our $known_clients_tn = "known_clients";
138 my $known_clients_file_name;
139 my @known_clients_col_names = ('hostname', 'status', 'hostkey', 'timestamp', 'macaddress', 'events');
141 # holds all logged in user at each client
142 our $login_users_db;
143 our $login_users_tn = "login_users";
144 my $login_users_file_name;
145 my @login_users_col_names = ('client', 'user', 'timestamp');
147 # holds all fai server, the debian release and tag
148 our $fai_server_db;
149 our $fai_server_tn = "fai_server";
150 my $fai_server_file_name;
151 our @fai_server_col_names = ('timestamp', 'server', 'release', 'sections', 'tag');
152 our $fai_release_tn = "fai_release";
153 our @fai_release_col_names = ('timestamp', 'release', 'class', 'type', 'state');
155 # holds all packages available from different repositories
156 our $packages_list_db;
157 our $packages_list_tn = "packages_list";
158 my $packages_list_file_name;
159 our @packages_list_col_names = ('distribution', 'package', 'version', 'section', 'description', 'template', 'timestamp');
160 my $outdir = "/tmp/packages_list_db";
161 my $arch = "i386";
163 # holds all messages which should be delivered to a user
164 our $messaging_db;
165 our $messaging_tn = "messaging";
166 our @messaging_col_names = ('subject', 'message_from', 'message_to', 'flag', 'direction', 'delivery_time', 'message', 'timestamp', 'id INTEGER' );
167 my $messaging_file_name;
169 # path to directory to store client install log files
170 our $client_fai_log_dir = "/var/log/fai";
172 # queue which stores taskes until one of the $max_children children are ready to process the task
173 my @tasks = qw();
174 my $max_children = 2;
177 %cfg_defaults = (
178 "general" => {
179 "log-file" => [\$log_file, "/var/run/".$prg.".log"],
180 "pid-file" => [\$pid_file, "/var/run/".$prg.".pid"],
181 },
182 "bus" => {
183 "activ" => [\$bus_activ, "true"],
184 },
185 "server" => {
186 "port" => [\$server_port, "20081"],
187 "known-clients" => [\$known_clients_file_name, '/var/lib/gosa-si/clients.db' ],
188 "known-servers" => [\$known_server_file_name, '/var/lib/gosa-si/servers.db'],
189 "login-users" => [\$login_users_file_name, '/var/lib/gosa-si/users.db'],
190 "fai-server" => [\$fai_server_file_name, '/var/lib/gosa-si/fai.db'],
191 "packages-list" => [\$packages_list_file_name, '/var/lib/gosa-si/packages.db'],
192 "messaging" => [\$messaging_file_name, '/var/lib/gosa-si/messaging.db'],
193 "source-list" => [\$sources_list, '/etc/apt/sources.list'],
194 "repo-path" => [\$repo_path, '/srv/www/repository'],
195 "ldap-uri" => [\$ldap_uri, ""],
196 "ldap-base" => [\$ldap_base, ""],
197 "ldap-admin-dn" => [\$ldap_admin_dn, ""],
198 "ldap-admin-password" => [\$ldap_admin_password, ""],
199 "gosa-unit-tag" => [\$gosa_unit_tag, ""],
200 "max-clients" => [\$max_clients, 10],
201 },
202 "GOsaPackages" => {
203 "ip" => [\$gosa_ip, "0.0.0.0"],
204 "port" => [\$gosa_port, "20082"],
205 "job-queue" => [\$job_queue_file_name, '/var/lib/gosa-si/jobs.db'],
206 "job-queue-loop-delay" => [\$job_queue_loop_delay, 3],
207 "messaging-db-loop-delay" => [\$messaging_db_loop_delay, 3],
208 "key" => [\$GosaPackages_key, "none"],
209 },
210 "SIPackages" => {
211 "key" => [\$SIPackages_key, "none"],
212 },
213 );
216 #=== FUNCTION ================================================================
217 # NAME: usage
218 # PARAMETERS: nothing
219 # RETURNS: nothing
220 # DESCRIPTION: print out usage text to STDERR
221 #===============================================================================
222 sub usage {
223 print STDERR << "EOF" ;
224 usage: $prg [-hvf] [-c config]
226 -h : this (help) message
227 -c <file> : config file
228 -f : foreground, process will not be forked to background
229 -v : be verbose (multiple to increase verbosity)
230 -no-bus : starts $prg without connection to bus
231 -no-arp : starts $prg without connection to arp module
233 EOF
234 print "\n" ;
235 }
238 #=== FUNCTION ================================================================
239 # NAME: read_configfile
240 # PARAMETERS: cfg_file - string -
241 # RETURNS: nothing
242 # DESCRIPTION: read cfg_file and set variables
243 #===============================================================================
244 sub read_configfile {
245 my $cfg;
246 if( defined( $cfg_file) && ( (-s $cfg_file) > 0 )) {
247 if( -r $cfg_file ) {
248 $cfg = Config::IniFiles->new( -file => $cfg_file );
249 } else {
250 print STDERR "Couldn't read config file!\n";
251 }
252 } else {
253 $cfg = Config::IniFiles->new() ;
254 }
255 foreach my $section (keys %cfg_defaults) {
256 foreach my $param (keys %{$cfg_defaults{ $section }}) {
257 my $pinfo = $cfg_defaults{ $section }{ $param };
258 ${@$pinfo[ 0 ]} = $cfg->val( $section, $param, @$pinfo[ 1 ] );
259 }
260 }
261 }
264 #=== FUNCTION ================================================================
265 # NAME: logging
266 # PARAMETERS: level - string - default 'info'
267 # msg - string -
268 # facility - string - default 'LOG_DAEMON'
269 # RETURNS: nothing
270 # DESCRIPTION: function for logging
271 #===============================================================================
272 sub daemon_log {
273 # log into log_file
274 my( $msg, $level ) = @_;
275 if(not defined $msg) { return }
276 if(not defined $level) { $level = 1 }
277 if(defined $log_file){
278 open(LOG_HANDLE, ">>$log_file");
279 if(not defined open( LOG_HANDLE, ">>$log_file" )) {
280 print STDERR "cannot open $log_file: $!";
281 return }
282 chomp($msg);
283 if($level <= $verbose){
284 my ($seconds, $minutes, $hours, $monthday, $month,
285 $year, $weekday, $yearday, $sommertime) = localtime(time);
286 $hours = $hours < 10 ? $hours = "0".$hours : $hours;
287 $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes;
288 $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds;
289 my @monthnames = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
290 $month = $monthnames[$month];
291 $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday;
292 $year+=1900;
293 my $name = $prg;
295 my $log_msg = "$month $monthday $hours:$minutes:$seconds $name $msg\n";
296 print LOG_HANDLE $log_msg;
297 if( $foreground ) {
298 print STDERR $log_msg;
299 }
300 }
301 close( LOG_HANDLE );
302 }
303 }
306 #=== FUNCTION ================================================================
307 # NAME: check_cmdline_param
308 # PARAMETERS: nothing
309 # RETURNS: nothing
310 # DESCRIPTION: validates commandline parameter
311 #===============================================================================
312 sub check_cmdline_param () {
313 my $err_config;
314 my $err_counter = 0;
315 if(not defined($cfg_file)) {
316 $cfg_file = "/etc/gosa-si/server.conf";
317 if(! -r $cfg_file) {
318 $err_config = "please specify a config file";
319 $err_counter += 1;
320 }
321 }
322 if( $err_counter > 0 ) {
323 &usage( "", 1 );
324 if( defined( $err_config)) { print STDERR "$err_config\n"}
325 print STDERR "\n";
326 exit( -1 );
327 }
328 }
331 #=== FUNCTION ================================================================
332 # NAME: check_pid
333 # PARAMETERS: nothing
334 # RETURNS: nothing
335 # DESCRIPTION: handels pid processing
336 #===============================================================================
337 sub check_pid {
338 $pid = -1;
339 # Check, if we are already running
340 if( open(LOCK_FILE, "<$pid_file") ) {
341 $pid = <LOCK_FILE>;
342 if( defined $pid ) {
343 chomp( $pid );
344 if( -f "/proc/$pid/stat" ) {
345 my($stat) = `cat /proc/$pid/stat` =~ m/$pid \((.+)\).*/;
346 if( $stat ) {
347 daemon_log("ERROR: Already running",1);
348 close( LOCK_FILE );
349 exit -1;
350 }
351 }
352 }
353 close( LOCK_FILE );
354 unlink( $pid_file );
355 }
357 # create a syslog msg if it is not to possible to open PID file
358 if (not sysopen(LOCK_FILE, $pid_file, O_WRONLY|O_CREAT|O_EXCL, 0644)) {
359 my($msg) = "Couldn't obtain lockfile '$pid_file' ";
360 if (open(LOCK_FILE, '<', $pid_file)
361 && ($pid = <LOCK_FILE>))
362 {
363 chomp($pid);
364 $msg .= "(PID $pid)\n";
365 } else {
366 $msg .= "(unable to read PID)\n";
367 }
368 if( ! ($foreground) ) {
369 openlog( $0, "cons,pid", "daemon" );
370 syslog( "warning", $msg );
371 closelog();
372 }
373 else {
374 print( STDERR " $msg " );
375 }
376 exit( -1 );
377 }
378 }
380 #=== FUNCTION ================================================================
381 # NAME: import_modules
382 # PARAMETERS: module_path - string - abs. path to the directory the modules
383 # are stored
384 # RETURNS: nothing
385 # DESCRIPTION: each file in module_path which ends with '.pm' and activation
386 # state is on is imported by "require 'file';"
387 #===============================================================================
388 sub import_modules {
389 daemon_log(" ", 1);
391 if (not -e $modules_path) {
392 daemon_log("ERROR: cannot find directory or directory is not readable: $modules_path", 1);
393 }
395 opendir (DIR, $modules_path) or die "ERROR while loading modules from directory $modules_path : $!\n";
396 while (defined (my $file = readdir (DIR))) {
397 if (not $file =~ /(\S*?).pm$/) {
398 next;
399 }
400 my $mod_name = $1;
402 if( $file =~ /ArpHandler.pm/ ) {
403 if( $no_arp > 0 ) {
404 next;
405 }
406 }
408 eval { require $file; };
409 if ($@) {
410 daemon_log("ERROR: gosa-si-server could not load module $file", 1);
411 daemon_log("$@", 5);
412 } else {
413 my $info = eval($mod_name.'::get_module_info()');
414 # Only load module if get_module_info() returns a non-null object
415 if( $info ) {
416 my ($input_address, $input_key, $input, $input_active, $input_type) = @{$info};
417 $known_modules->{$mod_name} = $info;
418 daemon_log("INFO: module $mod_name loaded", 5);
419 }
420 }
421 }
422 close (DIR);
423 }
426 #=== FUNCTION ================================================================
427 # NAME: sig_int_handler
428 # PARAMETERS: signal - string - signal arose from system
429 # RETURNS: noting
430 # DESCRIPTION: handels tasks to be done befor signal becomes active
431 #===============================================================================
432 sub sig_int_handler {
433 my ($signal) = @_;
435 # if (defined($ldap_handle)) {
436 # $ldap_handle->disconnect;
437 # }
438 # TODO alle verbliebenden ldap verbindungen aus allen heaps beenden
441 daemon_log("shutting down gosa-si-server", 1);
442 system("kill `ps -C gosa-si-server -o pid=`");
443 }
444 $SIG{INT} = \&sig_int_handler;
447 sub check_key_and_xml_validity {
448 my ($crypted_msg, $module_key, $session_id) = @_;
449 my $msg;
450 my $msg_hash;
451 my $error_string;
452 eval{
453 $msg = &decrypt_msg($crypted_msg, $module_key);
455 if ($msg =~ /<xml>/i){
456 $msg =~ s/\s+/ /g; # just for better daemon_log
457 daemon_log("$session_id DEBUG: decrypted_msg: \n$msg", 8);
458 $msg_hash = $xml->XMLin($msg, ForceArray=>1);
460 ##############
461 # check header
462 if( not exists $msg_hash->{'header'} ) { die "no header specified"; }
463 my $header_l = $msg_hash->{'header'};
464 if( 1 > @{$header_l} ) { die 'empty header tag'; }
465 if( 1 < @{$header_l} ) { die 'more than one header specified'; }
466 my $header = @{$header_l}[0];
467 if( 0 == length $header) { die 'empty string in header tag'; }
469 ##############
470 # check source
471 if( not exists $msg_hash->{'source'} ) { die "no source specified"; }
472 my $source_l = $msg_hash->{'source'};
473 if( 1 > @{$source_l} ) { die 'empty source tag'; }
474 if( 1 < @{$source_l} ) { die 'more than one source specified'; }
475 my $source = @{$source_l}[0];
476 if( 0 == length $source) { die 'source error'; }
478 ##############
479 # check target
480 if( not exists $msg_hash->{'target'} ) { die "no target specified"; }
481 my $target_l = $msg_hash->{'target'};
482 if( 1 > @{$target_l} ) { die 'empty target tag'; }
483 }
484 };
485 if($@) {
486 daemon_log("$session_id DEBUG: do not understand the message: $@", 7);
487 $msg = undef;
488 $msg_hash = undef;
489 }
491 return ($msg, $msg_hash);
492 }
495 sub check_outgoing_xml_validity {
496 my ($msg) = @_;
498 my $msg_hash;
499 eval{
500 $msg_hash = $xml->XMLin($msg, ForceArray=>1);
502 ##############
503 # check header
504 my $header_l = $msg_hash->{'header'};
505 if( 1 != @{$header_l} ) {
506 die 'no or more than one headers specified';
507 }
508 my $header = @{$header_l}[0];
509 if( 0 == length $header) {
510 die 'header has length 0';
511 }
513 ##############
514 # check source
515 my $source_l = $msg_hash->{'source'};
516 if( 1 != @{$source_l} ) {
517 die 'no or more than 1 sources specified';
518 }
519 my $source = @{$source_l}[0];
520 if( 0 == length $source) {
521 die 'source has length 0';
522 }
523 unless( $source =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
524 $source =~ /^GOSA$/i ) {
525 die "source '$source' is neither a complete ip-address with port nor 'GOSA'";
526 }
528 ##############
529 # check target
530 my $target_l = $msg_hash->{'target'};
531 if( 0 == @{$target_l} ) {
532 die "no targets specified";
533 }
534 foreach my $target (@$target_l) {
535 if( 0 == length $target) {
536 die "target has length 0";
537 }
538 unless( $target =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
539 $target =~ /^GOSA$/i ||
540 $target =~ /^\*$/ ||
541 $target =~ /KNOWN_SERVER/i ||
542 $target =~ /JOBDB/i ||
543 $target =~ /^([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 ){
544 die "target '$target' is not a complete ip-address with port or a valid target name or a mac-address";
545 }
546 }
547 };
548 if($@) {
549 daemon_log("WARNING: outgoing msg is not gosa-si envelope conform", 5);
550 daemon_log("$@ ".(defined($msg) && length($msg)>0)?$msg:"Empty Message", 8);
551 $msg_hash = undef;
552 }
554 return ($msg_hash);
555 }
558 sub input_from_known_server {
559 my ($input, $remote_ip, $session_id) = @_ ;
560 my ($msg, $msg_hash, $module);
562 my $sql_statement= "SELECT * FROM known_server";
563 my $query_res = $known_server_db->select_dbentry( $sql_statement );
565 while( my ($hit_num, $hit) = each %{ $query_res } ) {
566 my $host_name = $hit->{hostname};
567 if( not $host_name =~ "^$remote_ip") {
568 next;
569 }
570 my $host_key = $hit->{hostkey};
571 daemon_log("$session_id DEBUG: input_from_known_server: host_name: $host_name", 7);
572 daemon_log("DEBUG: input_from_known_server: host_key: $host_key", 7);
574 # check if module can open msg envelope with module key
575 my ($tmp_msg, $tmp_msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
576 if( (!$tmp_msg) || (!$tmp_msg_hash) ) {
577 daemon_log("$session_id DEBUG: input_from_known_server: deciphering raise error", 7);
578 daemon_log("$@", 8);
579 next;
580 }
581 else {
582 $msg = $tmp_msg;
583 $msg_hash = $tmp_msg_hash;
584 $module = "SIPackages";
585 last;
586 }
587 }
589 if( (!$msg) || (!$msg_hash) || (!$module) ) {
590 daemon_log("$session_id DEBUG: Incoming message is not from a known server", 7);
591 }
593 return ($msg, $msg_hash, $module);
594 }
597 sub input_from_known_client {
598 my ($input, $remote_ip, $session_id) = @_ ;
599 my ($msg, $msg_hash, $module);
601 my $sql_statement= "SELECT * FROM known_clients";
602 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
603 while( my ($hit_num, $hit) = each %{ $query_res } ) {
604 my $host_name = $hit->{hostname};
605 if( not $host_name =~ /^$remote_ip:\d*$/) {
606 next;
607 }
608 my $host_key = $hit->{hostkey};
609 &daemon_log("$session_id DEBUG: input_from_known_client: host_name: $host_name", 7);
610 &daemon_log("$session_id DEBUG: input_from_known_client: host_key: $host_key", 7);
612 # check if module can open msg envelope with module key
613 ($msg, $msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
615 if( (!$msg) || (!$msg_hash) ) {
616 &daemon_log("$session_id DEGUG: input_from_known_client: deciphering raise error", 7);
617 &daemon_log("$@", 8);
618 next;
619 }
620 else {
621 $module = "SIPackages";
622 last;
623 }
624 }
626 if( (!$msg) || (!$msg_hash) || (!$module) ) {
627 &daemon_log("$session_id DEBUG: Incoming message is not from a known client", 7);
628 }
630 return ($msg, $msg_hash, $module);
631 }
634 sub input_from_unknown_host {
635 no strict "refs";
636 my ($input, $session_id) = @_ ;
637 my ($msg, $msg_hash, $module);
638 my $error_string;
640 my %act_modules = %$known_modules;
642 while( my ($mod, $info) = each(%act_modules)) {
644 # check a key exists for this module
645 my $module_key = ${$mod."_key"};
646 if( not defined $module_key ) {
647 if( $mod eq 'ArpHandler' ) {
648 next;
649 }
650 daemon_log("$session_id ERROR: no key specified in config file for $mod", 1);
651 next;
652 }
653 daemon_log("$session_id DEBUG: $mod: $module_key", 7);
655 # check if module can open msg envelope with module key
656 ($msg, $msg_hash) = &check_key_and_xml_validity($input, $module_key, $session_id);
657 if( (not defined $msg) || (not defined $msg_hash) ) {
658 next;
659 }
660 else {
661 $module = $mod;
662 last;
663 }
664 }
666 if( (!$msg) || (!$msg_hash) || (!$module)) {
667 daemon_log("$session_id DEBUG: Incoming message is not from an unknown host", 7);
668 }
670 return ($msg, $msg_hash, $module);
671 }
674 sub create_ciphering {
675 my ($passwd) = @_;
676 if((!defined($passwd)) || length($passwd)==0) {
677 $passwd = "";
678 }
679 $passwd = substr(md5_hex("$passwd") x 32, 0, 32);
680 my $iv = substr(md5_hex('GONICUS GmbH'),0, 16);
681 my $my_cipher = Crypt::Rijndael->new($passwd , Crypt::Rijndael::MODE_CBC());
682 $my_cipher->set_iv($iv);
683 return $my_cipher;
684 }
687 sub encrypt_msg {
688 my ($msg, $key) = @_;
689 my $my_cipher = &create_ciphering($key);
690 my $len;
691 {
692 use bytes;
693 $len= 16-length($msg)%16;
694 }
695 $msg = "\0"x($len).$msg;
696 $msg = $my_cipher->encrypt($msg);
697 chomp($msg = &encode_base64($msg));
698 # there are no newlines allowed inside msg
699 $msg=~ s/\n//g;
700 return $msg;
701 }
704 sub decrypt_msg {
706 my ($msg, $key) = @_ ;
707 $msg = &decode_base64($msg);
708 my $my_cipher = &create_ciphering($key);
709 $msg = $my_cipher->decrypt($msg);
710 $msg =~ s/\0*//g;
711 return $msg;
712 }
715 sub get_encrypt_key {
716 my ($target) = @_ ;
717 my $encrypt_key;
718 my $error = 0;
720 # target can be in known_server
721 if( not defined $encrypt_key ) {
722 my $sql_statement= "SELECT * FROM known_server WHERE hostname='$target'";
723 my $query_res = $known_server_db->select_dbentry( $sql_statement );
724 while( my ($hit_num, $hit) = each %{ $query_res } ) {
725 my $host_name = $hit->{hostname};
726 if( $host_name ne $target ) {
727 next;
728 }
729 $encrypt_key = $hit->{hostkey};
730 last;
731 }
732 }
734 # target can be in known_client
735 if( not defined $encrypt_key ) {
736 my $sql_statement= "SELECT * FROM known_clients WHERE hostname='$target'";
737 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
738 while( my ($hit_num, $hit) = each %{ $query_res } ) {
739 my $host_name = $hit->{hostname};
740 if( $host_name ne $target ) {
741 next;
742 }
743 $encrypt_key = $hit->{hostkey};
744 last;
745 }
746 }
748 return $encrypt_key;
749 }
752 #=== FUNCTION ================================================================
753 # NAME: open_socket
754 # PARAMETERS: PeerAddr string something like 192.168.1.1 or 192.168.1.1:10000
755 # [PeerPort] string necessary if port not appended by PeerAddr
756 # RETURNS: socket IO::Socket::INET
757 # DESCRIPTION: open a socket to PeerAddr
758 #===============================================================================
759 sub open_socket {
760 my ($PeerAddr, $PeerPort) = @_ ;
761 if(defined($PeerPort)){
762 $PeerAddr = $PeerAddr.":".$PeerPort;
763 }
764 my $socket;
765 $socket = new IO::Socket::INET(PeerAddr => $PeerAddr,
766 Porto => "tcp",
767 Type => SOCK_STREAM,
768 Timeout => 5,
769 );
770 if(not defined $socket) {
771 return;
772 }
773 # &daemon_log("DEBUG: open_socket: $PeerAddr", 7);
774 return $socket;
775 }
778 #=== FUNCTION ================================================================
779 # NAME: get_ip
780 # PARAMETERS: interface name (i.e. eth0)
781 # RETURNS: (ip address)
782 # DESCRIPTION: Uses ioctl to get ip address directly from system.
783 #===============================================================================
784 sub get_ip {
785 my $ifreq= shift;
786 my $result= "";
787 my $SIOCGIFADDR= 0x8915; # man 2 ioctl_list
788 my $proto= getprotobyname('ip');
790 socket SOCKET, PF_INET, SOCK_DGRAM, $proto
791 or die "socket: $!";
793 if(ioctl SOCKET, $SIOCGIFADDR, $ifreq) {
794 my ($if, $sin) = unpack 'a16 a16', $ifreq;
795 my ($port, $addr) = sockaddr_in $sin;
796 my $ip = inet_ntoa $addr;
798 if ($ip && length($ip) > 0) {
799 $result = $ip;
800 }
801 }
803 return $result;
804 }
807 sub get_local_ip_for_remote_ip {
808 my $remote_ip= shift;
809 my $result="0.0.0.0";
811 if($remote_ip =~ /^(\d\d?\d?\.){3}\d\d?\d?$/) {
812 if($remote_ip eq "127.0.0.1") {
813 $result = "127.0.0.1";
814 } else {
815 my $PROC_NET_ROUTE= ('/proc/net/route');
817 open(PROC_NET_ROUTE, "<$PROC_NET_ROUTE")
818 or die "Could not open $PROC_NET_ROUTE";
820 my @ifs = <PROC_NET_ROUTE>;
822 close(PROC_NET_ROUTE);
824 # Eat header line
825 shift @ifs;
826 chomp @ifs;
827 foreach my $line(@ifs) {
828 my ($Iface,$Destination,$Gateway,$Flags,$RefCnt,$Use,$Metric,$Mask,$MTU,$Window,$IRTT)=split(/\s/, $line);
829 my $destination;
830 my $mask;
831 my ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Destination);
832 $destination= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
833 ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Mask);
834 $mask= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
835 if(new NetAddr::IP($remote_ip)->within(new NetAddr::IP($destination, $mask))) {
836 # destination matches route, save mac and exit
837 $result= &get_ip($Iface);
838 last;
839 }
840 }
841 }
842 } else {
843 daemon_log("get_local_ip_for_remote_ip was called with a non-ip parameter: $remote_ip", 1);
844 }
845 return $result;
846 }
849 sub send_msg_to_target {
850 my ($msg, $address, $encrypt_key, $msg_header, $session_id) = @_ ;
851 my $error = 0;
852 my $header;
853 my $new_status;
854 my $act_status;
855 my ($sql_statement, $res);
857 if( $msg_header ) {
858 $header = "'$msg_header'-";
859 } else {
860 $header = "";
861 }
863 # Patch the source ip
864 if($msg =~ /<source>0\.0\.0\.0:\d*?<\/source>/) {
865 my $remote_ip = &get_local_ip_for_remote_ip(sprintf("%s", $address =~ /^([0-9\.]*?):.*$/));
866 $msg =~ s/<source>(0\.0\.0\.0):(\d*?)<\/source>/<source>$remote_ip:$2<\/source>/s;
867 }
869 # encrypt xml msg
870 my $crypted_msg = &encrypt_msg($msg, $encrypt_key);
872 # opensocket
873 my $socket = &open_socket($address);
874 if( !$socket ) {
875 daemon_log("$session_id ERROR: cannot send ".$header."msg to $address , host not reachable", 1);
876 $error++;
877 }
879 if( $error == 0 ) {
880 # send xml msg
881 print $socket $crypted_msg."\n";
883 daemon_log("$session_id INFO: send ".$header."msg to $address", 5);
884 #daemon_log("DEBUG: message:\n$msg", 9);
886 }
888 # close socket in any case
889 if( $socket ) {
890 close $socket;
891 }
893 if( $error > 0 ) { $new_status = "down"; }
894 else { $new_status = $msg_header; }
897 # known_clients
898 $sql_statement = "SELECT * FROM known_clients WHERE hostname='$address'";
899 $res = $known_clients_db->select_dbentry($sql_statement);
900 if( keys(%$res) > 0) {
901 $act_status = $res->{1}->{'status'};
902 if( $act_status eq "down" ) {
903 $sql_statement = "DELETE FROM known_clients WHERE hostname='$address'";
904 $res = $known_clients_db->del_dbentry($sql_statement);
905 daemon_log("$session_id WARNING: failed 2x to send msg to host '$address', delete host from known_clients", 3);
906 } else {
907 $sql_statement = "UPDATE known_clients SET status='$new_status' WHERE hostname='$address'";
908 $res = $known_clients_db->update_dbentry($sql_statement);
909 if($new_status eq "down"){
910 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
911 } else {
912 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
913 }
914 }
915 }
917 # known_server
918 $sql_statement = "SELECT * FROM known_server WHERE hostname='$address'";
919 $res = $known_server_db->select_dbentry($sql_statement);
920 if( keys(%$res) > 0 ) {
921 $act_status = $res->{1}->{'status'};
922 if( $act_status eq "down" ) {
923 $sql_statement = "DELETE FROM known_server WHERE hostname='$address'";
924 $res = $known_server_db->del_dbentry($sql_statement);
925 daemon_log("$session_id WARNING: failed 2x to a send msg to host '$address', delete host from known_server", 3);
926 }
927 else {
928 $sql_statement = "UPDATE known_server SET status='$new_status' WHERE hostname='$address'";
929 $res = $known_server_db->update_dbentry($sql_statement);
930 if($new_status eq "down"){
931 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
932 }
933 else {
934 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
935 }
936 }
937 }
938 return $error;
939 }
942 sub update_jobdb_status_for_send_msgs {
943 my ($answer, $error) = @_;
944 if( $answer =~ /<jobdb_id>(\d+)<\/jobdb_id>/ ) {
945 my $jobdb_id = $1;
947 # sending msg faild
948 if( $error ) {
949 if (not $answer =~ /<header>trigger_action_reinstall<\/header>/) {
950 my $sql_statement = "UPDATE $job_queue_tn ".
951 "SET status='error', result='can not deliver msg, please consult log file' ".
952 "WHERE id='$jobdb_id'";
953 my $res = $job_db->update_dbentry($sql_statement);
954 }
956 # sending msg was successful
957 } else {
958 my $sql_statement = "UPDATE $job_queue_tn ".
959 "SET status='done' ".
960 "WHERE id='$jobdb_id' AND status='processed'";
961 my $res = $job_db->update_dbentry($sql_statement);
962 }
963 }
964 }
966 sub _start {
967 my ($kernel) = $_[KERNEL];
968 &trigger_db_loop($kernel);
969 $global_kernel = $kernel;
970 $kernel->yield('create_fai_server_db', $fai_server_tn );
971 $kernel->yield('create_fai_release_db', $fai_release_tn );
972 $kernel->sig(USR1 => "sig_handler");
973 $kernel->sig(USR2 => "create_packages_list_db");
974 }
976 sub sig_handler {
977 my ($kernel, $signal) = @_[KERNEL, ARG0] ;
978 daemon_log("0 INFO got signal '$signal'", 1);
979 $kernel->sig_handled();
980 return;
981 }
983 sub next_task {
984 my ($session, $heap) = @_[SESSION, HEAP];
986 while ( keys( %{ $heap->{task} } ) < $max_children ) {
987 my $next_task = shift @tasks;
988 last unless defined $next_task;
990 my $task = POE::Wheel::Run->new(
991 Program => sub { process_task($session, $heap, $next_task) },
992 StdioFilter => POE::Filter::Reference->new(),
993 StdoutEvent => "task_result",
994 StderrEvent => "task_debug",
995 CloseEvent => "task_done",
996 );
998 $heap->{task}->{ $task->ID } = $task;
999 }
1000 }
1002 sub handle_task_result {
1003 my ($kernel, $heap, $result) = @_[KERNEL, HEAP, ARG0];
1004 my $client_answer = $result->{'answer'};
1005 if( $client_answer =~ s/session_id=(\d+)$// ) {
1006 my $session_id = $1;
1007 if( defined $session_id ) {
1008 my $session_reference = $kernel->ID_id_to_session($session_id);
1009 if( defined $session_reference ) {
1010 $heap = $session_reference->get_heap();
1011 }
1012 }
1014 if(exists $heap->{'client'}) {
1015 $heap->{'client'}->put($client_answer);
1016 }
1017 }
1018 $kernel->sig(CHLD => "child_reap");
1019 }
1021 sub handle_task_debug {
1022 my $result = $_[ARG0];
1023 print STDERR "$result\n";
1024 }
1026 sub handle_task_done {
1027 my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1028 delete $heap->{task}->{$task_id};
1029 $kernel->yield("next_task");
1030 }
1032 sub process_task {
1033 no strict "refs";
1034 my ($session, $heap, $input) = @_;
1035 my $session_id = $session->ID;
1036 my ($msg, $msg_hash, $module);
1037 my $error = 0;
1038 my $answer_l;
1039 my ($answer_header, @answer_target_l, $answer_source);
1040 my $client_answer = "";
1042 daemon_log("", 5);
1043 daemon_log("$session_id INFO: Incoming msg with session ID $session_id from '".$heap->{'remote_ip'}."'", 5);
1044 daemon_log("$session_id DEBUG: Incoming msg:\n$input", 9);
1046 ####################
1047 # check incoming msg
1048 # msg is from a new client or gosa
1049 ($msg, $msg_hash, $module) = &input_from_unknown_host($input, $session_id);
1050 # msg is from a gosa-si-server or gosa-si-bus
1051 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1052 ($msg, $msg_hash, $module) = &input_from_known_server($input, $heap->{'remote_ip'}, $session_id);
1053 }
1054 # msg is from a gosa-si-client
1055 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1056 ($msg, $msg_hash, $module) = &input_from_known_client($input, $heap->{'remote_ip'}, $session_id);
1057 }
1058 # an error occurred
1059 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1060 # if an incoming msg could not be decrypted (maybe a wrong key), send client a ping. If the client
1061 # could not understand a msg from its server the client cause a re-registering process
1062 daemon_log("$session_id INFO cannot understand incoming msg, send 'ping'-msg to all host with ip '".$heap->{remote_ip}."' to cause a re-registering of the client if necessary", 5);
1063 my $sql_statement = "SELECT * FROM $main::known_clients_tn WHERE (hostname LIKE '".$heap->{'remote_ip'}."%')";
1064 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1065 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1066 my $host_name = $hit->{'hostname'};
1067 my $host_key = $hit->{'hostkey'};
1068 my $ping_msg = "<xml> <header>gosa_ping</header> <source>$server_address</source> <target>$host_name</target></xml>";
1069 my $error = &send_msg_to_target($ping_msg, $host_name, $host_key, "gosa_ping", $session_id);
1070 &update_jobdb_status_for_send_msgs($ping_msg, $error);
1071 }
1072 $error++;
1073 }
1075 ######################
1076 # process incoming msg
1077 if( $error == 0) {
1078 daemon_log("$session_id INFO: Incoming msg with header '".@{$msg_hash->{'header'}}[0].
1079 "' from '".$heap->{'remote_ip'}."'", 5);
1080 daemon_log("$session_id DEBUG: Processing module ".$module, 7);
1081 $answer_l = &{ $module."::process_incoming_msg" }($msg, $msg_hash, $session_id);
1083 if ( 0 < @{$answer_l} ) {
1084 my $answer_str = join("\n", @{$answer_l});
1085 daemon_log("$session_id DEBUG: $module: Got answer from module: \n".$answer_str,8);
1086 }
1087 }
1088 if( !$answer_l ) { $error++ };
1090 ########
1091 # answer
1092 if( $error == 0 ) {
1094 foreach my $answer ( @{$answer_l} ) {
1095 # for each answer in answer list
1097 # check outgoing msg to xml validity
1098 my $answer_hash = &check_outgoing_xml_validity($answer);
1099 if( not defined $answer_hash ) {
1100 next;
1101 }
1103 $answer_header = @{$answer_hash->{'header'}}[0];
1104 @answer_target_l = @{$answer_hash->{'target'}};
1105 $answer_source = @{$answer_hash->{'source'}}[0];
1107 # deliver msg to all targets
1108 foreach my $answer_target ( @answer_target_l ) {
1110 # targets of msg are all gosa-si-clients in known_clients_db
1111 if( $answer_target eq "*" ) {
1112 # answer is for all clients
1113 my $sql_statement= "SELECT * FROM known_clients";
1114 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1115 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1116 my $host_name = $hit->{hostname};
1117 my $host_key = $hit->{hostkey};
1118 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1119 &update_jobdb_status_for_send_msgs($answer, $error);
1120 }
1121 }
1123 # targets of msg are all gosa-si-server in known_server_db
1124 elsif( $answer_target eq "KNOWN_SERVER" ) {
1125 # answer is for all server in known_server
1126 my $sql_statement= "SELECT * FROM known_server";
1127 my $query_res = $known_server_db->select_dbentry( $sql_statement );
1128 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1129 my $host_name = $hit->{hostname};
1130 my $host_key = $hit->{hostkey};
1131 $answer =~ s/KNOWN_SERVER/$host_name/g;
1132 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1133 &update_jobdb_status_for_send_msgs($answer, $error);
1134 }
1135 }
1137 # target of msg is GOsa
1138 elsif( $answer_target eq "GOSA" ) {
1139 my $session_id = ($1) if $answer =~ /<session_id>(\d+?)<\/session_id>/;
1140 my $add_on = "";
1141 if( defined $session_id ) {
1142 $add_on = ".session_id=$session_id";
1143 }
1144 # answer is for GOSA and has to returned to connected client
1145 my $gosa_answer = &encrypt_msg($answer, $GosaPackages_key);
1146 $client_answer = $gosa_answer.$add_on;
1147 }
1149 # target of msg is job queue at this host
1150 elsif( $answer_target eq "JOBDB") {
1151 $answer =~ /<header>(\S+)<\/header>/;
1152 my $header;
1153 if( defined $1 ) { $header = $1; }
1154 my $error = &send_msg_to_target($answer, $server_address, $GosaPackages_key, $header, $session_id);
1155 &update_jobdb_status_for_send_msgs($answer, $error);
1156 }
1158 # target of msg is a mac address
1159 elsif( $answer_target =~ /^([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 ) {
1160 daemon_log("$session_id INFO: target is mac address '$answer_target', looking for host in known_clients", 5);
1161 my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$answer_target'";
1162 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1163 my $found_ip_flag = 0;
1164 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1165 my $host_name = $hit->{hostname};
1166 my $host_key = $hit->{hostkey};
1167 $answer =~ s/$answer_target/$host_name/g;
1168 daemon_log("$session_id INFO: found host '$host_name', associated to '$answer_target'", 5);
1169 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1170 &update_jobdb_status_for_send_msgs($answer, $error);
1171 $found_ip_flag++ ;
1172 }
1173 if( $found_ip_flag == 0) {
1174 daemon_log("$session_id WARNING: no host found in known_clients with mac address '$answer_target'", 3);
1175 if( $bus_activ eq "true" ) {
1176 daemon_log("$session_id INFO: try to forward msg '$answer_header' to bus '$bus_address'", 5);
1177 my $sql_statement = "SELECT * FROM known_server WHERE hostname='$bus_address'";
1178 my $query_res = $known_server_db->select_dbentry( $sql_statement );
1179 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1180 my $bus_address = $hit->{hostname};
1181 my $bus_key = $hit->{hostkey};
1182 my $error = &send_msg_to_target($answer, $bus_address, $bus_key, $answer_header, $session_id);
1183 &update_jobdb_status_for_send_msgs($answer, $error);
1184 last;
1185 }
1186 }
1188 }
1190 # answer is for one specific host
1191 } else {
1192 # get encrypt_key
1193 my $encrypt_key = &get_encrypt_key($answer_target);
1194 if( not defined $encrypt_key ) {
1195 # unknown target, forward msg to bus
1196 daemon_log("$session_id WARNING: unknown target '$answer_target'", 3);
1197 if( $bus_activ eq "true" ) {
1198 daemon_log("$session_id INFO: try to forward msg '$answer_header' to bus '$bus_address'", 5);
1199 my $sql_statement = "SELECT * FROM known_server WHERE hostname='$bus_address'";
1200 my $query_res = $known_server_db->select_dbentry( $sql_statement );
1201 my $res_length = keys( %{$query_res} );
1202 if( $res_length == 0 ){
1203 daemon_log("$session_id WARNING: send '$answer_header' to '$bus_address' failed, ".
1204 "no bus found in known_server", 3);
1205 }
1206 else {
1207 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1208 my $bus_key = $hit->{hostkey};
1209 my $error = &send_msg_to_target($answer, $bus_address, $bus_key, $answer_header,$session_id );
1210 &update_jobdb_status_for_send_msgs($answer, $error);
1211 }
1212 }
1213 }
1214 next;
1215 }
1216 my $error = &send_msg_to_target($answer, $answer_target, $encrypt_key, $answer_header,$session_id);
1217 &update_jobdb_status_for_send_msgs($answer, $error);
1218 }
1219 }
1220 }
1221 }
1223 my $filter = POE::Filter::Reference->new();
1224 my %result = (
1225 status => "seems ok to me",
1226 answer => $client_answer,
1227 );
1229 my $output = $filter->put( [ \%result ] );
1230 print @$output;
1233 }
1236 sub trigger_db_loop {
1237 my ($kernel) = @_ ;
1238 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1239 $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay);
1240 $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1241 $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1242 }
1244 sub watch_for_done_jobs {
1245 my ($kernel,$heap) = @_[KERNEL, HEAP];
1247 my $sql_statement = "SELECT * FROM ".$job_queue_tn.
1248 " WHERE status='done'";
1249 my $res = $job_db->select_dbentry( $sql_statement );
1251 while( my ($id, $hit) = each %{$res} ) {
1252 my $jobdb_id = $hit->{id};
1253 my $sql_statement = "DELETE FROM $job_queue_tn WHERE id='$jobdb_id'";
1254 my $res = $job_db->del_dbentry($sql_statement);
1255 }
1257 $kernel->delay_set('watch_for_done_jobs',$job_queue_loop_delay);
1258 }
1260 sub watch_for_new_jobs {
1261 my ($kernel,$heap) = @_[KERNEL, HEAP];
1263 # check gosa job queue for jobs with executable timestamp
1264 my $timestamp = &get_time();
1265 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE status='waiting' AND (CAST (timestamp AS INTEGER) + 120) < $timestamp ORDER BY timestamp";
1266 my $res = $job_db->exec_statement( $sql_statement );
1268 # Merge all new jobs that would do the same actions
1269 my @drops;
1270 my $hits;
1271 foreach my $hit (reverse @{$res} ) {
1272 my $macaddress= lc @{$hit}[8];
1273 my $headertag= @{$hit}[5];
1274 if(defined($hits->{$macaddress}->{$headertag})) {
1275 push @drops, "DELETE FROM $job_queue_tn WHERE id = '$hits->{$macaddress}->{$headertag}[0]'";
1276 }
1277 $hits->{$macaddress}->{$headertag}= $hit;
1278 }
1280 # Delete new jobs with a matching job in state 'processing'
1281 foreach my $macaddress (keys %{$hits}) {
1282 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1283 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1284 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND headertag='$jobdb_headertag' AND status='processing'";
1285 my $res = $job_db->exec_statement( $sql_statement );
1286 foreach my $hit (@{$res}) {
1287 push @drops, "DELETE FROM $job_queue_tn WHERE id = '$jobdb_id'";
1288 }
1289 }
1290 }
1292 # Commit deletion
1293 $job_db->exec_statementlist(\@drops);
1295 # Look for new jobs that could be executed
1296 foreach my $macaddress (keys %{$hits}) {
1298 # Look if there is an executing job
1299 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='processing'";
1300 my $res = $job_db->exec_statement( $sql_statement );
1302 # Skip new jobs for host if there is a processing job
1303 if(defined($res) and defined @{$res}[0]) {
1304 next;
1305 }
1307 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1308 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1309 my $job_msg = @{$hits->{$macaddress}->{$jobdb_headertag}}[7];
1311 daemon_log("J DEBUG: its time to execute $job_msg", 7);
1312 my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$macaddress'";
1313 my $res_hash = $known_clients_db->select_dbentry( $sql_statement );
1315 # expect macaddress is unique!!!!!!
1316 my $target = $res_hash->{1}->{hostname};
1318 # change header
1319 $job_msg =~ s/<header>job_/<header>gosa_/;
1321 # add sqlite_id
1322 $job_msg =~ s/<\/xml>$/<jobdb_id>$jobdb_id<\/jobdb_id><\/xml>/;
1324 $job_msg =~ /<header>(\S+)<\/header>/;
1325 my $header = $1 ;
1326 my $func_error = &send_msg_to_target($job_msg, $server_address, $GosaPackages_key, $header, "J");
1328 # update status in job queue to 'processing'
1329 $sql_statement = "UPDATE $job_queue_tn SET status='processing' WHERE id='$jobdb_id'";
1330 my $res = $job_db->update_dbentry($sql_statement);
1332 # We don't want parallel processing
1333 last;
1334 }
1335 }
1337 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1338 }
1341 sub watch_for_new_messages {
1342 my ($kernel,$heap) = @_[KERNEL, HEAP];
1343 my @coll_user_msg; # collection list of outgoing messages
1345 # check messaging_db for new incoming messages with executable timestamp
1346 my $timestamp = &get_time();
1347 #my $sql_statement = "SELECT * FROM $messaging_tn WHERE (CAST (delivery_time AS INTEGER) + 120) < $timestamp";
1348 my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( (CAST(timestamp AS INTEGER))<$timestamp AND flag='n' AND direction='in' )";
1349 my $res = $messaging_db->exec_statement( $sql_statement );
1351 foreach my $hit (@{$res}) {
1353 # create outgoing messages
1354 my $message_to = @{$hit}[2];
1356 # translate message_to to plain login name
1357 my @reciever_l = ($message_to);
1358 my $message_id = @{$hit}[8];
1360 #add each outgoing msg to messaging_db
1361 my $reciever;
1362 foreach $reciever (@reciever_l) {
1363 my $sql_statement = "INSERT INTO $messaging_tn (subject, message_from, message_to, flag, direction, delivery_time, message, timestamp, id) ".
1364 "VALUES ('".
1365 @{$hit}[0]."', '". # subject
1366 @{$hit}[1]."', '". # message_from
1367 $reciever."', '". # message_to
1368 "none"."', '". # flag
1369 "out"."', '". # direction
1370 @{$hit}[5]."', '". # delivery_time
1371 @{$hit}[6]."', '". # message
1372 $timestamp."', '". # timestamp
1373 @{$hit}[8]. # id
1374 "')";
1375 &daemon_log("M DEBUG: $sql_statement", 1);
1376 my $res = $messaging_db->exec_statement($sql_statement);
1377 &daemon_log("M INFO: message '".@{$hit}[8]."' is prepared for delivery to reciever '$reciever'", 5);
1378 }
1380 # send outgoing messages
1381 my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( flag='p' AND direction='out' )";
1382 my $res = $messaging_db->exec_statement( $sql_statement );
1383 foreach my $hit (@{$res}) {
1384 # add subject, from, to and message to list coll_user_msg
1385 my @user_msg = [@{$hit}[0], @{$hit}[1], $reciever, @{$hit}[6]];
1386 push( @coll_user_msg, \@user_msg);
1387 }
1389 # send outgoing list to myself (gosa-si-server) to deliver each message to user
1390 # reason for this workaround: if to much messages have to be delivered, it can come to
1391 # denial of service problems of the server. so, the incoming message list can be processed
1392 # by a forked child and gosa-si-server is always ready to work.
1393 my $collection_out_msg = &create_xml_hash("collection_user_messages", $server_address, $server_address);
1394 # add to hash 'msg1' => [subject, from, to, message]
1395 # hash to string
1396 # send msg to myself
1397 # TODO
1399 # set incoming message to flag d=deliverd
1400 $sql_statement = "UPDATE $messaging_tn SET flag='p' WHERE id='$message_id'";
1401 &daemon_log("M DEBUG: $sql_statement", 7);
1402 $res = $messaging_db->update_dbentry($sql_statement);
1403 &daemon_log("M INFO: message '".@{$hit}[8]."' is set to flag 'p' (processed)", 5);
1405 }
1407 $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1410 return;
1411 }
1414 sub watch_for_done_messages {
1415 my ($kernel,$heap) = @_[KERNEL, HEAP];
1417 $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1418 return;
1419 }
1422 sub get_ldap_handle {
1423 my ($session_id) = @_;
1424 my $heap;
1425 my $ldap_handle;
1427 if (not defined $session_id ) { $session_id = 0 };
1429 if ($session_id == 0) {
1430 daemon_log("$session_id DEBUG: get_ldap_handle invoked without a session_id, create a new ldap_handle", 7);
1431 $ldap_handle = Net::LDAP->new( $ldap_uri );
1432 $ldap_handle->bind($ldap_admin_dn, password => $ldap_admin_password);
1434 } else {
1435 my $session_reference = $global_kernel->ID_id_to_session($session_id);
1436 if( defined $session_reference ) {
1437 $heap = $session_reference->get_heap();
1438 }
1440 if (not defined $heap) {
1441 daemon_log("$session_id DEBUG: cannot get heap for session_id '$session_id'", 7);
1442 return;
1443 }
1445 # TODO: This "if" is nonsense, because it doesn't prove that the
1446 # used handle is still valid - or if we've to reconnect...
1447 #if (not exists $heap->{ldap_handle}) {
1448 $ldap_handle = Net::LDAP->new( $ldap_uri );
1449 $ldap_handle->bind($ldap_admin_dn, password => $ldap_admin_password);
1450 $heap->{ldap_handle} = $ldap_handle;
1451 #}
1452 }
1453 return $ldap_handle;
1454 }
1457 sub change_fai_state {
1458 my ($st, $targets, $session_id) = @_;
1459 $session_id = 0 if not defined $session_id;
1460 # Set FAI state to localboot
1461 my %mapActions= (
1462 reboot => '',
1463 update => 'softupdate',
1464 localboot => 'localboot',
1465 reinstall => 'install',
1466 rescan => '',
1467 wake => '',
1468 memcheck => 'memcheck',
1469 sysinfo => 'sysinfo',
1470 install => 'install',
1471 );
1473 # Return if this is unknown
1474 if (!exists $mapActions{ $st }){
1475 daemon_log("$session_id ERROR: unknown action '$st', can not translate ot FAIstate", 1);
1476 return;
1477 }
1479 my $state= $mapActions{ $st };
1481 my $ldap_handle = &get_ldap_handle($session_id);
1482 if( defined($ldap_handle) ) {
1484 # Build search filter for hosts
1485 my $search= "(&(objectClass=GOhard)";
1486 foreach (@{$targets}){
1487 $search.= "(macAddress=$_)";
1488 }
1489 $search.= ")";
1491 # If there's any host inside of the search string, procress them
1492 if (!($search =~ /macAddress/)){
1493 daemon_log("$session_id ERROR: no macAddress found in filter statement for LDAP search: '$search'", 1);
1494 return;
1495 }
1497 # Perform search for Unit Tag
1498 my $mesg = $ldap_handle->search(
1499 base => $ldap_base,
1500 scope => 'sub',
1501 attrs => ['dn', 'FAIstate', 'objectClass'],
1502 filter => "$search"
1503 );
1505 if ($mesg->count) {
1506 my @entries = $mesg->entries;
1507 foreach my $entry (@entries) {
1508 # Only modify entry if it is not set to '$state'
1509 if ($entry->get_value("FAIstate") ne "$state"){
1510 daemon_log("$session_id INFO: Setting FAIstate to '$state' for ".$entry->dn, 5);
1511 my $result;
1512 my %tmp = map { $_ => 1 } $entry->get_value("objectClass");
1513 if (exists $tmp{'FAIobject'}){
1514 if ($state eq ''){
1515 $result= $ldap_handle->modify($entry->dn, changes => [
1516 delete => [ FAIstate => [] ] ]);
1517 } else {
1518 $result= $ldap_handle->modify($entry->dn, changes => [
1519 replace => [ FAIstate => $state ] ]);
1520 }
1521 } elsif ($state ne ''){
1522 $result= $ldap_handle->modify($entry->dn, changes => [
1523 add => [ objectClass => 'FAIobject' ],
1524 add => [ FAIstate => $state ] ]);
1525 }
1527 # Errors?
1528 if ($result->code){
1529 daemon_log("$session_id Error: Setting FAIstate to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1530 }
1531 } else {
1532 daemon_log("$session_id DEBUG FAIstate at host '".$entry->dn."' already at state '$st'", 7);
1533 }
1534 }
1535 }
1536 # if no ldap handle defined
1537 } else {
1538 daemon_log("$session_id ERROR: no LDAP handle defined for update FAIstate", 1);
1539 }
1541 }
1544 sub change_goto_state {
1545 my ($st, $targets, $session_id) = @_;
1546 $session_id = 0 if not defined $session_id;
1548 # Switch on or off?
1549 my $state= $st eq 'active' ? 'active': 'locked';
1551 my $ldap_handle = &get_ldap_handle($session_id);
1552 if( defined($ldap_handle) ) {
1554 # Build search filter for hosts
1555 my $search= "(&(objectClass=GOhard)";
1556 foreach (@{$targets}){
1557 $search.= "(macAddress=$_)";
1558 }
1559 $search.= ")";
1561 # If there's any host inside of the search string, procress them
1562 if (!($search =~ /macAddress/)){
1563 return;
1564 }
1566 # Perform search for Unit Tag
1567 my $mesg = $ldap_handle->search(
1568 base => $ldap_base,
1569 scope => 'sub',
1570 attrs => ['dn', 'gotoMode'],
1571 filter => "$search"
1572 );
1574 if ($mesg->count) {
1575 my @entries = $mesg->entries;
1576 foreach my $entry (@entries) {
1578 # Only modify entry if it is not set to '$state'
1579 if ($entry->get_value("gotoMode") ne $state){
1581 daemon_log("$session_id INFO: Setting gotoMode to '$state' for ".$entry->dn, 5);
1582 my $result;
1583 $result= $ldap_handle->modify($entry->dn, changes => [
1584 replace => [ gotoMode => $state ] ]);
1586 # Errors?
1587 if ($result->code){
1588 &daemon_log("$session_id Error: Setting gotoMode to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1589 }
1591 }
1592 }
1593 }
1595 }
1596 }
1599 sub create_fai_server_db {
1600 my ($table_name, $kernel) = @_;
1601 my $result;
1602 my $ldap_handle = &get_ldap_handle();
1603 if(defined($ldap_handle)) {
1604 daemon_log("INFO: create_fai_server_db: start", 5);
1605 my $mesg= $ldap_handle->search(
1606 base => $ldap_base,
1607 scope => 'sub',
1608 attrs => ['FAIrepository', 'gosaUnitTag'],
1609 filter => "(&(FAIrepository=*)(objectClass=FAIrepositoryServer))",
1610 );
1611 if($mesg->{'resultCode'} == 0 &&
1612 $mesg->count != 0) {
1613 foreach my $entry (@{$mesg->{entries}}) {
1614 if($entry->exists('FAIrepository')) {
1615 # Add an entry for each Repository configured for server
1616 foreach my $repo(@{$entry->get_value('FAIrepository', asref => 1)}) {
1617 my($tmp_url,$tmp_server,$tmp_release,$tmp_sections) = split(/\|/, $repo);
1618 my $tmp_tag= $entry->get_value('gosaUnitTag') || "";
1619 $result= $fai_server_db->add_dbentry( {
1620 table => $table_name,
1621 primkey => ['server', 'release', 'tag'],
1622 server => $tmp_url,
1623 release => $tmp_release,
1624 sections => $tmp_sections,
1625 tag => (length($tmp_tag)>0)?$tmp_tag:"",
1626 } );
1627 }
1628 }
1629 }
1630 }
1631 daemon_log("INFO: create_fai_server_db: finished", 5);
1633 # TODO: Find a way to post the 'create_packages_list_db' event
1634 &create_packages_list_db($ldap_handle);
1635 }
1637 $ldap_handle->disconnect;
1638 return $result;
1639 }
1641 sub run_create_fai_server_db {
1642 my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
1643 my $task = POE::Wheel::Run->new(
1644 Program => sub { &create_fai_server_db($table_name,$kernel) },
1645 StdoutEvent => "session_run_result",
1646 StderrEvent => "session_run_debug",
1647 CloseEvent => "session_run_done",
1648 );
1650 $heap->{task}->{ $task->ID } = $task;
1651 return;
1652 }
1655 sub create_fai_release_db {
1656 my ($table_name) = @_;
1657 my $result;
1659 my $ldap_handle = &get_ldap_handle();
1660 if(defined($ldap_handle)) {
1661 daemon_log("INFO: create_fai_release_db: start",5);
1662 my $mesg= $ldap_handle->search(
1663 base => $ldap_base,
1664 scope => 'sub',
1665 attrs => [],
1666 filter => "(&(objectClass=organizationalUnit)(ou=fai))",
1667 );
1668 if($mesg->{'resultCode'} == 0 &&
1669 $mesg->count != 0) {
1670 # Walk through all possible FAI container ou's
1671 my @sql_list;
1672 my $timestamp= &get_time();
1673 foreach my $ou (@{$mesg->{entries}}) {
1674 my $tmp_classes= resolve_fai_classes($ou->dn, $ldap_handle);
1675 if(defined($tmp_classes) && ref($tmp_classes) eq 'HASH') {
1676 my @tmp_array=get_fai_release_entries($tmp_classes);
1677 if(@tmp_array) {
1678 foreach my $entry (@tmp_array) {
1679 if(defined($entry) && ref($entry) eq 'HASH') {
1680 my $sql=
1681 "INSERT INTO $table_name "
1682 ."(timestamp, release, class, type, state) VALUES ("
1683 .$timestamp.","
1684 ."'".$entry->{'release'}."',"
1685 ."'".$entry->{'class'}."',"
1686 ."'".$entry->{'type'}."',"
1687 ."'".$entry->{'state'}."')";
1688 push @sql_list, $sql;
1689 }
1690 }
1691 }
1692 }
1693 }
1694 daemon_log("DEBUG: Inserting ".scalar @sql_list." entries to DB",6);
1695 if(@sql_list) {
1696 unshift @sql_list, "DELETE FROM $table_name";
1697 $fai_server_db->exec_statementlist(\@sql_list);
1698 }
1699 daemon_log("DEBUG: Done with inserting",6);
1700 }
1701 daemon_log("INFO: create_fai_release_db: finished",5);
1702 }
1703 $ldap_handle->disconnect;
1704 return $result;
1705 }
1706 sub run_create_fai_release_db {
1707 my ($session, $heap, $table_name) = @_[SESSION, HEAP, ARG0];
1708 my $task = POE::Wheel::Run->new(
1709 Program => sub { &create_fai_release_db($table_name) },
1710 StdoutEvent => "session_run_result",
1711 StderrEvent => "session_run_debug",
1712 CloseEvent => "session_run_done",
1713 );
1715 $heap->{task}->{ $task->ID } = $task;
1716 return;
1717 }
1719 sub get_fai_types {
1720 my $tmp_classes = shift || return undef;
1721 my @result;
1723 foreach my $type(keys %{$tmp_classes}) {
1724 if(defined($tmp_classes->{$type}[0]) && (!($tmp_classes->{$type}[0] =~ /^.*?removed.*?$/))) {
1725 my $entry = {
1726 type => $type,
1727 state => $tmp_classes->{$type}[0],
1728 };
1729 push @result, $entry;
1730 }
1731 }
1733 return @result;
1734 }
1736 sub get_fai_state {
1737 my $result = "";
1738 my $tmp_classes = shift || return $result;
1740 foreach my $type(keys %{$tmp_classes}) {
1741 if(defined($tmp_classes->{$type}[0])) {
1742 $result = $tmp_classes->{$type}[0];
1744 # State is equal for all types in class
1745 last;
1746 }
1747 }
1749 return $result;
1750 }
1752 sub resolve_fai_classes {
1753 my ($fai_base, $ldap_handle) = @_;
1754 my $result;
1755 my @possible_fai_classes= ("FAIscript", "FAIhook", "FAIpartitionTable", "FAItemplate", "FAIvariable", "FAIprofile", "FAIpackageList");
1756 my $fai_filter= "(|(&(objectClass=FAIclass)(|(objectClass=".join(")(objectClass=", @possible_fai_classes).")))(objectClass=FAIbranch))";
1757 my $fai_classes;
1759 daemon_log("DEBUG: Searching for FAI entries in base $fai_base",6);
1760 my $mesg= $ldap_handle->search(
1761 base => $fai_base,
1762 scope => 'sub',
1763 attrs => ['cn','objectClass','FAIstate'],
1764 filter => $fai_filter,
1765 );
1766 daemon_log("DEBUG: Found ".$mesg->count()." FAI entries",6);
1768 if($mesg->{'resultCode'} == 0 &&
1769 $mesg->count != 0) {
1770 foreach my $entry (@{$mesg->{entries}}) {
1771 if($entry->exists('cn')) {
1772 my $tmp_dn= $entry->dn();
1774 # Skip classname and ou dn parts for class
1775 my $tmp_release = ($1) if $tmp_dn =~ /^[^,]+,[^,]+,(.*?),$fai_base$/;
1777 # Skip classes without releases
1778 if((!defined($tmp_release)) || length($tmp_release)==0) {
1779 next;
1780 }
1782 my $tmp_cn= $entry->get_value('cn');
1783 my $tmp_state= $entry->get_value('FAIstate');
1785 my $tmp_type;
1786 # Get FAI type
1787 for my $oclass(@{$entry->get_value('objectClass', asref => 1)}) {
1788 if(grep $_ eq $oclass, @possible_fai_classes) {
1789 $tmp_type= $oclass;
1790 last;
1791 }
1792 }
1794 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
1795 # A Subrelease
1796 my @sub_releases = split(/,/, $tmp_release);
1798 # Walk through subreleases and build hash tree
1799 my $hash;
1800 while(my $tmp_sub_release = pop @sub_releases) {
1801 $hash .= "\{'$tmp_sub_release'\}->";
1802 }
1803 eval('push @{$fai_classes->'.$hash.'{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";');
1804 } else {
1805 # A branch, no subrelease
1806 push @{$fai_classes->{$tmp_release}->{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";
1807 }
1808 } elsif (!$entry->exists('cn')) {
1809 my $tmp_dn= $entry->dn();
1810 my $tmp_release = ($1) if $tmp_dn =~ /^(.*?),$fai_base$/;
1812 # Skip classes without releases
1813 if((!defined($tmp_release)) || length($tmp_release)==0) {
1814 next;
1815 }
1817 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
1818 # A Subrelease
1819 my @sub_releases= split(/,/, $tmp_release);
1821 # Walk through subreleases and build hash tree
1822 my $hash;
1823 while(my $tmp_sub_release = pop @sub_releases) {
1824 $hash .= "\{'$tmp_sub_release'\}->";
1825 }
1826 # Remove the last two characters
1827 chop($hash);
1828 chop($hash);
1830 eval('$fai_classes->'.$hash.'= {}');
1831 } else {
1832 # A branch, no subrelease
1833 if(!exists($fai_classes->{$tmp_release})) {
1834 $fai_classes->{$tmp_release} = {};
1835 }
1836 }
1837 }
1838 }
1840 # The hash is complete, now we can honor the copy-on-write based missing entries
1841 foreach my $release (keys %$fai_classes) {
1842 $result->{$release}= deep_copy(apply_fai_inheritance($fai_classes->{$release}));
1843 }
1844 }
1845 return $result;
1846 }
1848 sub apply_fai_inheritance {
1849 my $fai_classes = shift || return {};
1850 my $tmp_classes;
1852 # Get the classes from the branch
1853 foreach my $class (keys %{$fai_classes}) {
1854 # Skip subreleases
1855 if($class =~ /^ou=.*$/) {
1856 next;
1857 } else {
1858 $tmp_classes->{$class}= deep_copy($fai_classes->{$class});
1859 }
1860 }
1862 # Apply to each subrelease
1863 foreach my $subrelease (keys %{$fai_classes}) {
1864 if($subrelease =~ /ou=/) {
1865 foreach my $tmp_class (keys %{$tmp_classes}) {
1866 if(!exists($fai_classes->{$subrelease}->{$tmp_class})) {
1867 $fai_classes->{$subrelease}->{$tmp_class} =
1868 deep_copy($tmp_classes->{$tmp_class});
1869 } else {
1870 foreach my $type (keys %{$tmp_classes->{$tmp_class}}) {
1871 if(!exists($fai_classes->{$subrelease}->{$tmp_class}->{$type})) {
1872 $fai_classes->{$subrelease}->{$tmp_class}->{$type}=
1873 deep_copy($tmp_classes->{$tmp_class}->{$type});
1874 }
1875 }
1876 }
1877 }
1878 }
1879 }
1881 # Find subreleases in deeper levels
1882 foreach my $subrelease (keys %{$fai_classes}) {
1883 if($subrelease =~ /ou=/) {
1884 foreach my $subsubrelease (keys %{$fai_classes->{$subrelease}}) {
1885 if($subsubrelease =~ /ou=/) {
1886 apply_fai_inheritance($fai_classes->{$subrelease});
1887 }
1888 }
1889 }
1890 }
1892 return $fai_classes;
1893 }
1895 sub get_fai_release_entries {
1896 my $tmp_classes = shift || return;
1897 my $parent = shift || "";
1898 my @result = shift || ();
1900 foreach my $entry (keys %{$tmp_classes}) {
1901 if(defined($entry)) {
1902 if($entry =~ /^ou=.*$/) {
1903 my $release_name = $entry;
1904 $release_name =~ s/ou=//g;
1905 if(length($parent)>0) {
1906 $release_name = $parent."/".$release_name;
1907 }
1908 my @bufentries = get_fai_release_entries($tmp_classes->{$entry}, $release_name, @result);
1909 foreach my $bufentry(@bufentries) {
1910 push @result, $bufentry;
1911 }
1912 } else {
1913 my @types = get_fai_types($tmp_classes->{$entry});
1914 foreach my $type (@types) {
1915 push @result,
1916 {
1917 'class' => $entry,
1918 'type' => $type->{'type'},
1919 'release' => $parent,
1920 'state' => $type->{'state'},
1921 };
1922 }
1923 }
1924 }
1925 }
1927 return @result;
1928 }
1930 sub deep_copy {
1931 my $this = shift;
1932 if (not ref $this) {
1933 $this;
1934 } elsif (ref $this eq "ARRAY") {
1935 [map deep_copy($_), @$this];
1936 } elsif (ref $this eq "HASH") {
1937 +{map { $_ => deep_copy($this->{$_}) } keys %$this};
1938 } else { die "what type is $_?" }
1939 }
1942 sub session_run_result {
1943 my ($kernel, $heap, $client_answer) = @_[KERNEL, HEAP, ARG0];
1944 $kernel->sig(CHLD => "child_reap");
1945 }
1947 sub session_run_debug {
1948 my $result = $_[ARG0];
1949 print STDERR "$result\n";
1950 }
1952 sub session_run_done {
1953 my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1954 delete $heap->{task}->{$task_id};
1955 }
1957 sub create_sources_list {
1958 my ($ldap_handle) = @_;
1959 my $result="/tmp/gosa_si_tmp_sources_list";
1961 # Remove old file
1962 if(stat($result)) {
1963 unlink($result);
1964 }
1966 my $fh;
1967 open($fh, ">$result") or return undef;
1968 if(defined($ldap_server_dn) and length($ldap_server_dn) > 0) {
1969 my $mesg=$ldap_handle->search(
1970 base => $ldap_server_dn,
1971 scope => 'base',
1972 attrs => 'FAIrepository',
1973 filter => 'objectClass=FAIrepositoryServer'
1974 );
1975 if($mesg->count) {
1976 foreach my $entry(@{$mesg->{'entries'}}) {
1977 foreach my $value(@{$entry->get_value('FAIrepository', asref => 1)}) {
1978 my ($server, $tag, $release, $sections)= split /\|/, $value;
1979 my $line = "deb $server $release";
1980 $sections =~ s/,/ /g;
1981 $line.= " $sections";
1982 print $fh $line."\n";
1983 }
1984 }
1985 }
1986 }
1987 close($fh);
1989 return $result;
1990 }
1992 sub create_packages_list_db {
1993 my ($ldap_handle, $sources_file) = @_ ;
1995 if (not defined $ldap_handle) {
1996 $ldap_handle= &get_ldap_handle();
1998 if (not defined $ldap_handle) {
1999 daemon_log("0 ERROR: no ldap_handle available to create_packages_list_db", 1);
2000 return;
2001 }
2002 }
2004 if (not defined $sources_file) {
2005 $sources_file = &create_sources_list($ldap_handle);
2006 }
2008 my $line;
2009 daemon_log("INFO: create_packages_list_db: start", 5);
2011 open(CONFIG, "<$sources_file") or do {
2012 daemon_log( "ERROR: create_packages_list_db: Failed to open '$sources_file'", 1);
2013 return;
2014 };
2016 # Read lines
2017 while ($line = <CONFIG>){
2018 # Unify
2019 chop($line);
2020 $line =~ s/^\s+//;
2021 $line =~ s/^\s+/ /;
2023 # Strip comments
2024 $line =~ s/#.*$//g;
2026 # Skip empty lines
2027 if ($line =~ /^\s*$/){
2028 next;
2029 }
2031 # Interpret deb line
2032 if ($line =~ /^deb [^\s]+\s[^\s]+\s[^\s]+/){
2033 my( $baseurl, $dist, $sections ) = ($line =~ /^deb\s([^\s]+)\s+([^\s]+)\s+(.*)$/);
2034 my $section;
2035 foreach $section (split(' ', $sections)){
2036 &parse_package_info( $baseurl, $dist, $section );
2037 }
2038 }
2039 }
2041 close (CONFIG);
2043 daemon_log("INFO: create_packages_list_db: finished", 5);
2044 return;
2045 }
2047 sub run_create_packages_list_db {
2048 my ($session, $heap) = @_[SESSION, HEAP];
2049 my $task = POE::Wheel::Run->new(
2050 Program => sub {&create_packages_list_db},
2051 StdoutEvent => "session_run_result",
2052 StderrEvent => "session_run_debug",
2053 CloseEvent => "session_run_done",
2054 );
2055 $heap->{task}->{ $task->ID } = $task;
2056 }
2058 sub parse_package_info {
2059 my ($baseurl, $dist, $section)= @_;
2060 my ($package);
2062 my ($path) = ($baseurl =~ m%://[^/]*(.*)$%);
2063 $repo_dirs{ "${repo_path}/pool" } = 1;
2065 foreach $package ("Packages.gz"){
2066 daemon_log("DEBUG: create_packages_list: fetch $baseurl, $dist, $section", 7);
2067 get_package( "$baseurl/dists/$dist/$section/binary-$arch/$package", "$outdir/$dist/$section" );
2068 parse_package( "$outdir/$dist/$section", $dist, $path );
2069 }
2070 find(\&cleanup_and_extract, keys( %repo_dirs ) );
2071 }
2073 sub get_package {
2074 my ($url, $dest)= @_;
2076 my $tpath = dirname($dest);
2077 -d "$tpath" || mkpath "$tpath";
2079 # This is ugly, but I've no time to take a look at "how it works in perl"
2080 if(0 == system("wget '$url' -O '$dest' 2>/dev/null") ) {
2081 system("gunzip -cd '$dest' > '$dest.in'");
2082 unlink($dest);
2083 } else {
2084 daemon_log("ERROR: create_packages_list_db: get_packages: fetching '$url' failed!", 1);
2085 }
2086 return 0;
2087 }
2089 sub parse_package {
2090 my ($path, $dist, $srv_path)= @_;
2091 my ($package, $version, $section, $description);
2092 my @sql_list;
2093 my $PACKAGES;
2095 if(not stat("$path.in")) {
2096 daemon_log("ERROR: create_packages_list: parse_package: file '$path.in' is not readable",1);
2097 return;
2098 }
2100 open($PACKAGES, "<$path.in");
2101 if(not defined($PACKAGES)) {
2102 daemon_log("ERROR: create_packages_list_db: parse_package: can not open '$path.in'",1);
2103 return;
2104 }
2106 # Read lines
2107 while (<$PACKAGES>){
2108 my $line = $_;
2109 # Unify
2110 chop($line);
2112 # Use empty lines as a trigger
2113 if ($line =~ /^\s*$/){
2114 my $sql = "INSERT INTO packages_list VALUES ('$dist', '$package', '$version', '$section', '', 'none', '0')";
2115 push(@sql_list, $sql);
2116 $package = "none";
2117 $version = "none";
2118 $section = "none";
2119 $description = "none";
2120 next;
2121 }
2123 # Trigger for package name
2124 if ($line =~ /^Package:\s/){
2125 ($package)= ($line =~ /^Package: (.*)$/);
2126 next;
2127 }
2129 # Trigger for version
2130 if ($line =~ /^Version:\s/){
2131 ($version)= ($line =~ /^Version: (.*)$/);
2132 next;
2133 }
2135 # Trigger for description
2136 if ($line =~ /^Description:\s/){
2137 ($description)= ($line =~ /^Description: (.*)$/);
2138 next;
2139 }
2141 # Trigger for section
2142 if ($line =~ /^Section:\s/){
2143 ($section)= ($line =~ /^Section: (.*)$/);
2144 next;
2145 }
2147 # Trigger for filename
2148 if ($line =~ /^Filename:\s/){
2149 my ($filename) = ($line =~ /^Filename: (.*)$/);
2150 store_fileinfo( $package, $filename, $dist, $srv_path, $version, $repo_path );
2151 next;
2152 }
2153 }
2155 close( $PACKAGES );
2156 unlink( "$path.in" );
2158 $packages_list_db->exec_statementlist(\@sql_list);
2159 }
2161 sub store_fileinfo {
2162 my( $package, $file, $dist, $path, $vers, $srvdir) = @_;
2164 my %fileinfo = (
2165 'package' => $package,
2166 'dist' => $dist,
2167 'version' => $vers,
2168 );
2170 $repo_files{ "${srvdir}/$file" } = \%fileinfo;
2171 }
2173 sub cleanup_and_extract {
2174 my $fileinfo = $repo_files{ $File::Find::name };
2176 if( defined $fileinfo ) {
2178 my $dir = "$outdir/$fileinfo->{ 'dist' }/debconf.d";
2179 my $sql;
2180 my $package = $fileinfo->{ 'package' };
2181 my $newver = $fileinfo->{ 'version' };
2183 mkpath($dir);
2184 system( "dpkg -e '$File::Find::name' '$dir/DEBIAN'" );
2186 if( -f "$dir/DEBIAN/templates" ) {
2188 daemon_log("DEBUG: Found debconf templates in '$package' - $newver", 5);
2190 my $tmpl= "";
2191 {
2192 local $/=undef;
2193 open FILE, "$dir/DEBIAN/templates";
2194 $tmpl = &encode_base64(<FILE>);
2195 close FILE;
2196 }
2197 rmtree("$dir/DEBIAN/templates");
2199 $sql= "update $main::packages_list_tn set template = '$tmpl' where package = '$package' and version = '$newver';";
2201 } else {
2202 $sql= "update $main::packages_list_tn set template = '' where package = '$package' and version = '$newver';";
2203 }
2205 my $res= $main::packages_list_db->update_dbentry($sql);
2206 }
2207 }
2210 #==== MAIN = main ==============================================================
2211 # parse commandline options
2212 Getopt::Long::Configure( "bundling" );
2213 GetOptions("h|help" => \&usage,
2214 "c|config=s" => \$cfg_file,
2215 "f|foreground" => \$foreground,
2216 "v|verbose+" => \$verbose,
2217 "no-bus+" => \$no_bus,
2218 "no-arp+" => \$no_arp,
2219 );
2221 # read and set config parameters
2222 &check_cmdline_param ;
2223 &read_configfile;
2224 &check_pid;
2226 $SIG{CHLD} = 'IGNORE';
2228 # forward error messages to logfile
2229 if( ! $foreground ) {
2230 open( STDIN, '+>/dev/null' );
2231 open( STDOUT, '+>&STDIN' );
2232 open( STDERR, '+>&STDIN' );
2233 }
2235 # Just fork, if we are not in foreground mode
2236 if( ! $foreground ) {
2237 chdir '/' or die "Can't chdir to /: $!";
2238 $pid = fork;
2239 setsid or die "Can't start a new session: $!";
2240 umask 0;
2241 } else {
2242 $pid = $$;
2243 }
2245 # Do something useful - put our PID into the pid_file
2246 if( 0 != $pid ) {
2247 open( LOCK_FILE, ">$pid_file" );
2248 print LOCK_FILE "$pid\n";
2249 close( LOCK_FILE );
2250 if( !$foreground ) {
2251 exit( 0 )
2252 };
2253 }
2255 daemon_log(" ", 1);
2256 daemon_log("$0 started!", 1);
2258 if ($no_bus > 0) {
2259 $bus_activ = "false"
2260 }
2262 # connect to gosa-si job queue
2263 $job_db = GOSA::DBsqlite->new($job_queue_file_name);
2264 $job_db->create_table($job_queue_tn, \@job_queue_col_names);
2266 # connect to known_clients_db
2267 $known_clients_db = GOSA::DBsqlite->new($known_clients_file_name);
2268 $known_clients_db->create_table($known_clients_tn, \@known_clients_col_names);
2270 # connect to known_server_db
2271 $known_server_db = GOSA::DBsqlite->new($known_server_file_name);
2272 $known_server_db->create_table($known_server_tn, \@known_server_col_names);
2274 # connect to login_usr_db
2275 $login_users_db = GOSA::DBsqlite->new($login_users_file_name);
2276 $login_users_db->create_table($login_users_tn, \@login_users_col_names);
2278 # connect to fai_server_db and fai_release_db
2279 unlink($fai_server_file_name);
2280 $fai_server_db = GOSA::DBsqlite->new($fai_server_file_name);
2281 $fai_server_db->create_table($fai_server_tn, \@fai_server_col_names);
2282 $fai_server_db->create_table($fai_release_tn, \@fai_release_col_names);
2284 # connect to packages_list_db
2285 unlink($packages_list_file_name);
2286 $packages_list_db = GOSA::DBsqlite->new($packages_list_file_name);
2287 $packages_list_db->create_table($packages_list_tn, \@packages_list_col_names);
2289 # connect to messaging_db
2290 $messaging_db = GOSA::DBsqlite->new($messaging_file_name);
2291 $messaging_db->create_table($messaging_tn, \@messaging_col_names);
2294 # create xml object used for en/decrypting
2295 $xml = new XML::Simple();
2297 # create socket for incoming xml messages
2299 POE::Component::Server::TCP->new(
2300 Port => $server_port,
2301 ClientInput => sub {
2302 my ($kernel, $input) = @_[KERNEL, ARG0];
2303 push(@tasks, $input);
2304 $kernel->yield("next_task");
2305 },
2306 InlineStates => {
2307 next_task => \&next_task,
2308 task_result => \&handle_task_result,
2309 task_done => \&handle_task_done,
2310 task_debug => \&handle_task_debug,
2311 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!" },
2312 }
2313 );
2315 daemon_log("start socket for incoming xml messages at port '$server_port' ", 1);
2317 # create session for repeatedly checking the job queue for jobs
2318 POE::Session->create(
2319 inline_states => {
2320 _start => \&_start,
2321 sig_handler => \&sig_handler,
2322 watch_for_new_messages => \&watch_for_new_messages,
2323 watch_for_done_messages => \&watch_for_done_messages,
2324 watch_for_new_jobs => \&watch_for_new_jobs,
2325 watch_for_done_jobs => \&watch_for_done_jobs,
2326 create_packages_list_db => \&run_create_packages_list_db,
2327 create_fai_server_db => \&run_create_fai_server_db,
2328 create_fai_release_db => \&run_create_fai_release_db,
2329 session_run_result => \&session_run_result,
2330 session_run_debug => \&session_run_debug,
2331 session_run_done => \&session_run_done,
2332 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!" },
2333 }
2334 );
2337 # import all modules
2338 &import_modules;
2340 # check wether all modules are gosa-si valid passwd check
2342 POE::Kernel->run();
2343 exit;