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