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";
109 $no_arp = 0;
110 my $packages_list_under_construction = 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 PRIMARY KEY",
119 "timestamp DEFAULT 'none'",
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 "plainname DEFAULT 'none'",
128 );
130 # holds all other gosa-sd as well as the gosa-sd-bus
131 our $known_server_db;
132 our $known_server_tn = "known_server";
133 my $known_server_file_name;
134 my @known_server_col_names = ('hostname', 'status', 'hostkey', 'timestamp');
136 # holds all registrated clients
137 our $known_clients_db;
138 our $known_clients_tn = "known_clients";
139 my $known_clients_file_name;
140 my @known_clients_col_names = ('hostname', 'status', 'hostkey', 'timestamp', 'macaddress', 'events');
142 # holds all logged in user at each client
143 our $login_users_db;
144 our $login_users_tn = "login_users";
145 my $login_users_file_name;
146 my @login_users_col_names = ('client', 'user', 'timestamp');
148 # holds all fai server, the debian release and tag
149 our $fai_server_db;
150 our $fai_server_tn = "fai_server";
151 my $fai_server_file_name;
152 our @fai_server_col_names = ('timestamp', 'server', 'release', 'sections', 'tag');
154 our $fai_release_db;
155 our $fai_release_tn = "fai_release";
156 my $fai_release_file_name;
157 our @fai_release_col_names = ('timestamp', 'release', 'class', 'type', 'state');
159 # holds all packages available from different repositories
160 our $packages_list_db;
161 our $packages_list_tn = "packages_list";
162 my $packages_list_file_name;
163 our @packages_list_col_names = ('distribution', 'package', 'version', 'section', 'description', 'template', 'timestamp');
164 my $outdir = "/tmp/packages_list_db";
165 my $arch = "i386";
167 # holds all messages which should be delivered to a user
168 our $messaging_db;
169 our $messaging_tn = "messaging";
170 our @messaging_col_names = ('subject', 'message_from', 'message_to', 'flag', 'direction', 'delivery_time', 'message', 'timestamp', 'id INTEGER' );
171 my $messaging_file_name;
173 # path to directory to store client install log files
174 our $client_fai_log_dir = "/var/log/fai";
176 # queue which stores taskes until one of the $max_children children are ready to process the task
177 my @tasks = qw();
178 my $max_children = 2;
181 %cfg_defaults = (
182 "general" => {
183 "log-file" => [\$log_file, "/var/run/".$prg.".log"],
184 "pid-file" => [\$pid_file, "/var/run/".$prg.".pid"],
185 },
186 "bus" => {
187 "activ" => [\$bus_activ, "true"],
188 },
189 "server" => {
190 "port" => [\$server_port, "20081"],
191 "known-clients" => [\$known_clients_file_name, '/var/lib/gosa-si/clients.db' ],
192 "known-servers" => [\$known_server_file_name, '/var/lib/gosa-si/servers.db'],
193 "login-users" => [\$login_users_file_name, '/var/lib/gosa-si/users.db'],
194 "fai-server" => [\$fai_server_file_name, '/var/lib/gosa-si/fai_server.db'],
195 "fai-release" => [\$fai_release_file_name, '/var/lib/gosa-si/fai_release.db'],
196 "packages-list" => [\$packages_list_file_name, '/var/lib/gosa-si/packages.db'],
197 "messaging" => [\$messaging_file_name, '/var/lib/gosa-si/messaging.db'],
198 "source-list" => [\$sources_list, '/etc/apt/sources.list'],
199 "repo-path" => [\$repo_path, '/srv/www/repository'],
200 "ldap-uri" => [\$ldap_uri, ""],
201 "ldap-base" => [\$ldap_base, ""],
202 "ldap-admin-dn" => [\$ldap_admin_dn, ""],
203 "ldap-admin-password" => [\$ldap_admin_password, ""],
204 "gosa-unit-tag" => [\$gosa_unit_tag, ""],
205 "max-clients" => [\$max_clients, 10],
206 },
207 "GOsaPackages" => {
208 "ip" => [\$gosa_ip, "0.0.0.0"],
209 "port" => [\$gosa_port, "20082"],
210 "job-queue" => [\$job_queue_file_name, '/var/lib/gosa-si/jobs.db'],
211 "job-queue-loop-delay" => [\$job_queue_loop_delay, 3],
212 "messaging-db-loop-delay" => [\$messaging_db_loop_delay, 3],
213 "key" => [\$GosaPackages_key, "none"],
214 },
215 "SIPackages" => {
216 "key" => [\$SIPackages_key, "none"],
217 },
218 );
221 #=== FUNCTION ================================================================
222 # NAME: usage
223 # PARAMETERS: nothing
224 # RETURNS: nothing
225 # DESCRIPTION: print out usage text to STDERR
226 #===============================================================================
227 sub usage {
228 print STDERR << "EOF" ;
229 usage: $prg [-hvf] [-c config]
231 -h : this (help) message
232 -c <file> : config file
233 -f : foreground, process will not be forked to background
234 -v : be verbose (multiple to increase verbosity)
235 -no-bus : starts $prg without connection to bus
236 -no-arp : starts $prg without connection to arp module
238 EOF
239 print "\n" ;
240 }
243 #=== FUNCTION ================================================================
244 # NAME: read_configfile
245 # PARAMETERS: cfg_file - string -
246 # RETURNS: nothing
247 # DESCRIPTION: read cfg_file and set variables
248 #===============================================================================
249 sub read_configfile {
250 my $cfg;
251 if( defined( $cfg_file) && ( (-s $cfg_file) > 0 )) {
252 if( -r $cfg_file ) {
253 $cfg = Config::IniFiles->new( -file => $cfg_file );
254 } else {
255 print STDERR "Couldn't read config file!\n";
256 }
257 } else {
258 $cfg = Config::IniFiles->new() ;
259 }
260 foreach my $section (keys %cfg_defaults) {
261 foreach my $param (keys %{$cfg_defaults{ $section }}) {
262 my $pinfo = $cfg_defaults{ $section }{ $param };
263 ${@$pinfo[ 0 ]} = $cfg->val( $section, $param, @$pinfo[ 1 ] );
264 }
265 }
266 }
269 #=== FUNCTION ================================================================
270 # NAME: logging
271 # PARAMETERS: level - string - default 'info'
272 # msg - string -
273 # facility - string - default 'LOG_DAEMON'
274 # RETURNS: nothing
275 # DESCRIPTION: function for logging
276 #===============================================================================
277 sub daemon_log {
278 # log into log_file
279 my( $msg, $level ) = @_;
280 if(not defined $msg) { return }
281 if(not defined $level) { $level = 1 }
282 if(defined $log_file){
283 open(LOG_HANDLE, ">>$log_file");
284 if(not defined open( LOG_HANDLE, ">>$log_file" )) {
285 print STDERR "cannot open $log_file: $!";
286 return }
287 chomp($msg);
288 $msg =~s/\n//g; # no newlines are allowed in log messages, this is important for later log parsing
289 if($level <= $verbose){
290 my ($seconds, $minutes, $hours, $monthday, $month,
291 $year, $weekday, $yearday, $sommertime) = localtime(time);
292 $hours = $hours < 10 ? $hours = "0".$hours : $hours;
293 $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes;
294 $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds;
295 my @monthnames = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
296 $month = $monthnames[$month];
297 $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday;
298 $year+=1900;
299 my $name = $prg;
301 my $log_msg = "$month $monthday $hours:$minutes:$seconds $name $msg\n";
302 print LOG_HANDLE $log_msg;
303 if( $foreground ) {
304 print STDERR $log_msg;
305 }
306 }
307 close( LOG_HANDLE );
308 }
309 }
312 #=== FUNCTION ================================================================
313 # NAME: check_cmdline_param
314 # PARAMETERS: nothing
315 # RETURNS: nothing
316 # DESCRIPTION: validates commandline parameter
317 #===============================================================================
318 sub check_cmdline_param () {
319 my $err_config;
320 my $err_counter = 0;
321 if(not defined($cfg_file)) {
322 $cfg_file = "/etc/gosa-si/server.conf";
323 if(! -r $cfg_file) {
324 $err_config = "please specify a config file";
325 $err_counter += 1;
326 }
327 }
328 if( $err_counter > 0 ) {
329 &usage( "", 1 );
330 if( defined( $err_config)) { print STDERR "$err_config\n"}
331 print STDERR "\n";
332 exit( -1 );
333 }
334 }
337 #=== FUNCTION ================================================================
338 # NAME: check_pid
339 # PARAMETERS: nothing
340 # RETURNS: nothing
341 # DESCRIPTION: handels pid processing
342 #===============================================================================
343 sub check_pid {
344 $pid = -1;
345 # Check, if we are already running
346 if( open(LOCK_FILE, "<$pid_file") ) {
347 $pid = <LOCK_FILE>;
348 if( defined $pid ) {
349 chomp( $pid );
350 if( -f "/proc/$pid/stat" ) {
351 my($stat) = `cat /proc/$pid/stat` =~ m/$pid \((.+)\).*/;
352 if( $stat ) {
353 daemon_log("ERROR: Already running",1);
354 close( LOCK_FILE );
355 exit -1;
356 }
357 }
358 }
359 close( LOCK_FILE );
360 unlink( $pid_file );
361 }
363 # create a syslog msg if it is not to possible to open PID file
364 if (not sysopen(LOCK_FILE, $pid_file, O_WRONLY|O_CREAT|O_EXCL, 0644)) {
365 my($msg) = "Couldn't obtain lockfile '$pid_file' ";
366 if (open(LOCK_FILE, '<', $pid_file)
367 && ($pid = <LOCK_FILE>))
368 {
369 chomp($pid);
370 $msg .= "(PID $pid)\n";
371 } else {
372 $msg .= "(unable to read PID)\n";
373 }
374 if( ! ($foreground) ) {
375 openlog( $0, "cons,pid", "daemon" );
376 syslog( "warning", $msg );
377 closelog();
378 }
379 else {
380 print( STDERR " $msg " );
381 }
382 exit( -1 );
383 }
384 }
386 #=== FUNCTION ================================================================
387 # NAME: import_modules
388 # PARAMETERS: module_path - string - abs. path to the directory the modules
389 # are stored
390 # RETURNS: nothing
391 # DESCRIPTION: each file in module_path which ends with '.pm' and activation
392 # state is on is imported by "require 'file';"
393 #===============================================================================
394 sub import_modules {
395 daemon_log(" ", 1);
397 if (not -e $modules_path) {
398 daemon_log("ERROR: cannot find directory or directory is not readable: $modules_path", 1);
399 }
401 opendir (DIR, $modules_path) or die "ERROR while loading modules from directory $modules_path : $!\n";
402 while (defined (my $file = readdir (DIR))) {
403 if (not $file =~ /(\S*?).pm$/) {
404 next;
405 }
406 my $mod_name = $1;
408 if( $file =~ /ArpHandler.pm/ ) {
409 if( $no_arp > 0 ) {
410 next;
411 }
412 }
414 eval { require $file; };
415 if ($@) {
416 daemon_log("ERROR: gosa-si-server could not load module $file", 1);
417 daemon_log("$@", 5);
418 } else {
419 my $info = eval($mod_name.'::get_module_info()');
420 # Only load module if get_module_info() returns a non-null object
421 if( $info ) {
422 my ($input_address, $input_key, $input, $input_active, $input_type) = @{$info};
423 $known_modules->{$mod_name} = $info;
424 daemon_log("INFO: module $mod_name loaded", 5);
425 }
426 }
427 }
428 close (DIR);
429 }
432 #=== FUNCTION ================================================================
433 # NAME: sig_int_handler
434 # PARAMETERS: signal - string - signal arose from system
435 # RETURNS: noting
436 # DESCRIPTION: handels tasks to be done befor signal becomes active
437 #===============================================================================
438 sub sig_int_handler {
439 my ($signal) = @_;
441 # if (defined($ldap_handle)) {
442 # $ldap_handle->disconnect;
443 # }
444 # TODO alle verbliebenden ldap verbindungen aus allen heaps beenden
447 daemon_log("shutting down gosa-si-server", 1);
448 system("kill `ps -C gosa-si-server -o pid=`");
449 }
450 $SIG{INT} = \&sig_int_handler;
453 sub check_key_and_xml_validity {
454 my ($crypted_msg, $module_key, $session_id) = @_;
455 my $msg;
456 my $msg_hash;
457 my $error_string;
458 eval{
459 $msg = &decrypt_msg($crypted_msg, $module_key);
461 if ($msg =~ /<xml>/i){
462 $msg =~ s/\s+/ /g; # just for better daemon_log
463 daemon_log("$session_id DEBUG: decrypted_msg: \n$msg", 8);
464 $msg_hash = $xml->XMLin($msg, ForceArray=>1);
466 ##############
467 # check header
468 if( not exists $msg_hash->{'header'} ) { die "no header specified"; }
469 my $header_l = $msg_hash->{'header'};
470 if( 1 > @{$header_l} ) { die 'empty header tag'; }
471 if( 1 < @{$header_l} ) { die 'more than one header specified'; }
472 my $header = @{$header_l}[0];
473 if( 0 == length $header) { die 'empty string in header tag'; }
475 ##############
476 # check source
477 if( not exists $msg_hash->{'source'} ) { die "no source specified"; }
478 my $source_l = $msg_hash->{'source'};
479 if( 1 > @{$source_l} ) { die 'empty source tag'; }
480 if( 1 < @{$source_l} ) { die 'more than one source specified'; }
481 my $source = @{$source_l}[0];
482 if( 0 == length $source) { die 'source error'; }
484 ##############
485 # check target
486 if( not exists $msg_hash->{'target'} ) { die "no target specified"; }
487 my $target_l = $msg_hash->{'target'};
488 if( 1 > @{$target_l} ) { die 'empty target tag'; }
489 }
490 };
491 if($@) {
492 daemon_log("$session_id DEBUG: do not understand the message: $@", 7);
493 $msg = undef;
494 $msg_hash = undef;
495 }
497 return ($msg, $msg_hash);
498 }
501 sub check_outgoing_xml_validity {
502 my ($msg) = @_;
504 my $msg_hash;
505 eval{
506 $msg_hash = $xml->XMLin($msg, ForceArray=>1);
508 ##############
509 # check header
510 my $header_l = $msg_hash->{'header'};
511 if( 1 != @{$header_l} ) {
512 die 'no or more than one headers specified';
513 }
514 my $header = @{$header_l}[0];
515 if( 0 == length $header) {
516 die 'header has length 0';
517 }
519 ##############
520 # check source
521 my $source_l = $msg_hash->{'source'};
522 if( 1 != @{$source_l} ) {
523 die 'no or more than 1 sources specified';
524 }
525 my $source = @{$source_l}[0];
526 if( 0 == length $source) {
527 die 'source has length 0';
528 }
529 unless( $source =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
530 $source =~ /^GOSA$/i ) {
531 die "source '$source' is neither a complete ip-address with port nor 'GOSA'";
532 }
534 ##############
535 # check target
536 my $target_l = $msg_hash->{'target'};
537 if( 0 == @{$target_l} ) {
538 die "no targets specified";
539 }
540 foreach my $target (@$target_l) {
541 if( 0 == length $target) {
542 die "target has length 0";
543 }
544 unless( $target =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
545 $target =~ /^GOSA$/i ||
546 $target =~ /^\*$/ ||
547 $target =~ /KNOWN_SERVER/i ||
548 $target =~ /JOBDB/i ||
549 $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 ){
550 die "target '$target' is not a complete ip-address with port or a valid target name or a mac-address";
551 }
552 }
553 };
554 if($@) {
555 daemon_log("WARNING: outgoing msg is not gosa-si envelope conform", 5);
556 daemon_log("$@ ".(defined($msg) && length($msg)>0)?$msg:"Empty Message", 8);
557 $msg_hash = undef;
558 }
560 return ($msg_hash);
561 }
564 sub input_from_known_server {
565 my ($input, $remote_ip, $session_id) = @_ ;
566 my ($msg, $msg_hash, $module);
568 my $sql_statement= "SELECT * FROM known_server";
569 my $query_res = $known_server_db->select_dbentry( $sql_statement );
571 while( my ($hit_num, $hit) = each %{ $query_res } ) {
572 my $host_name = $hit->{hostname};
573 if( not $host_name =~ "^$remote_ip") {
574 next;
575 }
576 my $host_key = $hit->{hostkey};
577 daemon_log("$session_id DEBUG: input_from_known_server: host_name: $host_name", 7);
578 daemon_log("DEBUG: input_from_known_server: host_key: $host_key", 7);
580 # check if module can open msg envelope with module key
581 my ($tmp_msg, $tmp_msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
582 if( (!$tmp_msg) || (!$tmp_msg_hash) ) {
583 daemon_log("$session_id DEBUG: input_from_known_server: deciphering raise error", 7);
584 daemon_log("$@", 8);
585 next;
586 }
587 else {
588 $msg = $tmp_msg;
589 $msg_hash = $tmp_msg_hash;
590 $module = "SIPackages";
591 last;
592 }
593 }
595 if( (!$msg) || (!$msg_hash) || (!$module) ) {
596 daemon_log("$session_id DEBUG: Incoming message is not from a known server", 7);
597 }
599 return ($msg, $msg_hash, $module);
600 }
603 sub input_from_known_client {
604 my ($input, $remote_ip, $session_id) = @_ ;
605 my ($msg, $msg_hash, $module);
607 my $sql_statement= "SELECT * FROM known_clients";
608 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
609 while( my ($hit_num, $hit) = each %{ $query_res } ) {
610 my $host_name = $hit->{hostname};
611 if( not $host_name =~ /^$remote_ip:\d*$/) {
612 next;
613 }
614 my $host_key = $hit->{hostkey};
615 &daemon_log("$session_id DEBUG: input_from_known_client: host_name: $host_name", 7);
616 &daemon_log("$session_id DEBUG: input_from_known_client: host_key: $host_key", 7);
618 # check if module can open msg envelope with module key
619 ($msg, $msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
621 if( (!$msg) || (!$msg_hash) ) {
622 &daemon_log("$session_id DEGUG: input_from_known_client: deciphering raise error", 7);
623 &daemon_log("$@", 8);
624 next;
625 }
626 else {
627 $module = "SIPackages";
628 last;
629 }
630 }
632 if( (!$msg) || (!$msg_hash) || (!$module) ) {
633 &daemon_log("$session_id DEBUG: Incoming message is not from a known client", 7);
634 }
636 return ($msg, $msg_hash, $module);
637 }
640 sub input_from_unknown_host {
641 no strict "refs";
642 my ($input, $session_id) = @_ ;
643 my ($msg, $msg_hash, $module);
644 my $error_string;
646 my %act_modules = %$known_modules;
648 while( my ($mod, $info) = each(%act_modules)) {
650 # check a key exists for this module
651 my $module_key = ${$mod."_key"};
652 if( not defined $module_key ) {
653 if( $mod eq 'ArpHandler' ) {
654 next;
655 }
656 daemon_log("$session_id ERROR: no key specified in config file for $mod", 1);
657 next;
658 }
659 daemon_log("$session_id DEBUG: $mod: $module_key", 7);
661 # check if module can open msg envelope with module key
662 ($msg, $msg_hash) = &check_key_and_xml_validity($input, $module_key, $session_id);
663 if( (not defined $msg) || (not defined $msg_hash) ) {
664 next;
665 }
666 else {
667 $module = $mod;
668 last;
669 }
670 }
672 if( (!$msg) || (!$msg_hash) || (!$module)) {
673 daemon_log("$session_id DEBUG: Incoming message is not from an unknown host", 7);
674 }
676 return ($msg, $msg_hash, $module);
677 }
680 sub create_ciphering {
681 my ($passwd) = @_;
682 if((!defined($passwd)) || length($passwd)==0) {
683 $passwd = "";
684 }
685 $passwd = substr(md5_hex("$passwd") x 32, 0, 32);
686 my $iv = substr(md5_hex('GONICUS GmbH'),0, 16);
687 my $my_cipher = Crypt::Rijndael->new($passwd , Crypt::Rijndael::MODE_CBC());
688 $my_cipher->set_iv($iv);
689 return $my_cipher;
690 }
693 sub encrypt_msg {
694 my ($msg, $key) = @_;
695 my $my_cipher = &create_ciphering($key);
696 my $len;
697 {
698 use bytes;
699 $len= 16-length($msg)%16;
700 }
701 $msg = "\0"x($len).$msg;
702 $msg = $my_cipher->encrypt($msg);
703 chomp($msg = &encode_base64($msg));
704 # there are no newlines allowed inside msg
705 $msg=~ s/\n//g;
706 return $msg;
707 }
710 sub decrypt_msg {
712 my ($msg, $key) = @_ ;
713 $msg = &decode_base64($msg);
714 my $my_cipher = &create_ciphering($key);
715 $msg = $my_cipher->decrypt($msg);
716 $msg =~ s/\0*//g;
717 return $msg;
718 }
721 sub get_encrypt_key {
722 my ($target) = @_ ;
723 my $encrypt_key;
724 my $error = 0;
726 # target can be in known_server
727 if( not defined $encrypt_key ) {
728 my $sql_statement= "SELECT * FROM known_server WHERE hostname='$target'";
729 my $query_res = $known_server_db->select_dbentry( $sql_statement );
730 while( my ($hit_num, $hit) = each %{ $query_res } ) {
731 my $host_name = $hit->{hostname};
732 if( $host_name ne $target ) {
733 next;
734 }
735 $encrypt_key = $hit->{hostkey};
736 last;
737 }
738 }
740 # target can be in known_client
741 if( not defined $encrypt_key ) {
742 my $sql_statement= "SELECT * FROM known_clients WHERE hostname='$target'";
743 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
744 while( my ($hit_num, $hit) = each %{ $query_res } ) {
745 my $host_name = $hit->{hostname};
746 if( $host_name ne $target ) {
747 next;
748 }
749 $encrypt_key = $hit->{hostkey};
750 last;
751 }
752 }
754 return $encrypt_key;
755 }
758 #=== FUNCTION ================================================================
759 # NAME: open_socket
760 # PARAMETERS: PeerAddr string something like 192.168.1.1 or 192.168.1.1:10000
761 # [PeerPort] string necessary if port not appended by PeerAddr
762 # RETURNS: socket IO::Socket::INET
763 # DESCRIPTION: open a socket to PeerAddr
764 #===============================================================================
765 sub open_socket {
766 my ($PeerAddr, $PeerPort) = @_ ;
767 if(defined($PeerPort)){
768 $PeerAddr = $PeerAddr.":".$PeerPort;
769 }
770 my $socket;
771 $socket = new IO::Socket::INET(PeerAddr => $PeerAddr,
772 Porto => "tcp",
773 Type => SOCK_STREAM,
774 Timeout => 5,
775 );
776 if(not defined $socket) {
777 return;
778 }
779 # &daemon_log("DEBUG: open_socket: $PeerAddr", 7);
780 return $socket;
781 }
784 #=== FUNCTION ================================================================
785 # NAME: get_ip
786 # PARAMETERS: interface name (i.e. eth0)
787 # RETURNS: (ip address)
788 # DESCRIPTION: Uses ioctl to get ip address directly from system.
789 #===============================================================================
790 sub get_ip {
791 my $ifreq= shift;
792 my $result= "";
793 my $SIOCGIFADDR= 0x8915; # man 2 ioctl_list
794 my $proto= getprotobyname('ip');
796 socket SOCKET, PF_INET, SOCK_DGRAM, $proto
797 or die "socket: $!";
799 if(ioctl SOCKET, $SIOCGIFADDR, $ifreq) {
800 my ($if, $sin) = unpack 'a16 a16', $ifreq;
801 my ($port, $addr) = sockaddr_in $sin;
802 my $ip = inet_ntoa $addr;
804 if ($ip && length($ip) > 0) {
805 $result = $ip;
806 }
807 }
809 return $result;
810 }
813 sub get_local_ip_for_remote_ip {
814 my $remote_ip= shift;
815 my $result="0.0.0.0";
817 if($remote_ip =~ /^(\d\d?\d?\.){3}\d\d?\d?$/) {
818 if($remote_ip eq "127.0.0.1") {
819 $result = "127.0.0.1";
820 } else {
821 my $PROC_NET_ROUTE= ('/proc/net/route');
823 open(PROC_NET_ROUTE, "<$PROC_NET_ROUTE")
824 or die "Could not open $PROC_NET_ROUTE";
826 my @ifs = <PROC_NET_ROUTE>;
828 close(PROC_NET_ROUTE);
830 # Eat header line
831 shift @ifs;
832 chomp @ifs;
833 foreach my $line(@ifs) {
834 my ($Iface,$Destination,$Gateway,$Flags,$RefCnt,$Use,$Metric,$Mask,$MTU,$Window,$IRTT)=split(/\s/, $line);
835 my $destination;
836 my $mask;
837 my ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Destination);
838 $destination= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
839 ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Mask);
840 $mask= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
841 if(new NetAddr::IP($remote_ip)->within(new NetAddr::IP($destination, $mask))) {
842 # destination matches route, save mac and exit
843 $result= &get_ip($Iface);
844 last;
845 }
846 }
847 }
848 } else {
849 daemon_log("get_local_ip_for_remote_ip was called with a non-ip parameter: $remote_ip", 1);
850 }
851 return $result;
852 }
855 sub send_msg_to_target {
856 my ($msg, $address, $encrypt_key, $msg_header, $session_id) = @_ ;
857 my $error = 0;
858 my $header;
859 my $new_status;
860 my $act_status;
861 my ($sql_statement, $res);
863 if( $msg_header ) {
864 $header = "'$msg_header'-";
865 } else {
866 $header = "";
867 }
869 # Patch the source ip
870 if($msg =~ /<source>0\.0\.0\.0:\d*?<\/source>/) {
871 my $remote_ip = &get_local_ip_for_remote_ip(sprintf("%s", $address =~ /^([0-9\.]*?):.*$/));
872 $msg =~ s/<source>(0\.0\.0\.0):(\d*?)<\/source>/<source>$remote_ip:$2<\/source>/s;
873 }
875 # encrypt xml msg
876 my $crypted_msg = &encrypt_msg($msg, $encrypt_key);
878 # opensocket
879 my $socket = &open_socket($address);
880 if( !$socket ) {
881 daemon_log("$session_id ERROR: cannot send ".$header."msg to $address , host not reachable", 1);
882 $error++;
883 }
885 if( $error == 0 ) {
886 # send xml msg
887 print $socket $crypted_msg."\n";
889 daemon_log("$session_id INFO: send ".$header."msg to $address", 5);
890 #daemon_log("DEBUG: message:\n$msg", 9);
892 }
894 # close socket in any case
895 if( $socket ) {
896 close $socket;
897 }
899 if( $error > 0 ) { $new_status = "down"; }
900 else { $new_status = $msg_header; }
903 # known_clients
904 $sql_statement = "SELECT * FROM known_clients WHERE hostname='$address'";
905 $res = $known_clients_db->select_dbentry($sql_statement);
906 if( keys(%$res) > 0) {
907 $act_status = $res->{1}->{'status'};
908 if( $act_status eq "down" ) {
909 $sql_statement = "DELETE FROM known_clients WHERE hostname='$address'";
910 $res = $known_clients_db->del_dbentry($sql_statement);
911 daemon_log("$session_id WARNING: failed 2x to send msg to host '$address', delete host from known_clients", 3);
912 } else {
913 $sql_statement = "UPDATE known_clients SET status='$new_status' WHERE hostname='$address'";
914 $res = $known_clients_db->update_dbentry($sql_statement);
915 if($new_status eq "down"){
916 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
917 } else {
918 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
919 }
920 }
921 }
923 # known_server
924 $sql_statement = "SELECT * FROM known_server WHERE hostname='$address'";
925 $res = $known_server_db->select_dbentry($sql_statement);
926 if( keys(%$res) > 0 ) {
927 $act_status = $res->{1}->{'status'};
928 if( $act_status eq "down" ) {
929 $sql_statement = "DELETE FROM known_server WHERE hostname='$address'";
930 $res = $known_server_db->del_dbentry($sql_statement);
931 daemon_log("$session_id WARNING: failed 2x to a send msg to host '$address', delete host from known_server", 3);
932 }
933 else {
934 $sql_statement = "UPDATE known_server SET status='$new_status' WHERE hostname='$address'";
935 $res = $known_server_db->update_dbentry($sql_statement);
936 if($new_status eq "down"){
937 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
938 }
939 else {
940 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
941 }
942 }
943 }
944 return $error;
945 }
948 sub update_jobdb_status_for_send_msgs {
949 my ($answer, $error) = @_;
950 if( $answer =~ /<jobdb_id>(\d+)<\/jobdb_id>/ ) {
951 my $jobdb_id = $1;
953 # sending msg faild
954 if( $error ) {
955 if (not $answer =~ /<header>trigger_action_reinstall<\/header>/) {
956 my $sql_statement = "UPDATE $job_queue_tn ".
957 "SET status='error', result='can not deliver msg, please consult log file' ".
958 "WHERE id='$jobdb_id'";
959 my $res = $job_db->update_dbentry($sql_statement);
960 }
962 # sending msg was successful
963 } else {
964 my $sql_statement = "UPDATE $job_queue_tn ".
965 "SET status='done' ".
966 "WHERE id='$jobdb_id' AND status='processed'";
967 my $res = $job_db->update_dbentry($sql_statement);
968 }
969 }
970 }
972 sub _start {
973 my ($kernel) = $_[KERNEL];
974 &trigger_db_loop($kernel);
975 $global_kernel = $kernel;
976 $kernel->yield('create_fai_server_db', $fai_server_tn );
977 $kernel->yield('create_fai_release_db', $fai_release_tn );
978 $kernel->sig(USR1 => "sig_handler");
979 $kernel->sig(USR2 => "create_packages_list_db");
980 }
982 sub sig_handler {
983 my ($kernel, $signal) = @_[KERNEL, ARG0] ;
984 daemon_log("0 INFO got signal '$signal'", 1);
985 $kernel->sig_handled();
986 return;
987 }
989 sub next_task {
990 my ($session, $heap) = @_[SESSION, HEAP];
992 while ( keys( %{ $heap->{task} } ) < $max_children ) {
993 my $next_task = shift @tasks;
994 last unless defined $next_task;
996 my $task = POE::Wheel::Run->new(
997 Program => sub { process_task($session, $heap, $next_task) },
998 StdioFilter => POE::Filter::Reference->new(),
999 StdoutEvent => "task_result",
1000 StderrEvent => "task_debug",
1001 CloseEvent => "task_done",
1002 );
1004 $heap->{task}->{ $task->ID } = $task;
1005 }
1006 }
1008 sub handle_task_result {
1009 my ($kernel, $heap, $result) = @_[KERNEL, HEAP, ARG0];
1010 my $client_answer = $result->{'answer'};
1011 if( $client_answer =~ s/session_id=(\d+)$// ) {
1012 my $session_id = $1;
1013 if( defined $session_id ) {
1014 my $session_reference = $kernel->ID_id_to_session($session_id);
1015 if( defined $session_reference ) {
1016 $heap = $session_reference->get_heap();
1017 }
1018 }
1020 if(exists $heap->{'client'}) {
1021 $heap->{'client'}->put($client_answer);
1022 }
1023 }
1024 $kernel->sig(CHLD => "child_reap");
1025 }
1027 sub handle_task_debug {
1028 my $result = $_[ARG0];
1029 print STDERR "$result\n";
1030 }
1032 sub handle_task_done {
1033 my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1034 delete $heap->{task}->{$task_id};
1035 $kernel->yield("next_task");
1036 }
1038 sub process_task {
1039 no strict "refs";
1040 my ($session, $heap, $input) = @_;
1041 my $session_id = $session->ID;
1042 my ($msg, $msg_hash, $module);
1043 my $error = 0;
1044 my $answer_l;
1045 my ($answer_header, @answer_target_l, $answer_source);
1046 my $client_answer = "";
1048 daemon_log("", 5);
1049 daemon_log("$session_id INFO: Incoming msg with session ID $session_id from '".$heap->{'remote_ip'}."'", 5);
1050 #daemon_log("$session_id DEBUG: Incoming msg:\n$input", 9);
1052 ####################
1053 # check incoming msg
1054 # msg is from a new client or gosa
1055 ($msg, $msg_hash, $module) = &input_from_unknown_host($input, $session_id);
1056 # msg is from a gosa-si-server or gosa-si-bus
1057 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1058 ($msg, $msg_hash, $module) = &input_from_known_server($input, $heap->{'remote_ip'}, $session_id);
1059 }
1060 # msg is from a gosa-si-client
1061 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1062 ($msg, $msg_hash, $module) = &input_from_known_client($input, $heap->{'remote_ip'}, $session_id);
1063 }
1064 # an error occurred
1065 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1066 # if an incoming msg could not be decrypted (maybe a wrong key), send client a ping. If the client
1067 # could not understand a msg from its server the client cause a re-registering process
1068 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);
1069 my $sql_statement = "SELECT * FROM $main::known_clients_tn WHERE (hostname LIKE '".$heap->{'remote_ip'}."%')";
1070 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1071 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1072 my $host_name = $hit->{'hostname'};
1073 my $host_key = $hit->{'hostkey'};
1074 my $ping_msg = "<xml> <header>gosa_ping</header> <source>$server_address</source> <target>$host_name</target></xml>";
1075 my $error = &send_msg_to_target($ping_msg, $host_name, $host_key, "gosa_ping", $session_id);
1076 &update_jobdb_status_for_send_msgs($ping_msg, $error);
1077 }
1078 $error++;
1079 }
1081 ######################
1082 # process incoming msg
1083 if( $error == 0) {
1084 daemon_log("$session_id INFO: Incoming msg with header '".@{$msg_hash->{'header'}}[0].
1085 "' from '".$heap->{'remote_ip'}."'", 5);
1086 daemon_log("$session_id DEBUG: Processing module ".$module, 7);
1087 $answer_l = &{ $module."::process_incoming_msg" }($msg, $msg_hash, $session_id);
1089 if ( 0 < @{$answer_l} ) {
1090 my $answer_str = join("\n", @{$answer_l});
1091 daemon_log("$session_id DEBUG: $module: Got answer from module: \n".$answer_str,8);
1092 }
1093 }
1094 if( !$answer_l ) { $error++ };
1096 ########
1097 # answer
1098 if( $error == 0 ) {
1100 foreach my $answer ( @{$answer_l} ) {
1101 # for each answer in answer list
1103 # check outgoing msg to xml validity
1104 my $answer_hash = &check_outgoing_xml_validity($answer);
1105 if( not defined $answer_hash ) {
1106 next;
1107 }
1109 $answer_header = @{$answer_hash->{'header'}}[0];
1110 @answer_target_l = @{$answer_hash->{'target'}};
1111 $answer_source = @{$answer_hash->{'source'}}[0];
1113 # deliver msg to all targets
1114 foreach my $answer_target ( @answer_target_l ) {
1116 # targets of msg are all gosa-si-clients in known_clients_db
1117 if( $answer_target eq "*" ) {
1118 # answer is for all clients
1119 my $sql_statement= "SELECT * FROM known_clients";
1120 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1121 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1122 my $host_name = $hit->{hostname};
1123 my $host_key = $hit->{hostkey};
1124 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1125 &update_jobdb_status_for_send_msgs($answer, $error);
1126 }
1127 }
1129 # targets of msg are all gosa-si-server in known_server_db
1130 elsif( $answer_target eq "KNOWN_SERVER" ) {
1131 # answer is for all server in known_server
1132 my $sql_statement= "SELECT * FROM known_server";
1133 my $query_res = $known_server_db->select_dbentry( $sql_statement );
1134 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1135 my $host_name = $hit->{hostname};
1136 my $host_key = $hit->{hostkey};
1137 $answer =~ s/KNOWN_SERVER/$host_name/g;
1138 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1139 &update_jobdb_status_for_send_msgs($answer, $error);
1140 }
1141 }
1143 # target of msg is GOsa
1144 elsif( $answer_target eq "GOSA" ) {
1145 my $session_id = ($1) if $answer =~ /<session_id>(\d+?)<\/session_id>/;
1146 my $add_on = "";
1147 if( defined $session_id ) {
1148 $add_on = ".session_id=$session_id";
1149 }
1150 # answer is for GOSA and has to returned to connected client
1151 my $gosa_answer = &encrypt_msg($answer, $GosaPackages_key);
1152 $client_answer = $gosa_answer.$add_on;
1153 }
1155 # target of msg is job queue at this host
1156 elsif( $answer_target eq "JOBDB") {
1157 $answer =~ /<header>(\S+)<\/header>/;
1158 my $header;
1159 if( defined $1 ) { $header = $1; }
1160 my $error = &send_msg_to_target($answer, $server_address, $GosaPackages_key, $header, $session_id);
1161 &update_jobdb_status_for_send_msgs($answer, $error);
1162 }
1164 # target of msg is a mac address
1165 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 ) {
1166 daemon_log("$session_id INFO: target is mac address '$answer_target', looking for host in known_clients", 5);
1167 my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$answer_target'";
1168 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1169 my $found_ip_flag = 0;
1170 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1171 my $host_name = $hit->{hostname};
1172 my $host_key = $hit->{hostkey};
1173 $answer =~ s/$answer_target/$host_name/g;
1174 daemon_log("$session_id INFO: found host '$host_name', associated to '$answer_target'", 5);
1175 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1176 &update_jobdb_status_for_send_msgs($answer, $error);
1177 $found_ip_flag++ ;
1178 }
1179 if( $found_ip_flag == 0) {
1180 daemon_log("$session_id WARNING: no host found in known_clients with mac address '$answer_target'", 3);
1181 if( $bus_activ eq "true" ) {
1182 daemon_log("$session_id INFO: try to forward msg '$answer_header' to bus '$bus_address'", 5);
1183 my $sql_statement = "SELECT * FROM known_server WHERE hostname='$bus_address'";
1184 my $query_res = $known_server_db->select_dbentry( $sql_statement );
1185 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1186 my $bus_address = $hit->{hostname};
1187 my $bus_key = $hit->{hostkey};
1188 my $error = &send_msg_to_target($answer, $bus_address, $bus_key, $answer_header, $session_id);
1189 &update_jobdb_status_for_send_msgs($answer, $error);
1190 last;
1191 }
1192 }
1194 }
1196 # answer is for one specific host
1197 } else {
1198 # get encrypt_key
1199 my $encrypt_key = &get_encrypt_key($answer_target);
1200 if( not defined $encrypt_key ) {
1201 # unknown target, forward msg to bus
1202 daemon_log("$session_id WARNING: unknown target '$answer_target'", 3);
1203 if( $bus_activ eq "true" ) {
1204 daemon_log("$session_id INFO: try to forward msg '$answer_header' to bus '$bus_address'", 5);
1205 my $sql_statement = "SELECT * FROM known_server WHERE hostname='$bus_address'";
1206 my $query_res = $known_server_db->select_dbentry( $sql_statement );
1207 my $res_length = keys( %{$query_res} );
1208 if( $res_length == 0 ){
1209 daemon_log("$session_id WARNING: send '$answer_header' to '$bus_address' failed, ".
1210 "no bus found in known_server", 3);
1211 }
1212 else {
1213 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1214 my $bus_key = $hit->{hostkey};
1215 my $error = &send_msg_to_target($answer, $bus_address, $bus_key, $answer_header,$session_id );
1216 &update_jobdb_status_for_send_msgs($answer, $error);
1217 }
1218 }
1219 }
1220 next;
1221 }
1222 my $error = &send_msg_to_target($answer, $answer_target, $encrypt_key, $answer_header,$session_id);
1223 &update_jobdb_status_for_send_msgs($answer, $error);
1224 }
1225 }
1226 }
1227 }
1229 my $filter = POE::Filter::Reference->new();
1230 my %result = (
1231 status => "seems ok to me",
1232 answer => $client_answer,
1233 );
1235 my $output = $filter->put( [ \%result ] );
1236 print @$output;
1239 }
1242 sub trigger_db_loop {
1243 my ($kernel) = @_ ;
1244 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1245 $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay);
1246 $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1247 $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1248 }
1250 sub watch_for_done_jobs {
1251 my ($kernel,$heap) = @_[KERNEL, HEAP];
1253 my $sql_statement = "SELECT * FROM ".$job_queue_tn.
1254 " WHERE status='done'";
1255 my $res = $job_db->select_dbentry( $sql_statement );
1257 while( my ($id, $hit) = each %{$res} ) {
1258 my $jobdb_id = $hit->{id};
1259 my $sql_statement = "DELETE FROM $job_queue_tn WHERE id='$jobdb_id'";
1260 my $res = $job_db->del_dbentry($sql_statement);
1261 }
1263 $kernel->delay_set('watch_for_done_jobs',$job_queue_loop_delay);
1264 }
1266 sub watch_for_new_jobs {
1267 my ($kernel,$heap) = @_[KERNEL, HEAP];
1269 # check gosa job queue for jobs with executable timestamp
1270 my $timestamp = &get_time();
1271 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE status='waiting' AND (CAST (timestamp AS INTEGER)) < $timestamp ORDER BY timestamp";
1272 my $res = $job_db->exec_statement( $sql_statement );
1274 # Merge all new jobs that would do the same actions
1275 my @drops;
1276 my $hits;
1277 foreach my $hit (reverse @{$res} ) {
1278 my $macaddress= lc @{$hit}[8];
1279 my $headertag= @{$hit}[5];
1280 if(defined($hits->{$macaddress}->{$headertag})) {
1281 push @drops, "DELETE FROM $job_queue_tn WHERE id = '$hits->{$macaddress}->{$headertag}[0]'";
1282 }
1283 $hits->{$macaddress}->{$headertag}= $hit;
1284 }
1286 # Delete new jobs with a matching job in state 'processing'
1287 foreach my $macaddress (keys %{$hits}) {
1288 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1289 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1290 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND headertag='$jobdb_headertag' AND status='processing'";
1291 my $res = $job_db->exec_statement( $sql_statement );
1292 foreach my $hit (@{$res}) {
1293 push @drops, "DELETE FROM $job_queue_tn WHERE id = '$jobdb_id'";
1294 }
1295 }
1296 }
1298 # Commit deletion
1299 $job_db->exec_statementlist(\@drops);
1301 # Look for new jobs that could be executed
1302 foreach my $macaddress (keys %{$hits}) {
1304 # Look if there is an executing job
1305 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='processing'";
1306 my $res = $job_db->exec_statement( $sql_statement );
1308 # Skip new jobs for host if there is a processing job
1309 if(defined($res) and defined @{$res}[0]) {
1310 next;
1311 }
1313 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1314 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1315 my $job_msg = @{$hits->{$macaddress}->{$jobdb_headertag}}[7];
1317 daemon_log("J DEBUG: its time to execute $job_msg", 7);
1318 my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$macaddress'";
1319 my $res_hash = $known_clients_db->select_dbentry( $sql_statement );
1321 # expect macaddress is unique!!!!!!
1322 my $target = $res_hash->{1}->{hostname};
1324 # change header
1325 $job_msg =~ s/<header>job_/<header>gosa_/;
1327 # add sqlite_id
1328 $job_msg =~ s/<\/xml>$/<jobdb_id>$jobdb_id<\/jobdb_id><\/xml>/;
1330 $job_msg =~ /<header>(\S+)<\/header>/;
1331 my $header = $1 ;
1332 my $func_error = &send_msg_to_target($job_msg, $server_address, $GosaPackages_key, $header, "J");
1334 # update status in job queue to 'processing'
1335 $sql_statement = "UPDATE $job_queue_tn SET status='processing' WHERE id='$jobdb_id'";
1336 my $res = $job_db->update_dbentry($sql_statement);
1338 # We don't want parallel processing
1339 last;
1340 }
1341 }
1343 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1344 }
1347 sub watch_for_new_messages {
1348 my ($kernel,$heap) = @_[KERNEL, HEAP];
1349 my @coll_user_msg; # collection list of outgoing messages
1351 # check messaging_db for new incoming messages with executable timestamp
1352 my $timestamp = &get_time();
1353 my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( (CAST(timestamp AS INTEGER))<$timestamp AND flag='n' AND direction='in' )";
1354 my $res = $messaging_db->exec_statement( $sql_statement );
1356 foreach my $hit (@{$res}) {
1358 # create outgoing messages
1359 my $message_to = @{$hit}[2];
1361 # translate message_to to plain login name
1362 my @reciever_l = ($message_to);
1363 my $message_id = @{$hit}[8];
1365 #add each outgoing msg to messaging_db
1366 my $reciever;
1367 foreach $reciever (@reciever_l) {
1368 my $sql_statement = "INSERT INTO $messaging_tn (subject, message_from, message_to, flag, direction, delivery_time, message, timestamp, id) ".
1369 "VALUES ('".
1370 @{$hit}[0]."', '". # subject
1371 @{$hit}[1]."', '". # message_from
1372 $reciever."', '". # message_to
1373 "none"."', '". # flag
1374 "out"."', '". # direction
1375 @{$hit}[5]."', '". # delivery_time
1376 @{$hit}[6]."', '". # message
1377 $timestamp."', '". # timestamp
1378 @{$hit}[8]. # id
1379 "')";
1380 &daemon_log("M DEBUG: $sql_statement", 1);
1381 my $res = $messaging_db->exec_statement($sql_statement);
1382 &daemon_log("M INFO: message '".@{$hit}[8]."' is prepared for delivery to reciever '$reciever'", 5);
1383 }
1385 # send outgoing messages
1386 my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( flag='p' AND direction='out' )";
1387 my $res = $messaging_db->exec_statement( $sql_statement );
1388 foreach my $hit (@{$res}) {
1389 # add subject, from, to and message to list coll_user_msg
1390 my @user_msg = [@{$hit}[0], @{$hit}[1], $reciever, @{$hit}[6]];
1391 push( @coll_user_msg, \@user_msg);
1392 }
1394 # send outgoing list to myself (gosa-si-server) to deliver each message to user
1395 # reason for this workaround: if to much messages have to be delivered, it can come to
1396 # denial of service problems of the server. so, the incoming message list can be processed
1397 # by a forked child and gosa-si-server is always ready to work.
1398 my $collection_out_msg = &create_xml_hash("collection_user_messages", $server_address, $server_address);
1399 # add to hash 'msg1' => [subject, from, to, message]
1400 # hash to string
1401 # send msg to myself
1402 # TODO
1404 # set incoming message to flag d=deliverd
1405 $sql_statement = "UPDATE $messaging_tn SET flag='p' WHERE id='$message_id'";
1406 &daemon_log("M DEBUG: $sql_statement", 7);
1407 $res = $messaging_db->update_dbentry($sql_statement);
1408 &daemon_log("M INFO: message '".@{$hit}[8]."' is set to flag 'p' (processed)", 5);
1410 }
1412 $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1415 return;
1416 }
1419 sub watch_for_done_messages {
1420 my ($kernel,$heap) = @_[KERNEL, HEAP];
1422 $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1423 return;
1424 }
1427 sub get_ldap_handle {
1428 my ($session_id) = @_;
1429 my $heap;
1430 my $ldap_handle;
1432 if (not defined $session_id ) { $session_id = 0 };
1433 if ($session_id =~ /[^0-9]*/) { $session_id = 0 };
1435 if ($session_id == 0) {
1436 daemon_log("$session_id DEBUG: get_ldap_handle invoked without a session_id, create a new ldap_handle", 7);
1437 $ldap_handle = Net::LDAP->new( $ldap_uri );
1438 $ldap_handle->bind($ldap_admin_dn, password => $ldap_admin_password);
1440 } else {
1441 my $session_reference = $global_kernel->ID_id_to_session($session_id);
1442 if( defined $session_reference ) {
1443 $heap = $session_reference->get_heap();
1444 }
1446 if (not defined $heap) {
1447 daemon_log("$session_id DEBUG: cannot get heap for session_id '$session_id'", 7);
1448 return;
1449 }
1451 # TODO: This "if" is nonsense, because it doesn't prove that the
1452 # used handle is still valid - or if we've to reconnect...
1453 #if (not exists $heap->{ldap_handle}) {
1454 $ldap_handle = Net::LDAP->new( $ldap_uri );
1455 $ldap_handle->bind($ldap_admin_dn, password => $ldap_admin_password);
1456 $heap->{ldap_handle} = $ldap_handle;
1457 #}
1458 }
1459 return $ldap_handle;
1460 }
1463 sub change_fai_state {
1464 my ($st, $targets, $session_id) = @_;
1465 $session_id = 0 if not defined $session_id;
1466 # Set FAI state to localboot
1467 my %mapActions= (
1468 reboot => '',
1469 update => 'softupdate',
1470 localboot => 'localboot',
1471 reinstall => 'install',
1472 rescan => '',
1473 wake => '',
1474 memcheck => 'memcheck',
1475 sysinfo => 'sysinfo',
1476 install => 'install',
1477 );
1479 # Return if this is unknown
1480 if (!exists $mapActions{ $st }){
1481 daemon_log("$session_id ERROR: unknown action '$st', can not translate ot FAIstate", 1);
1482 return;
1483 }
1485 my $state= $mapActions{ $st };
1487 my $ldap_handle = &get_ldap_handle($session_id);
1488 if( defined($ldap_handle) ) {
1490 # Build search filter for hosts
1491 my $search= "(&(objectClass=GOhard)";
1492 foreach (@{$targets}){
1493 $search.= "(macAddress=$_)";
1494 }
1495 $search.= ")";
1497 # If there's any host inside of the search string, procress them
1498 if (!($search =~ /macAddress/)){
1499 daemon_log("$session_id ERROR: no macAddress found in filter statement for LDAP search: '$search'", 1);
1500 return;
1501 }
1503 # Perform search for Unit Tag
1504 my $mesg = $ldap_handle->search(
1505 base => $ldap_base,
1506 scope => 'sub',
1507 attrs => ['dn', 'FAIstate', 'objectClass'],
1508 filter => "$search"
1509 );
1511 if ($mesg->count) {
1512 my @entries = $mesg->entries;
1513 foreach my $entry (@entries) {
1514 # Only modify entry if it is not set to '$state'
1515 if ($entry->get_value("FAIstate") ne "$state"){
1516 daemon_log("$session_id INFO: Setting FAIstate to '$state' for ".$entry->dn, 5);
1517 my $result;
1518 my %tmp = map { $_ => 1 } $entry->get_value("objectClass");
1519 if (exists $tmp{'FAIobject'}){
1520 if ($state eq ''){
1521 $result= $ldap_handle->modify($entry->dn, changes => [
1522 delete => [ FAIstate => [] ] ]);
1523 } else {
1524 $result= $ldap_handle->modify($entry->dn, changes => [
1525 replace => [ FAIstate => $state ] ]);
1526 }
1527 } elsif ($state ne ''){
1528 $result= $ldap_handle->modify($entry->dn, changes => [
1529 add => [ objectClass => 'FAIobject' ],
1530 add => [ FAIstate => $state ] ]);
1531 }
1533 # Errors?
1534 if ($result->code){
1535 daemon_log("$session_id Error: Setting FAIstate to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1536 }
1537 } else {
1538 daemon_log("$session_id DEBUG FAIstate at host '".$entry->dn."' already at state '$st'", 7);
1539 }
1540 }
1541 }
1542 # if no ldap handle defined
1543 } else {
1544 daemon_log("$session_id ERROR: no LDAP handle defined for update FAIstate", 1);
1545 }
1547 }
1550 sub change_goto_state {
1551 my ($st, $targets, $session_id) = @_;
1552 $session_id = 0 if not defined $session_id;
1554 # Switch on or off?
1555 my $state= $st eq 'active' ? 'active': 'locked';
1557 my $ldap_handle = &get_ldap_handle($session_id);
1558 if( defined($ldap_handle) ) {
1560 # Build search filter for hosts
1561 my $search= "(&(objectClass=GOhard)";
1562 foreach (@{$targets}){
1563 $search.= "(macAddress=$_)";
1564 }
1565 $search.= ")";
1567 # If there's any host inside of the search string, procress them
1568 if (!($search =~ /macAddress/)){
1569 return;
1570 }
1572 # Perform search for Unit Tag
1573 my $mesg = $ldap_handle->search(
1574 base => $ldap_base,
1575 scope => 'sub',
1576 attrs => ['dn', 'gotoMode'],
1577 filter => "$search"
1578 );
1580 if ($mesg->count) {
1581 my @entries = $mesg->entries;
1582 foreach my $entry (@entries) {
1584 # Only modify entry if it is not set to '$state'
1585 if ($entry->get_value("gotoMode") ne $state){
1587 daemon_log("$session_id INFO: Setting gotoMode to '$state' for ".$entry->dn, 5);
1588 my $result;
1589 $result= $ldap_handle->modify($entry->dn, changes => [
1590 replace => [ gotoMode => $state ] ]);
1592 # Errors?
1593 if ($result->code){
1594 &daemon_log("$session_id Error: Setting gotoMode to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1595 }
1597 }
1598 }
1599 }
1601 }
1602 }
1605 sub run_create_fai_server_db {
1606 my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
1607 my $session_id = $session->ID;
1608 my $task = POE::Wheel::Run->new(
1609 Program => sub { &create_fai_server_db($table_name,$kernel, undef, $session_id) },
1610 StdoutEvent => "session_run_result",
1611 StderrEvent => "session_run_debug",
1612 CloseEvent => "session_run_done",
1613 );
1615 $heap->{task}->{ $task->ID } = $task;
1616 return;
1617 }
1620 sub create_fai_server_db {
1621 my ($table_name, $kernel, $dont_create_packages_list, $session_id) = @_;
1622 my $result;
1624 if (not defined $session_id) { $session_id = 0; }
1625 my $ldap_handle = &get_ldap_handle();
1626 if(defined($ldap_handle)) {
1627 daemon_log("$session_id INFO: create_fai_server_db: start", 5);
1628 my $mesg= $ldap_handle->search(
1629 base => $ldap_base,
1630 scope => 'sub',
1631 attrs => ['FAIrepository', 'gosaUnitTag'],
1632 filter => "(&(FAIrepository=*)(objectClass=FAIrepositoryServer))",
1633 );
1634 if($mesg->{'resultCode'} == 0 &&
1635 $mesg->count != 0) {
1636 foreach my $entry (@{$mesg->{entries}}) {
1637 if($entry->exists('FAIrepository')) {
1638 # Add an entry for each Repository configured for server
1639 foreach my $repo(@{$entry->get_value('FAIrepository', asref => 1)}) {
1640 my($tmp_url,$tmp_server,$tmp_release,$tmp_sections) = split(/\|/, $repo);
1641 my $tmp_tag= $entry->get_value('gosaUnitTag') || "";
1642 $result= $fai_server_db->add_dbentry( {
1643 table => $table_name,
1644 primkey => ['server', 'release', 'tag'],
1645 server => $tmp_url,
1646 release => $tmp_release,
1647 sections => $tmp_sections,
1648 tag => (length($tmp_tag)>0)?$tmp_tag:"",
1649 } );
1650 }
1651 }
1652 }
1653 }
1654 daemon_log("$session_id INFO: create_fai_server_db: finished", 5);
1656 # TODO: Find a way to post the 'create_packages_list_db' event
1657 if(not defined($dont_create_packages_list)) {
1658 &create_packages_list_db(undef, undef, $session_id);
1659 }
1660 }
1662 $ldap_handle->disconnect;
1663 return $result;
1664 }
1667 sub run_create_fai_release_db {
1668 my ($session, $heap, $table_name) = @_[SESSION, HEAP, ARG0];
1669 my $session_id = $session->ID;
1670 my $task = POE::Wheel::Run->new(
1671 Program => sub { &create_fai_release_db($table_name, $session_id) },
1672 StdoutEvent => "session_run_result",
1673 StderrEvent => "session_run_debug",
1674 CloseEvent => "session_run_done",
1675 );
1677 $heap->{task}->{ $task->ID } = $task;
1678 return;
1679 }
1682 sub create_fai_release_db {
1683 my ($table_name, $session_id) = @_;
1684 my $result;
1686 # used for logging
1687 if (not defined $session_id) { $session_id = 0; }
1689 my $ldap_handle = &get_ldap_handle();
1690 if(defined($ldap_handle)) {
1691 daemon_log("$session_id INFO: create_fai_release_db: start",5);
1692 my $mesg= $ldap_handle->search(
1693 base => $ldap_base,
1694 scope => 'sub',
1695 attrs => [],
1696 filter => "(&(objectClass=organizationalUnit)(ou=fai))",
1697 );
1698 if($mesg->{'resultCode'} == 0 &&
1699 $mesg->count != 0) {
1700 # Walk through all possible FAI container ou's
1701 my @sql_list;
1702 my $timestamp= &get_time();
1703 foreach my $ou (@{$mesg->{entries}}) {
1704 my $tmp_classes= resolve_fai_classes($ou->dn, $ldap_handle, $session_id);
1705 if(defined($tmp_classes) && ref($tmp_classes) eq 'HASH') {
1706 my @tmp_array=get_fai_release_entries($tmp_classes);
1707 if(@tmp_array) {
1708 foreach my $entry (@tmp_array) {
1709 if(defined($entry) && ref($entry) eq 'HASH') {
1710 my $sql=
1711 "INSERT INTO $table_name "
1712 ."(timestamp, release, class, type, state) VALUES ("
1713 .$timestamp.","
1714 ."'".$entry->{'release'}."',"
1715 ."'".$entry->{'class'}."',"
1716 ."'".$entry->{'type'}."',"
1717 ."'".$entry->{'state'}."')";
1718 push @sql_list, $sql;
1719 }
1720 }
1721 }
1722 }
1723 }
1725 daemon_log("$session_id DEBUG: Inserting ".scalar @sql_list." entries to DB",8);
1726 if(@sql_list) {
1727 unshift @sql_list, "DELETE FROM $table_name";
1728 $fai_release_db->exec_statementlist(\@sql_list);
1729 }
1730 daemon_log("$session_id DEBUG: Done with inserting",6);
1731 }
1732 daemon_log("$session_id INFO: create_fai_release_db: finished",5);
1733 }
1734 $ldap_handle->disconnect;
1735 return $result;
1736 }
1738 sub get_fai_types {
1739 my $tmp_classes = shift || return undef;
1740 my @result;
1742 foreach my $type(keys %{$tmp_classes}) {
1743 if(defined($tmp_classes->{$type}[0]) && (!($tmp_classes->{$type}[0] =~ /^.*?removed.*?$/))) {
1744 my $entry = {
1745 type => $type,
1746 state => $tmp_classes->{$type}[0],
1747 };
1748 push @result, $entry;
1749 }
1750 }
1752 return @result;
1753 }
1755 sub get_fai_state {
1756 my $result = "";
1757 my $tmp_classes = shift || return $result;
1759 foreach my $type(keys %{$tmp_classes}) {
1760 if(defined($tmp_classes->{$type}[0])) {
1761 $result = $tmp_classes->{$type}[0];
1763 # State is equal for all types in class
1764 last;
1765 }
1766 }
1768 return $result;
1769 }
1771 sub resolve_fai_classes {
1772 my ($fai_base, $ldap_handle, $session_id) = @_;
1773 if (not defined $session_id) { $session_id = 0; }
1774 my $result;
1775 my @possible_fai_classes= ("FAIscript", "FAIhook", "FAIpartitionTable", "FAItemplate", "FAIvariable", "FAIprofile", "FAIpackageList");
1776 my $fai_filter= "(|(&(objectClass=FAIclass)(|(objectClass=".join(")(objectClass=", @possible_fai_classes).")))(objectClass=FAIbranch))";
1777 my $fai_classes;
1779 daemon_log("$session_id DEBUG: Searching for FAI entries in base $fai_base",6);
1780 my $mesg= $ldap_handle->search(
1781 base => $fai_base,
1782 scope => 'sub',
1783 attrs => ['cn','objectClass','FAIstate'],
1784 filter => $fai_filter,
1785 );
1786 daemon_log("$session_id DEBUG: Found ".$mesg->count()." FAI entries",6);
1788 if($mesg->{'resultCode'} == 0 &&
1789 $mesg->count != 0) {
1790 foreach my $entry (@{$mesg->{entries}}) {
1791 if($entry->exists('cn')) {
1792 my $tmp_dn= $entry->dn();
1794 # Skip classname and ou dn parts for class
1795 my $tmp_release = ($1) if $tmp_dn =~ /^[^,]+,[^,]+,(.*?),$fai_base$/;
1797 # Skip classes without releases
1798 if((!defined($tmp_release)) || length($tmp_release)==0) {
1799 next;
1800 }
1802 my $tmp_cn= $entry->get_value('cn');
1803 my $tmp_state= $entry->get_value('FAIstate');
1805 my $tmp_type;
1806 # Get FAI type
1807 for my $oclass(@{$entry->get_value('objectClass', asref => 1)}) {
1808 if(grep $_ eq $oclass, @possible_fai_classes) {
1809 $tmp_type= $oclass;
1810 last;
1811 }
1812 }
1814 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
1815 # A Subrelease
1816 my @sub_releases = split(/,/, $tmp_release);
1818 # Walk through subreleases and build hash tree
1819 my $hash;
1820 while(my $tmp_sub_release = pop @sub_releases) {
1821 $hash .= "\{'$tmp_sub_release'\}->";
1822 }
1823 eval('push @{$fai_classes->'.$hash.'{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";');
1824 } else {
1825 # A branch, no subrelease
1826 push @{$fai_classes->{$tmp_release}->{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";
1827 }
1828 } elsif (!$entry->exists('cn')) {
1829 my $tmp_dn= $entry->dn();
1830 my $tmp_release = ($1) if $tmp_dn =~ /^(.*?),$fai_base$/;
1832 # Skip classes without releases
1833 if((!defined($tmp_release)) || length($tmp_release)==0) {
1834 next;
1835 }
1837 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
1838 # A Subrelease
1839 my @sub_releases= split(/,/, $tmp_release);
1841 # Walk through subreleases and build hash tree
1842 my $hash;
1843 while(my $tmp_sub_release = pop @sub_releases) {
1844 $hash .= "\{'$tmp_sub_release'\}->";
1845 }
1846 # Remove the last two characters
1847 chop($hash);
1848 chop($hash);
1850 eval('$fai_classes->'.$hash.'= {}');
1851 } else {
1852 # A branch, no subrelease
1853 if(!exists($fai_classes->{$tmp_release})) {
1854 $fai_classes->{$tmp_release} = {};
1855 }
1856 }
1857 }
1858 }
1860 # The hash is complete, now we can honor the copy-on-write based missing entries
1861 foreach my $release (keys %$fai_classes) {
1862 $result->{$release}= deep_copy(apply_fai_inheritance($fai_classes->{$release}));
1863 }
1864 }
1865 return $result;
1866 }
1868 sub apply_fai_inheritance {
1869 my $fai_classes = shift || return {};
1870 my $tmp_classes;
1872 # Get the classes from the branch
1873 foreach my $class (keys %{$fai_classes}) {
1874 # Skip subreleases
1875 if($class =~ /^ou=.*$/) {
1876 next;
1877 } else {
1878 $tmp_classes->{$class}= deep_copy($fai_classes->{$class});
1879 }
1880 }
1882 # Apply to each subrelease
1883 foreach my $subrelease (keys %{$fai_classes}) {
1884 if($subrelease =~ /ou=/) {
1885 foreach my $tmp_class (keys %{$tmp_classes}) {
1886 if(!exists($fai_classes->{$subrelease}->{$tmp_class})) {
1887 $fai_classes->{$subrelease}->{$tmp_class} =
1888 deep_copy($tmp_classes->{$tmp_class});
1889 } else {
1890 foreach my $type (keys %{$tmp_classes->{$tmp_class}}) {
1891 if(!exists($fai_classes->{$subrelease}->{$tmp_class}->{$type})) {
1892 $fai_classes->{$subrelease}->{$tmp_class}->{$type}=
1893 deep_copy($tmp_classes->{$tmp_class}->{$type});
1894 }
1895 }
1896 }
1897 }
1898 }
1899 }
1901 # Find subreleases in deeper levels
1902 foreach my $subrelease (keys %{$fai_classes}) {
1903 if($subrelease =~ /ou=/) {
1904 foreach my $subsubrelease (keys %{$fai_classes->{$subrelease}}) {
1905 if($subsubrelease =~ /ou=/) {
1906 apply_fai_inheritance($fai_classes->{$subrelease});
1907 }
1908 }
1909 }
1910 }
1912 return $fai_classes;
1913 }
1915 sub get_fai_release_entries {
1916 my $tmp_classes = shift || return;
1917 my $parent = shift || "";
1918 my @result = shift || ();
1920 foreach my $entry (keys %{$tmp_classes}) {
1921 if(defined($entry)) {
1922 if($entry =~ /^ou=.*$/) {
1923 my $release_name = $entry;
1924 $release_name =~ s/ou=//g;
1925 if(length($parent)>0) {
1926 $release_name = $parent."/".$release_name;
1927 }
1928 my @bufentries = get_fai_release_entries($tmp_classes->{$entry}, $release_name, @result);
1929 foreach my $bufentry(@bufentries) {
1930 push @result, $bufentry;
1931 }
1932 } else {
1933 my @types = get_fai_types($tmp_classes->{$entry});
1934 foreach my $type (@types) {
1935 push @result,
1936 {
1937 'class' => $entry,
1938 'type' => $type->{'type'},
1939 'release' => $parent,
1940 'state' => $type->{'state'},
1941 };
1942 }
1943 }
1944 }
1945 }
1947 return @result;
1948 }
1950 sub deep_copy {
1951 my $this = shift;
1952 if (not ref $this) {
1953 $this;
1954 } elsif (ref $this eq "ARRAY") {
1955 [map deep_copy($_), @$this];
1956 } elsif (ref $this eq "HASH") {
1957 +{map { $_ => deep_copy($this->{$_}) } keys %$this};
1958 } else { die "what type is $_?" }
1959 }
1962 sub session_run_result {
1963 my ($kernel, $heap, $client_answer) = @_[KERNEL, HEAP, ARG0];
1964 $kernel->sig(CHLD => "child_reap");
1965 }
1967 sub session_run_debug {
1968 my $result = $_[ARG0];
1969 print STDERR "$result\n";
1970 }
1972 sub session_run_done {
1973 my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1974 delete $heap->{task}->{$task_id};
1975 }
1977 sub create_sources_list {
1978 my $ldap_handle = &get_ldap_handle;
1979 my $result="/tmp/gosa_si_tmp_sources_list";
1981 # Remove old file
1982 if(stat($result)) {
1983 unlink($result);
1984 }
1986 my $fh;
1987 open($fh, ">$result") or return undef;
1988 if(defined($ldap_server_dn) and length($ldap_server_dn) > 0) {
1989 my $mesg=$ldap_handle->search(
1990 base => $ldap_server_dn,
1991 scope => 'base',
1992 attrs => 'FAIrepository',
1993 filter => 'objectClass=FAIrepositoryServer'
1994 );
1995 if($mesg->count) {
1996 foreach my $entry(@{$mesg->{'entries'}}) {
1997 foreach my $value(@{$entry->get_value('FAIrepository', asref => 1)}) {
1998 my ($server, $tag, $release, $sections)= split /\|/, $value;
1999 my $line = "deb $server $release";
2000 $sections =~ s/,/ /g;
2001 $line.= " $sections";
2002 print $fh $line."\n";
2003 }
2004 }
2005 }
2006 }
2007 close($fh);
2009 return $result;
2010 }
2013 sub run_create_packages_list_db {
2014 my ($session, $heap) = @_[SESSION, HEAP];
2015 my $session_id = $session->ID;
2017 my $task = POE::Wheel::Run->new(
2018 Program => sub {&create_packages_list_db(undef, undef, $session_id)},
2019 StdoutEvent => "session_run_result",
2020 StderrEvent => "session_run_debug",
2021 CloseEvent => "session_run_done",
2022 );
2023 $heap->{task}->{ $task->ID } = $task;
2024 }
2027 sub create_packages_list_db {
2028 my ($ldap_handle, $sources_file, $session_id);
2030 if (not defined $session_id) { $session_id = 0; }
2031 if (not defined $ldap_handle) {
2032 $ldap_handle= &get_ldap_handle();
2034 if (not defined $ldap_handle) {
2035 daemon_log("$session_id ERROR: no ldap_handle available to create_packages_list_db", 1);
2036 return;
2037 }
2038 }
2039 if (not defined $sources_file) {
2040 $sources_file = &create_sources_list;
2041 }
2043 # it should not be possible to trigger a recreation of packages_list_db
2044 # while packages_list_db is under construction, so set flag packages_list_under_construction
2045 # which is tested befor recreation can be started
2046 if ($packages_list_under_construction) {
2047 daemon_log("$session_id WARNING: packages_list_db is right now under construction, please wait untill this process is finished", 3);
2048 return;
2049 } else {
2050 daemon_log("$session_id INFO: create_packages_list_db: start", 5);
2051 # set packages_list_under_construction to true
2052 $packages_list_under_construction = 1;
2053 }
2054 my $line;
2056 open(CONFIG, "<$sources_file") or do {
2057 daemon_log( "$session_id ERROR: create_packages_list_db: Failed to open '$sources_file'", 1);
2058 return;
2059 };
2061 # Read lines
2062 while ($line = <CONFIG>){
2063 # Unify
2064 chop($line);
2065 $line =~ s/^\s+//;
2066 $line =~ s/^\s+/ /;
2068 # Strip comments
2069 $line =~ s/#.*$//g;
2071 # Skip empty lines
2072 if ($line =~ /^\s*$/){
2073 next;
2074 }
2076 # Interpret deb line
2077 if ($line =~ /^deb [^\s]+\s[^\s]+\s[^\s]+/){
2078 my( $baseurl, $dist, $sections ) = ($line =~ /^deb\s([^\s]+)\s+([^\s]+)\s+(.*)$/);
2079 my $section;
2080 foreach $section (split(' ', $sections)){
2081 &parse_package_info( $baseurl, $dist, $section, $session_id );
2082 }
2083 }
2084 }
2086 close (CONFIG);
2088 daemon_log("$session_id INFO: create_packages_list_db: finished", 5);
2089 # set packages_list_under_construction to false
2090 $packages_list_under_construction = 0;
2092 return;
2093 }
2096 sub parse_package_info {
2097 my ($baseurl, $dist, $section, $session_id)= @_;
2098 my ($package);
2099 if (not defined $session_id) { $session_id = 0; }
2100 my ($path) = ($baseurl =~ m%://[^/]*(.*)$%);
2101 $repo_dirs{ "${repo_path}/pool" } = 1;
2103 foreach $package ("Packages.gz"){
2104 daemon_log("$session_id DEBUG: create_packages_list: fetch $baseurl, $dist, $section", 7);
2105 get_package( "$baseurl/dists/$dist/$section/binary-$arch/$package", "$outdir/$dist/$section", $session_id );
2106 parse_package( "$outdir/$dist/$section", $dist, $path, $session_id );
2107 }
2108 find(\&cleanup_and_extract, keys( %repo_dirs ));
2109 }
2112 sub get_package {
2113 my ($url, $dest, $session_id)= @_;
2114 if (not defined $session_id) { $session_id = 0; }
2116 my $tpath = dirname($dest);
2117 -d "$tpath" || mkpath "$tpath";
2119 # This is ugly, but I've no time to take a look at "how it works in perl"
2120 if(0 == system("wget '$url' -O '$dest' 2>/dev/null") ) {
2121 system("gunzip -cd '$dest' > '$dest.in'");
2122 daemon_log("$session_id DEBUG: run command: gunzip -cd '$dest' > '$dest.in'", 5);
2123 # unlink($dest);
2124 daemon_log("$session_id DEBUG: delete file '$dest'", 5);
2125 } else {
2126 daemon_log("$session_id ERROR: create_packages_list_db: get_packages: fetching '$url' failed!", 1);
2127 }
2128 return 0;
2129 }
2131 sub parse_package {
2132 my ($path, $dist, $srv_path, $session_id)= @_;
2133 if (not defined $session_id) { $session_id = 0;}
2134 my ($package, $version, $section, $description);
2135 my @sql_list;
2136 my $PACKAGES;
2138 if(not stat("$path.in")) {
2139 daemon_log("$session_id ERROR: create_packages_list: parse_package: file '$path.in' is not readable",1);
2140 return;
2141 }
2143 open($PACKAGES, "<$path.in");
2144 if(not defined($PACKAGES)) {
2145 daemon_log("$session_id ERROR: create_packages_list_db: parse_package: cannot open '$path.in'",1);
2146 return;
2147 }
2149 # Read lines
2150 while (<$PACKAGES>){
2151 my $line = $_;
2152 # Unify
2153 chop($line);
2155 # Use empty lines as a trigger
2156 if ($line =~ /^\s*$/){
2157 my $sql = "INSERT INTO packages_list VALUES ('$dist', '$package', '$version', '$section', '', 'none', '0')";
2158 push(@sql_list, $sql);
2159 $package = "none";
2160 $version = "none";
2161 $section = "none";
2162 $description = "none";
2163 next;
2164 }
2166 # Trigger for package name
2167 if ($line =~ /^Package:\s/){
2168 ($package)= ($line =~ /^Package: (.*)$/);
2169 next;
2170 }
2172 # Trigger for version
2173 if ($line =~ /^Version:\s/){
2174 ($version)= ($line =~ /^Version: (.*)$/);
2175 next;
2176 }
2178 # Trigger for description
2179 if ($line =~ /^Description:\s/){
2180 ($description)= ($line =~ /^Description: (.*)$/);
2181 next;
2182 }
2184 # Trigger for section
2185 if ($line =~ /^Section:\s/){
2186 ($section)= ($line =~ /^Section: (.*)$/);
2187 next;
2188 }
2190 # Trigger for filename
2191 if ($line =~ /^Filename:\s/){
2192 my ($filename) = ($line =~ /^Filename: (.*)$/);
2193 store_fileinfo( $package, $filename, $dist, $srv_path, $version, $repo_path );
2194 next;
2195 }
2196 }
2198 close( $PACKAGES );
2199 # unlink( "$path.in" );
2201 $packages_list_db->exec_statementlist(\@sql_list);
2202 }
2204 sub store_fileinfo {
2205 my( $package, $file, $dist, $path, $vers, $srvdir) = @_;
2207 my %fileinfo = (
2208 'package' => $package,
2209 'dist' => $dist,
2210 'version' => $vers,
2211 );
2213 $repo_files{ "${srvdir}/$file" } = \%fileinfo;
2214 }
2216 sub cleanup_and_extract {
2217 my ($session_id) = @_;
2218 if (not defined $session_id) { $session_id = 0; }
2219 my $fileinfo = $repo_files{ $File::Find::name };
2221 if( defined $fileinfo ) {
2223 my $dir = "$outdir/$fileinfo->{ 'dist' }/debconf.d";
2224 my $sql;
2225 my $package = $fileinfo->{ 'package' };
2226 my $newver = $fileinfo->{ 'version' };
2228 mkpath($dir);
2229 system( "dpkg -e '$File::Find::name' '$dir/DEBIAN'" );
2231 if( -f "$dir/DEBIAN/templates" ) {
2233 daemon_log("$session_id DEBUG: Found debconf templates in '$package' - $newver", 5);
2235 my $tmpl= "";
2236 {
2237 local $/=undef;
2238 open FILE, "$dir/DEBIAN/templates";
2239 $tmpl = &encode_base64(<FILE>);
2240 close FILE;
2241 }
2242 rmtree("$dir/DEBIAN/templates");
2244 $sql= "update $main::packages_list_tn set template = '$tmpl' where package = '$package' and version = '$newver';";
2246 } else {
2247 $sql= "update $main::packages_list_tn set template = '' where package = '$package' and version = '$newver';";
2248 }
2250 my $res= $main::packages_list_db->update_dbentry($sql);
2251 }
2252 }
2255 #==== MAIN = main ==============================================================
2256 # parse commandline options
2257 Getopt::Long::Configure( "bundling" );
2258 GetOptions("h|help" => \&usage,
2259 "c|config=s" => \$cfg_file,
2260 "f|foreground" => \$foreground,
2261 "v|verbose+" => \$verbose,
2262 "no-bus+" => \$no_bus,
2263 "no-arp+" => \$no_arp,
2264 );
2266 # read and set config parameters
2267 &check_cmdline_param ;
2268 &read_configfile;
2269 &check_pid;
2271 $SIG{CHLD} = 'IGNORE';
2273 # forward error messages to logfile
2274 if( ! $foreground ) {
2275 open( STDIN, '+>/dev/null' );
2276 open( STDOUT, '+>&STDIN' );
2277 open( STDERR, '+>&STDIN' );
2278 }
2280 # Just fork, if we are not in foreground mode
2281 if( ! $foreground ) {
2282 chdir '/' or die "Can't chdir to /: $!";
2283 $pid = fork;
2284 setsid or die "Can't start a new session: $!";
2285 umask 0;
2286 } else {
2287 $pid = $$;
2288 }
2290 # Do something useful - put our PID into the pid_file
2291 if( 0 != $pid ) {
2292 open( LOCK_FILE, ">$pid_file" );
2293 print LOCK_FILE "$pid\n";
2294 close( LOCK_FILE );
2295 if( !$foreground ) {
2296 exit( 0 )
2297 };
2298 }
2300 daemon_log(" ", 1);
2301 daemon_log("$0 started!", 1);
2303 if ($no_bus > 0) {
2304 $bus_activ = "false"
2305 }
2307 # connect to gosa-si job queue
2308 $job_db = GOSA::DBsqlite->new($job_queue_file_name);
2309 $job_db->create_table($job_queue_tn, \@job_queue_col_names);
2311 # connect to known_clients_db
2312 $known_clients_db = GOSA::DBsqlite->new($known_clients_file_name);
2313 $known_clients_db->create_table($known_clients_tn, \@known_clients_col_names);
2315 # connect to known_server_db
2316 $known_server_db = GOSA::DBsqlite->new($known_server_file_name);
2317 $known_server_db->create_table($known_server_tn, \@known_server_col_names);
2319 # connect to login_usr_db
2320 $login_users_db = GOSA::DBsqlite->new($login_users_file_name);
2321 $login_users_db->create_table($login_users_tn, \@login_users_col_names);
2323 # connect to fai_server_db and fai_release_db
2324 unlink($fai_server_file_name);
2325 $fai_server_db = GOSA::DBsqlite->new($fai_server_file_name);
2326 $fai_server_db->create_table($fai_server_tn, \@fai_server_col_names);
2328 unlink($fai_release_file_name);
2329 $fai_release_db = GOSA::DBsqlite->new($fai_release_file_name);
2330 $fai_release_db->create_table($fai_release_tn, \@fai_release_col_names);
2332 # connect to packages_list_db
2333 unlink($packages_list_file_name);
2334 $packages_list_db = GOSA::DBsqlite->new($packages_list_file_name);
2335 $packages_list_db->create_table($packages_list_tn, \@packages_list_col_names);
2337 # connect to messaging_db
2338 $messaging_db = GOSA::DBsqlite->new($messaging_file_name);
2339 $messaging_db->create_table($messaging_tn, \@messaging_col_names);
2342 # create xml object used for en/decrypting
2343 $xml = new XML::Simple();
2345 # create socket for incoming xml messages
2347 POE::Component::Server::TCP->new(
2348 Port => $server_port,
2349 ClientInput => sub {
2350 my ($kernel, $input) = @_[KERNEL, ARG0];
2351 push(@tasks, $input);
2352 $kernel->yield("next_task");
2353 },
2354 InlineStates => {
2355 next_task => \&next_task,
2356 task_result => \&handle_task_result,
2357 task_done => \&handle_task_done,
2358 task_debug => \&handle_task_debug,
2359 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!" },
2360 }
2361 );
2363 daemon_log("start socket for incoming xml messages at port '$server_port' ", 1);
2365 # create session for repeatedly checking the job queue for jobs
2366 POE::Session->create(
2367 inline_states => {
2368 _start => \&_start,
2369 sig_handler => \&sig_handler,
2370 watch_for_new_messages => \&watch_for_new_messages,
2371 watch_for_done_messages => \&watch_for_done_messages,
2372 watch_for_new_jobs => \&watch_for_new_jobs,
2373 watch_for_done_jobs => \&watch_for_done_jobs,
2374 create_packages_list_db => \&run_create_packages_list_db,
2375 create_fai_server_db => \&run_create_fai_server_db,
2376 create_fai_release_db => \&run_create_fai_release_db,
2377 session_run_result => \&session_run_result,
2378 session_run_debug => \&session_run_debug,
2379 session_run_done => \&session_run_done,
2380 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!" },
2381 }
2382 );
2385 # import all modules
2386 &import_modules;
2388 # check wether all modules are gosa-si valid passwd check
2390 POE::Kernel->run();
2391 exit;