5b125a6c137d547a58ad022098385137984b8546
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 # TODO
25 #
26 # max_children wird momentan nicht mehr verwendet, jede eingehende nachricht bekommt ein eigenes POE child
28 use strict;
29 use warnings;
30 use Getopt::Long;
31 use Config::IniFiles;
32 use POSIX;
34 use Fcntl;
35 use IO::Socket::INET;
36 use IO::Handle;
37 use IO::Select;
38 use Symbol qw(qualify_to_ref);
39 use Crypt::Rijndael;
40 use MIME::Base64;
41 use Digest::MD5 qw(md5 md5_hex md5_base64);
42 use XML::Simple;
43 use Data::Dumper;
44 use Sys::Syslog qw( :DEFAULT setlogsock);
45 use Cwd;
46 use File::Spec;
47 use File::Basename;
48 use File::Find;
49 use File::Copy;
50 use File::Path;
51 use GOSA::DBsqlite;
52 use GOSA::GosaSupportDaemon;
53 use POE qw(Component::Server::TCP Wheel::Run Filter::Reference);
54 use Net::LDAP;
55 use Net::LDAP::Util qw(:escape);
56 use Time::HiRes qw( usleep);
57 use DateTime;
59 my $modules_path = "/usr/lib/gosa-si/modules";
60 use lib "/usr/lib/gosa-si/modules";
62 # revision number of server and program name
63 my $server_version = '$HeadURL: https://oss.gonicus.de/repositories/gosa/trunk/gosa-si/gosa-si-server $:$Rev: 10826 $';
64 my $server_headURL;
65 my $server_revision;
66 my $server_status;
67 our $prg= basename($0);
69 our $global_kernel;
70 my ($foreground, $ping_timeout);
71 my ($server);
72 my ($gosa_server, $job_queue_timeout, $job_queue_loop_delay);
73 my ($messaging_db_loop_delay);
74 my ($known_modules);
75 my ($procid, $pid);
76 my ($arp_fifo);
77 my ($xml);
78 my $sources_list;
79 my $max_clients;
80 my %repo_files=();
81 my $repo_path;
82 my %repo_dirs=();
83 # variables declared in config file are always set to 'our'
84 our (%cfg_defaults, $log_file, $pid_file,
85 $server_ip, $server_port, $ClientPackages_key,
86 $arp_activ, $gosa_unit_tag,
87 $GosaPackages_key, $gosa_ip, $gosa_port, $gosa_timeout,
88 $foreign_server_string, $server_domain, $ServerPackages_key, $foreign_servers_register_delay,
89 );
91 # additional variable which should be globaly accessable
92 our $server_address;
93 our $server_mac_address;
94 our $gosa_address;
95 our $no_arp;
96 our $verbose;
97 our $forground;
98 our $cfg_file;
99 our ($ldap_uri, $ldap_base, $ldap_admin_dn, $ldap_admin_password, $ldap_server_dn);
102 # specifies the verbosity of the daemon_log
103 $verbose = 0 ;
105 # if foreground is not null, script will be not forked to background
106 $foreground = 0 ;
108 # specifies the timeout seconds while checking the online status of a registrating client
109 $ping_timeout = 5;
111 $no_arp = 0;
112 my $packages_list_under_construction = "/tmp/packages_list_creation_in_progress";
113 my @packages_list_statements;
114 my $watch_for_new_jobs_in_progress = 0;
116 # holds all incoming decrypted messages
117 our $incoming_db;
118 our $incoming_tn = 'incoming';
119 my $incoming_file_name;
120 my @incoming_col_names = ("id INTEGER PRIMARY KEY",
121 "timestamp DEFAULT 'none'",
122 "headertag DEFAULT 'none'",
123 "targettag DEFAULT 'none'",
124 "xmlmessage DEFAULT 'none'",
125 "module DEFAULT 'none'",
126 "sessionid DEFAULT '0'",
127 );
129 # holds all gosa jobs
130 our $job_db;
131 our $job_queue_tn = 'jobs';
132 my $job_queue_file_name;
133 my @job_queue_col_names = ("id INTEGER PRIMARY KEY",
134 "timestamp DEFAULT 'none'",
135 "status DEFAULT 'none'",
136 "result DEFAULT 'none'",
137 "progress DEFAULT 'none'",
138 "headertag DEFAULT 'none'",
139 "targettag DEFAULT 'none'",
140 "xmlmessage DEFAULT 'none'",
141 "macaddress DEFAULT 'none'",
142 "plainname DEFAULT 'none'",
143 );
145 # holds all other gosa-si-server
146 our $known_server_db;
147 our $known_server_tn = "known_server";
148 my $known_server_file_name;
149 my @known_server_col_names = ("hostname", "status", "hostkey", "timestamp");
151 # holds all registrated clients
152 our $known_clients_db;
153 our $known_clients_tn = "known_clients";
154 my $known_clients_file_name;
155 my @known_clients_col_names = ("hostname", "status", "hostkey", "timestamp", "macaddress", "events", "keylifetime");
157 # holds all registered clients at a foreign server
158 our $foreign_clients_db;
159 our $foreign_clients_tn = "foreign_clients";
160 my $foreign_clients_file_name;
161 my @foreign_clients_col_names = ("hostname", "macaddress", "regserver", "timestamp");
163 # holds all logged in user at each client
164 our $login_users_db;
165 our $login_users_tn = "login_users";
166 my $login_users_file_name;
167 my @login_users_col_names = ("client", "user", "timestamp");
169 # holds all fai server, the debian release and tag
170 our $fai_server_db;
171 our $fai_server_tn = "fai_server";
172 my $fai_server_file_name;
173 our @fai_server_col_names = ("timestamp", "server", "release", "sections", "tag");
175 our $fai_release_db;
176 our $fai_release_tn = "fai_release";
177 my $fai_release_file_name;
178 our @fai_release_col_names = ("timestamp", "release", "class", "type", "state");
180 # holds all packages available from different repositories
181 our $packages_list_db;
182 our $packages_list_tn = "packages_list";
183 my $packages_list_file_name;
184 our @packages_list_col_names = ("distribution", "package", "version", "section", "description", "template", "timestamp");
185 my $outdir = "/tmp/packages_list_db";
186 my $arch = "i386";
188 # holds all messages which should be delivered to a user
189 our $messaging_db;
190 our $messaging_tn = "messaging";
191 our @messaging_col_names = ("id INTEGER", "subject", "message_from", "message_to",
192 "flag", "direction", "delivery_time", "message", "timestamp" );
193 my $messaging_file_name;
195 # path to directory to store client install log files
196 our $client_fai_log_dir = "/var/log/fai";
198 # queue which stores taskes until one of the $max_children children are ready to process the task
199 my @tasks = qw();
200 my @msgs_to_decrypt = qw();
201 my $max_children = 2;
204 %cfg_defaults = (
205 "general" => {
206 "log-file" => [\$log_file, "/var/run/".$prg.".log"],
207 "pid-file" => [\$pid_file, "/var/run/".$prg.".pid"],
208 },
209 "server" => {
210 "port" => [\$server_port, "20081"],
211 "known-clients" => [\$known_clients_file_name, '/var/lib/gosa-si/clients.db' ],
212 "known-servers" => [\$known_server_file_name, '/var/lib/gosa-si/servers.db'],
213 "incoming" => [\$incoming_file_name, '/var/lib/gosa-si/incoming.db'],
214 "login-users" => [\$login_users_file_name, '/var/lib/gosa-si/users.db'],
215 "fai-server" => [\$fai_server_file_name, '/var/lib/gosa-si/fai_server.db'],
216 "fai-release" => [\$fai_release_file_name, '/var/lib/gosa-si/fai_release.db'],
217 "packages-list" => [\$packages_list_file_name, '/var/lib/gosa-si/packages.db'],
218 "messaging" => [\$messaging_file_name, '/var/lib/gosa-si/messaging.db'],
219 "foreign-clients" => [\$foreign_clients_file_name, '/var/lib/gosa-si/foreign_clients.db'],
220 "source-list" => [\$sources_list, '/etc/apt/sources.list'],
221 "repo-path" => [\$repo_path, '/srv/www/repository'],
222 "ldap-uri" => [\$ldap_uri, ""],
223 "ldap-base" => [\$ldap_base, ""],
224 "ldap-admin-dn" => [\$ldap_admin_dn, ""],
225 "ldap-admin-password" => [\$ldap_admin_password, ""],
226 "gosa-unit-tag" => [\$gosa_unit_tag, ""],
227 "max-clients" => [\$max_clients, 10],
228 },
229 "GOsaPackages" => {
230 "ip" => [\$gosa_ip, "0.0.0.0"],
231 "port" => [\$gosa_port, "20082"],
232 "job-queue" => [\$job_queue_file_name, '/var/lib/gosa-si/jobs.db'],
233 "job-queue-loop-delay" => [\$job_queue_loop_delay, 3],
234 "messaging-db-loop-delay" => [\$messaging_db_loop_delay, 3],
235 "key" => [\$GosaPackages_key, "none"],
236 },
237 "ClientPackages" => {
238 "key" => [\$ClientPackages_key, "none"],
239 },
240 "ServerPackages"=> {
241 "address" => [\$foreign_server_string, ""],
242 "domain" => [\$server_domain, ""],
243 "key" => [\$ServerPackages_key, "none"],
244 "key-lifetime" => [\$foreign_servers_register_delay, 120],
245 }
246 );
249 #=== FUNCTION ================================================================
250 # NAME: usage
251 # PARAMETERS: nothing
252 # RETURNS: nothing
253 # DESCRIPTION: print out usage text to STDERR
254 #===============================================================================
255 sub usage {
256 print STDERR << "EOF" ;
257 usage: $prg [-hvf] [-c config]
259 -h : this (help) message
260 -c <file> : config file
261 -f : foreground, process will not be forked to background
262 -v : be verbose (multiple to increase verbosity)
263 -no-arp : starts $prg without connection to arp module
265 EOF
266 print "\n" ;
267 }
270 #=== FUNCTION ================================================================
271 # NAME: read_configfile
272 # PARAMETERS: cfg_file - string -
273 # RETURNS: nothing
274 # DESCRIPTION: read cfg_file and set variables
275 #===============================================================================
276 sub read_configfile {
277 my $cfg;
278 if( defined( $cfg_file) && ( (-s $cfg_file) > 0 )) {
279 if( -r $cfg_file ) {
280 $cfg = Config::IniFiles->new( -file => $cfg_file );
281 } else {
282 print STDERR "Couldn't read config file!\n";
283 }
284 } else {
285 $cfg = Config::IniFiles->new() ;
286 }
287 foreach my $section (keys %cfg_defaults) {
288 foreach my $param (keys %{$cfg_defaults{ $section }}) {
289 my $pinfo = $cfg_defaults{ $section }{ $param };
290 ${@$pinfo[ 0 ]} = $cfg->val( $section, $param, @$pinfo[ 1 ] );
291 }
292 }
293 }
296 #=== FUNCTION ================================================================
297 # NAME: logging
298 # PARAMETERS: level - string - default 'info'
299 # msg - string -
300 # facility - string - default 'LOG_DAEMON'
301 # RETURNS: nothing
302 # DESCRIPTION: function for logging
303 #===============================================================================
304 sub daemon_log {
305 # log into log_file
306 my( $msg, $level ) = @_;
307 if(not defined $msg) { return }
308 if(not defined $level) { $level = 1 }
309 if(defined $log_file){
310 open(LOG_HANDLE, ">>$log_file");
311 chmod 0600, $log_file;
312 if(not defined open( LOG_HANDLE, ">>$log_file" )) {
313 print STDERR "cannot open $log_file: $!";
314 return
315 }
316 chomp($msg);
317 $msg =~s/\n//g; # no newlines are allowed in log messages, this is important for later log parsing
318 if($level <= $verbose){
319 my ($seconds, $minutes, $hours, $monthday, $month,
320 $year, $weekday, $yearday, $sommertime) = localtime(time);
321 $hours = $hours < 10 ? $hours = "0".$hours : $hours;
322 $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes;
323 $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds;
324 my @monthnames = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
325 $month = $monthnames[$month];
326 $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday;
327 $year+=1900;
328 my $name = $prg;
330 my $log_msg = "$month $monthday $hours:$minutes:$seconds $name $msg\n";
331 print LOG_HANDLE $log_msg;
332 if( $foreground ) {
333 print STDERR $log_msg;
334 }
335 }
336 close( LOG_HANDLE );
337 }
338 }
341 #=== FUNCTION ================================================================
342 # NAME: check_cmdline_param
343 # PARAMETERS: nothing
344 # RETURNS: nothing
345 # DESCRIPTION: validates commandline parameter
346 #===============================================================================
347 sub check_cmdline_param () {
348 my $err_config;
349 my $err_counter = 0;
350 if(not defined($cfg_file)) {
351 $cfg_file = "/etc/gosa-si/server.conf";
352 if(! -r $cfg_file) {
353 $err_config = "please specify a config file";
354 $err_counter += 1;
355 }
356 }
357 if( $err_counter > 0 ) {
358 &usage( "", 1 );
359 if( defined( $err_config)) { print STDERR "$err_config\n"}
360 print STDERR "\n";
361 exit( -1 );
362 }
363 }
366 #=== FUNCTION ================================================================
367 # NAME: check_pid
368 # PARAMETERS: nothing
369 # RETURNS: nothing
370 # DESCRIPTION: handels pid processing
371 #===============================================================================
372 sub check_pid {
373 $pid = -1;
374 # Check, if we are already running
375 if( open(LOCK_FILE, "<$pid_file") ) {
376 $pid = <LOCK_FILE>;
377 if( defined $pid ) {
378 chomp( $pid );
379 if( -f "/proc/$pid/stat" ) {
380 my($stat) = `cat /proc/$pid/stat` =~ m/$pid \((.+)\).*/;
381 if( $stat ) {
382 daemon_log("ERROR: Already running",1);
383 close( LOCK_FILE );
384 exit -1;
385 }
386 }
387 }
388 close( LOCK_FILE );
389 unlink( $pid_file );
390 }
392 # create a syslog msg if it is not to possible to open PID file
393 if (not sysopen(LOCK_FILE, $pid_file, O_WRONLY|O_CREAT|O_EXCL, 0644)) {
394 my($msg) = "Couldn't obtain lockfile '$pid_file' ";
395 if (open(LOCK_FILE, '<', $pid_file)
396 && ($pid = <LOCK_FILE>))
397 {
398 chomp($pid);
399 $msg .= "(PID $pid)\n";
400 } else {
401 $msg .= "(unable to read PID)\n";
402 }
403 if( ! ($foreground) ) {
404 openlog( $0, "cons,pid", "daemon" );
405 syslog( "warning", $msg );
406 closelog();
407 }
408 else {
409 print( STDERR " $msg " );
410 }
411 exit( -1 );
412 }
413 }
415 #=== FUNCTION ================================================================
416 # NAME: import_modules
417 # PARAMETERS: module_path - string - abs. path to the directory the modules
418 # are stored
419 # RETURNS: nothing
420 # DESCRIPTION: each file in module_path which ends with '.pm' and activation
421 # state is on is imported by "require 'file';"
422 #===============================================================================
423 sub import_modules {
424 daemon_log(" ", 1);
426 if (not -e $modules_path) {
427 daemon_log("0 ERROR: cannot find directory or directory is not readable: $modules_path", 1);
428 }
430 opendir (DIR, $modules_path) or die "ERROR while loading modules from directory $modules_path : $!\n";
431 while (defined (my $file = readdir (DIR))) {
432 if (not $file =~ /(\S*?).pm$/) {
433 next;
434 }
435 my $mod_name = $1;
437 if( $file =~ /ArpHandler.pm/ ) {
438 if( $no_arp > 0 ) {
439 next;
440 }
441 }
443 eval { require $file; };
444 if ($@) {
445 daemon_log("0 ERROR: gosa-si-server could not load module $file", 1);
446 daemon_log("$@", 5);
447 } else {
448 my $info = eval($mod_name.'::get_module_info()');
449 # Only load module if get_module_info() returns a non-null object
450 if( $info ) {
451 my ($input_address, $input_key, $input, $input_active, $input_type) = @{$info};
452 $known_modules->{$mod_name} = $info;
453 daemon_log("0 INFO: module $mod_name loaded", 5);
454 }
455 }
456 }
457 close (DIR);
458 }
461 #=== FUNCTION ================================================================
462 # NAME: sig_int_handler
463 # PARAMETERS: signal - string - signal arose from system
464 # RETURNS: noting
465 # DESCRIPTION: handels tasks to be done befor signal becomes active
466 #===============================================================================
467 sub sig_int_handler {
468 my ($signal) = @_;
470 # if (defined($ldap_handle)) {
471 # $ldap_handle->disconnect;
472 # }
473 # TODO alle verbliebenden ldap verbindungen aus allen heaps beenden
476 daemon_log("shutting down gosa-si-server", 1);
477 system("kill `ps -C gosa-si-server -o pid=`");
478 }
479 $SIG{INT} = \&sig_int_handler;
482 sub check_key_and_xml_validity {
483 my ($crypted_msg, $module_key, $session_id) = @_;
484 my $msg;
485 my $msg_hash;
486 my $error_string;
487 eval{
488 $msg = &decrypt_msg($crypted_msg, $module_key);
490 if ($msg =~ /<xml>/i){
491 $msg =~ s/\s+/ /g; # just for better daemon_log
492 daemon_log("$session_id DEBUG: decrypted_msg: \n$msg", 8);
493 $msg_hash = $xml->XMLin($msg, ForceArray=>1);
495 ##############
496 # check header
497 if( not exists $msg_hash->{'header'} ) { die "no header specified"; }
498 my $header_l = $msg_hash->{'header'};
499 if( 1 > @{$header_l} ) { die 'empty header tag'; }
500 if( 1 < @{$header_l} ) { die 'more than one header specified'; }
501 my $header = @{$header_l}[0];
502 if( 0 == length $header) { die 'empty string in header tag'; }
504 ##############
505 # check source
506 if( not exists $msg_hash->{'source'} ) { die "no source specified"; }
507 my $source_l = $msg_hash->{'source'};
508 if( 1 > @{$source_l} ) { die 'empty source tag'; }
509 if( 1 < @{$source_l} ) { die 'more than one source specified'; }
510 my $source = @{$source_l}[0];
511 if( 0 == length $source) { die 'source error'; }
513 ##############
514 # check target
515 if( not exists $msg_hash->{'target'} ) { die "no target specified"; }
516 my $target_l = $msg_hash->{'target'};
517 if( 1 > @{$target_l} ) { die 'empty target tag'; }
518 }
519 };
520 if($@) {
521 daemon_log("$session_id DEBUG: do not understand the message: $@", 7);
522 $msg = undef;
523 $msg_hash = undef;
524 }
526 return ($msg, $msg_hash);
527 }
530 sub check_outgoing_xml_validity {
531 my ($msg) = @_;
533 my $msg_hash;
534 eval{
535 $msg_hash = $xml->XMLin($msg, ForceArray=>1);
537 ##############
538 # check header
539 my $header_l = $msg_hash->{'header'};
540 if( 1 != @{$header_l} ) {
541 die 'no or more than one headers specified';
542 }
543 my $header = @{$header_l}[0];
544 if( 0 == length $header) {
545 die 'header has length 0';
546 }
548 ##############
549 # check source
550 my $source_l = $msg_hash->{'source'};
551 if( 1 != @{$source_l} ) {
552 die 'no or more than 1 sources specified';
553 }
554 my $source = @{$source_l}[0];
555 if( 0 == length $source) {
556 die 'source has length 0';
557 }
558 unless( $source =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
559 $source =~ /^GOSA$/i ) {
560 die "source '$source' is neither a complete ip-address with port nor 'GOSA'";
561 }
563 ##############
564 # check target
565 my $target_l = $msg_hash->{'target'};
566 if( 0 == @{$target_l} ) {
567 die "no targets specified";
568 }
569 foreach my $target (@$target_l) {
570 if( 0 == length $target) {
571 die "target has length 0";
572 }
573 unless( $target =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
574 $target =~ /^GOSA$/i ||
575 $target =~ /^\*$/ ||
576 $target =~ /KNOWN_SERVER/i ||
577 $target =~ /JOBDB/i ||
578 $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 ){
579 die "target '$target' is not a complete ip-address with port or a valid target name or a mac-address";
580 }
581 }
582 };
583 if($@) {
584 daemon_log("WARNING: outgoing msg is not gosa-si envelope conform", 5);
585 daemon_log("$@ ".(defined($msg) && length($msg)>0)?$msg:"Empty Message", 8);
586 $msg_hash = undef;
587 }
589 return ($msg_hash);
590 }
593 sub input_from_known_server {
594 my ($input, $remote_ip, $session_id) = @_ ;
595 my ($msg, $msg_hash, $module);
597 my $sql_statement= "SELECT * FROM known_server";
598 my $query_res = $known_server_db->select_dbentry( $sql_statement );
600 while( my ($hit_num, $hit) = each %{ $query_res } ) {
601 my $host_name = $hit->{hostname};
602 if( not $host_name =~ "^$remote_ip") {
603 next;
604 }
605 my $host_key = $hit->{hostkey};
606 daemon_log("$session_id DEBUG: input_from_known_server: host_name: $host_name", 7);
607 daemon_log("$session_id DEBUG: input_from_known_server: host_key: $host_key", 7);
609 # check if module can open msg envelope with module key
610 my ($tmp_msg, $tmp_msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
611 if( (!$tmp_msg) || (!$tmp_msg_hash) ) {
612 daemon_log("$session_id DEBUG: input_from_known_server: deciphering raise error", 7);
613 daemon_log("$@", 8);
614 next;
615 }
616 else {
617 $msg = $tmp_msg;
618 $msg_hash = $tmp_msg_hash;
619 $module = "ServerPackages";
620 last;
621 }
622 }
624 if( (!$msg) || (!$msg_hash) || (!$module) ) {
625 daemon_log("$session_id DEBUG: Incoming message is not from a known server", 7);
626 }
628 return ($msg, $msg_hash, $module);
629 }
632 sub input_from_known_client {
633 my ($input, $remote_ip, $session_id) = @_ ;
634 my ($msg, $msg_hash, $module);
636 my $sql_statement= "SELECT * FROM known_clients";
637 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
638 while( my ($hit_num, $hit) = each %{ $query_res } ) {
639 my $host_name = $hit->{hostname};
640 if( not $host_name =~ /^$remote_ip:\d*$/) {
641 next;
642 }
643 my $host_key = $hit->{hostkey};
644 &daemon_log("$session_id DEBUG: input_from_known_client: host_name: $host_name", 7);
645 &daemon_log("$session_id DEBUG: input_from_known_client: host_key: $host_key", 7);
647 # check if module can open msg envelope with module key
648 ($msg, $msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
650 if( (!$msg) || (!$msg_hash) ) {
651 &daemon_log("$session_id DEGUG: input_from_known_client: deciphering raise error", 7);
652 &daemon_log("$@", 8);
653 next;
654 }
655 else {
656 $module = "ClientPackages";
657 last;
658 }
659 }
661 if( (!$msg) || (!$msg_hash) || (!$module) ) {
662 &daemon_log("$session_id DEBUG: Incoming message is not from a known client", 7);
663 }
665 return ($msg, $msg_hash, $module);
666 }
669 sub input_from_unknown_host {
670 no strict "refs";
671 my ($input, $session_id) = @_ ;
672 my ($msg, $msg_hash, $module);
673 my $error_string;
675 my %act_modules = %$known_modules;
677 while( my ($mod, $info) = each(%act_modules)) {
679 # check a key exists for this module
680 my $module_key = ${$mod."_key"};
681 if( not defined $module_key ) {
682 if( $mod eq 'ArpHandler' ) {
683 next;
684 }
685 daemon_log("$session_id ERROR: no key specified in config file for $mod", 1);
686 next;
687 }
688 daemon_log("$session_id DEBUG: $mod: $module_key", 7);
690 # check if module can open msg envelope with module key
691 ($msg, $msg_hash) = &check_key_and_xml_validity($input, $module_key, $session_id);
692 if( (not defined $msg) || (not defined $msg_hash) ) {
693 next;
694 }
695 else {
696 $module = $mod;
697 last;
698 }
699 }
701 if( (!$msg) || (!$msg_hash) || (!$module)) {
702 daemon_log("$session_id DEBUG: Incoming message is not from an unknown host", 7);
703 }
705 return ($msg, $msg_hash, $module);
706 }
709 sub create_ciphering {
710 my ($passwd) = @_;
711 if((!defined($passwd)) || length($passwd)==0) {
712 $passwd = "";
713 }
714 $passwd = substr(md5_hex("$passwd") x 32, 0, 32);
715 my $iv = substr(md5_hex('GONICUS GmbH'),0, 16);
716 my $my_cipher = Crypt::Rijndael->new($passwd , Crypt::Rijndael::MODE_CBC());
717 $my_cipher->set_iv($iv);
718 return $my_cipher;
719 }
722 sub encrypt_msg {
723 my ($msg, $key) = @_;
724 my $my_cipher = &create_ciphering($key);
725 my $len;
726 {
727 use bytes;
728 $len= 16-length($msg)%16;
729 }
730 $msg = "\0"x($len).$msg;
731 $msg = $my_cipher->encrypt($msg);
732 chomp($msg = &encode_base64($msg));
733 # there are no newlines allowed inside msg
734 $msg=~ s/\n//g;
735 return $msg;
736 }
739 sub decrypt_msg {
741 my ($msg, $key) = @_ ;
742 $msg = &decode_base64($msg);
743 my $my_cipher = &create_ciphering($key);
744 $msg = $my_cipher->decrypt($msg);
745 $msg =~ s/\0*//g;
746 return $msg;
747 }
750 sub get_encrypt_key {
751 my ($target) = @_ ;
752 my $encrypt_key;
753 my $error = 0;
755 # target can be in known_server
756 if( not defined $encrypt_key ) {
757 my $sql_statement= "SELECT * FROM known_server WHERE hostname='$target'";
758 my $query_res = $known_server_db->select_dbentry( $sql_statement );
759 while( my ($hit_num, $hit) = each %{ $query_res } ) {
760 my $host_name = $hit->{hostname};
761 if( $host_name ne $target ) {
762 next;
763 }
764 $encrypt_key = $hit->{hostkey};
765 last;
766 }
767 }
769 # target can be in known_client
770 if( not defined $encrypt_key ) {
771 my $sql_statement= "SELECT * FROM known_clients WHERE hostname='$target'";
772 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
773 while( my ($hit_num, $hit) = each %{ $query_res } ) {
774 my $host_name = $hit->{hostname};
775 if( $host_name ne $target ) {
776 next;
777 }
778 $encrypt_key = $hit->{hostkey};
779 last;
780 }
781 }
783 return $encrypt_key;
784 }
787 #=== FUNCTION ================================================================
788 # NAME: open_socket
789 # PARAMETERS: PeerAddr string something like 192.168.1.1 or 192.168.1.1:10000
790 # [PeerPort] string necessary if port not appended by PeerAddr
791 # RETURNS: socket IO::Socket::INET
792 # DESCRIPTION: open a socket to PeerAddr
793 #===============================================================================
794 sub open_socket {
795 my ($PeerAddr, $PeerPort) = @_ ;
796 if(defined($PeerPort)){
797 $PeerAddr = $PeerAddr.":".$PeerPort;
798 }
799 my $socket;
800 $socket = new IO::Socket::INET(PeerAddr => $PeerAddr,
801 Porto => "tcp",
802 Type => SOCK_STREAM,
803 Timeout => 5,
804 );
805 if(not defined $socket) {
806 return;
807 }
808 # &daemon_log("DEBUG: open_socket: $PeerAddr", 7);
809 return $socket;
810 }
813 # moved to GosaSupportDaemon: 03-06-2008: rettenbe
814 #=== FUNCTION ================================================================
815 # NAME: get_ip
816 # PARAMETERS: interface name (i.e. eth0)
817 # RETURNS: (ip address)
818 # DESCRIPTION: Uses ioctl to get ip address directly from system.
819 #===============================================================================
820 #sub get_ip {
821 # my $ifreq= shift;
822 # my $result= "";
823 # my $SIOCGIFADDR= 0x8915; # man 2 ioctl_list
824 # my $proto= getprotobyname('ip');
825 #
826 # socket SOCKET, PF_INET, SOCK_DGRAM, $proto
827 # or die "socket: $!";
828 #
829 # if(ioctl SOCKET, $SIOCGIFADDR, $ifreq) {
830 # my ($if, $sin) = unpack 'a16 a16', $ifreq;
831 # my ($port, $addr) = sockaddr_in $sin;
832 # my $ip = inet_ntoa $addr;
833 #
834 # if ($ip && length($ip) > 0) {
835 # $result = $ip;
836 # }
837 # }
838 #
839 # return $result;
840 #}
843 sub get_local_ip_for_remote_ip {
844 my $remote_ip= shift;
845 my $result="0.0.0.0";
847 if($remote_ip =~ /^(\d\d?\d?\.){3}\d\d?\d?$/) {
848 if($remote_ip eq "127.0.0.1") {
849 $result = "127.0.0.1";
850 } else {
851 my $PROC_NET_ROUTE= ('/proc/net/route');
853 open(PROC_NET_ROUTE, "<$PROC_NET_ROUTE")
854 or die "Could not open $PROC_NET_ROUTE";
856 my @ifs = <PROC_NET_ROUTE>;
858 close(PROC_NET_ROUTE);
860 # Eat header line
861 shift @ifs;
862 chomp @ifs;
863 foreach my $line(@ifs) {
864 my ($Iface,$Destination,$Gateway,$Flags,$RefCnt,$Use,$Metric,$Mask,$MTU,$Window,$IRTT)=split(/\s/, $line);
865 my $destination;
866 my $mask;
867 my ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Destination);
868 $destination= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
869 ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Mask);
870 $mask= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
871 if(new NetAddr::IP($remote_ip)->within(new NetAddr::IP($destination, $mask))) {
872 # destination matches route, save mac and exit
873 $result= &get_ip($Iface);
874 last;
875 }
876 }
877 }
878 } else {
879 daemon_log("get_local_ip_for_remote_ip was called with a non-ip parameter: $remote_ip", 1);
880 }
881 return $result;
882 }
885 sub send_msg_to_target {
886 my ($msg, $address, $encrypt_key, $msg_header, $session_id) = @_ ;
887 my $error = 0;
888 my $header;
889 my $timestamp = &get_time();
890 my $new_status;
891 my $act_status;
892 my ($sql_statement, $res);
894 if( $msg_header ) {
895 $header = "'$msg_header'-";
896 } else {
897 $header = "";
898 }
900 # Patch the source ip
901 if($msg =~ /<source>0\.0\.0\.0:\d*?<\/source>/) {
902 my $remote_ip = &get_local_ip_for_remote_ip(sprintf("%s", $address =~ /^([0-9\.]*?):.*$/));
903 $msg =~ s/<source>(0\.0\.0\.0):(\d*?)<\/source>/<source>$remote_ip:$2<\/source>/s;
904 }
906 # encrypt xml msg
907 my $crypted_msg = &encrypt_msg($msg, $encrypt_key);
909 # opensocket
910 my $socket = &open_socket($address);
911 if( !$socket ) {
912 daemon_log("$session_id ERROR: cannot send ".$header."msg to $address , host not reachable", 1);
913 $error++;
914 }
916 if( $error == 0 ) {
917 # send xml msg
918 print $socket $crypted_msg."\n";
920 daemon_log("$session_id INFO: send ".$header."msg to $address", 5);
921 daemon_log("$session_id DEBUG: message:\n$msg", 9);
923 }
925 # close socket in any case
926 if( $socket ) {
927 close $socket;
928 }
930 if( $error > 0 ) { $new_status = "down"; }
931 else { $new_status = $msg_header; }
934 # known_clients
935 $sql_statement = "SELECT * FROM $known_clients_tn WHERE hostname='$address'";
936 $res = $known_clients_db->select_dbentry($sql_statement);
937 if( keys(%$res) == 1) {
938 $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
939 if ($act_status eq "down" && $new_status eq "down") {
940 $sql_statement = "DELETE FROM known_clients WHERE hostname='$address'";
941 $res = $known_clients_db->del_dbentry($sql_statement);
942 daemon_log("$session_id WARNING: failed 2x to send msg to host '$address', delete host from known_clients", 3);
943 } else {
944 $sql_statement = "UPDATE known_clients SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
945 $res = $known_clients_db->update_dbentry($sql_statement);
946 if($new_status eq "down"){
947 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
948 } else {
949 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
950 }
951 }
952 }
954 # known_server
955 $sql_statement = "SELECT * FROM $known_server_tn WHERE hostname='$address'";
956 $res = $known_server_db->select_dbentry($sql_statement);
957 if( keys(%$res) == 1) {
958 $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
959 if ($act_status eq "down" && $new_status eq "down") {
960 $sql_statement = "DELETE FROM known_server WHERE hostname='$address'";
961 $res = $known_server_db->del_dbentry($sql_statement);
962 daemon_log("$session_id WARNING: failed 2x to send a message to host '$address', delete host from known_server", 3);
963 }
964 else {
965 $sql_statement = "UPDATE known_server SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
966 $res = $known_server_db->update_dbentry($sql_statement);
967 if($new_status eq "down"){
968 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
969 } else {
970 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
971 }
972 }
973 }
974 return $error;
975 }
978 sub update_jobdb_status_for_send_msgs {
979 my ($answer, $error) = @_;
980 if( $answer =~ /<jobdb_id>(\d+)<\/jobdb_id>/ ) {
981 my $jobdb_id = $1;
983 # sending msg faild
984 if( $error ) {
985 if (not $answer =~ /<header>trigger_action_reinstall<\/header>/) {
986 my $sql_statement = "UPDATE $job_queue_tn ".
987 "SET status='error', result='can not deliver msg, please consult log file' ".
988 "WHERE id=$jobdb_id";
989 my $res = $job_db->update_dbentry($sql_statement);
990 }
992 # sending msg was successful
993 } else {
994 my $sql_statement = "UPDATE $job_queue_tn ".
995 "SET status='done' ".
996 "WHERE id=$jobdb_id AND status='processed'";
997 my $res = $job_db->update_dbentry($sql_statement);
998 }
999 }
1000 }
1003 sub sig_handler {
1004 my ($kernel, $signal) = @_[KERNEL, ARG0] ;
1005 daemon_log("0 INFO got signal '$signal'", 1);
1006 $kernel->sig_handled();
1007 return;
1008 }
1011 sub msg_to_decrypt {
1012 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
1013 my $session_id = $session->ID;
1014 my ($msg, $msg_hash, $module);
1015 my $error = 0;
1017 # hole neue msg aus @msgs_to_decrypt
1018 my $next_msg = shift @msgs_to_decrypt;
1020 # entschlüssle sie
1022 # msg is from a new client or gosa
1023 ($msg, $msg_hash, $module) = &input_from_unknown_host($next_msg, $session_id);
1024 # msg is from a gosa-si-server
1025 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1026 ($msg, $msg_hash, $module) = &input_from_known_server($next_msg, $heap->{'remote_ip'}, $session_id);
1027 }
1028 # msg is from a gosa-si-client
1029 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1030 ($msg, $msg_hash, $module) = &input_from_known_client($next_msg, $heap->{'remote_ip'}, $session_id);
1031 }
1032 # an error occurred
1033 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1034 # if an incoming msg could not be decrypted (maybe a wrong key), send client a ping. If the client
1035 # could not understand a msg from its server the client cause a re-registering process
1036 daemon_log("$session_id INFO cannot understand incoming msg, send 'ping'-msg to all host with ip '".$heap->{remote_ip}.
1037 "' to cause a re-registering of the client if necessary", 5);
1038 my $sql_statement = "SELECT * FROM $main::known_clients_tn WHERE (hostname LIKE '".$heap->{'remote_ip'}."%')";
1039 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1040 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1041 my $host_name = $hit->{'hostname'};
1042 my $host_key = $hit->{'hostkey'};
1043 my $ping_msg = "<xml> <header>gosa_ping</header> <source>$server_address</source> <target>$host_name</target></xml>";
1044 my $error = &send_msg_to_target($ping_msg, $host_name, $host_key, "gosa_ping", $session_id);
1045 &update_jobdb_status_for_send_msgs($ping_msg, $error);
1046 }
1047 $error++;
1048 }
1051 my $header;
1052 my $target;
1053 my $source;
1054 my $done = 0;
1055 my $sql;
1056 my $res;
1058 # check whether this message should be processed here
1059 if ($error == 0) {
1060 $header = @{$msg_hash->{'header'}}[0];
1061 $target = @{$msg_hash->{'target'}}[0];
1062 $source = @{$msg_hash->{'source'}}[0];
1063 my $not_found_in_known_clients_db = 0;
1064 my $not_found_in_known_server_db = 0;
1065 my $not_found_in_foreign_clients_db = 0;
1066 my $local_address;
1067 my ($target_ip, $target_port) = split(':', $target);
1068 if ($target =~ /^\d+\.\d+\.\d+\.\d+:\d+$/) {
1069 $local_address = &get_local_ip_for_remote_ip($target_ip).":$server_port";
1070 } else {
1071 $local_address = $server_address;
1072 }
1074 # target and source is equal to GOSA -> process here
1075 if (not $done) {
1076 if ($target eq "GOSA" && $source eq "GOSA") {
1077 $done = 1;
1078 }
1079 }
1081 # target is own address without forward_to_gosa-tag -> process here
1082 if (not $done) {
1083 if (($target eq $local_address) && (not exists $msg_hash->{'forward_to_gosa'})) {
1084 $done = 1;
1085 if ($source eq "GOSA") {
1086 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1087 }
1088 print STDERR "target is own address without forward_to_gosa-tag -> process here\n";
1089 }
1090 }
1092 # target is a client address in known_clients -> process here
1093 if (not $done) {
1094 $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1095 $res = $known_clients_db->select_dbentry($sql);
1096 if (keys(%$res) > 0) {
1097 $done = 1;
1098 my $hostname = $res->{1}->{'hostname'};
1099 $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1100 print STDERR "target is a client address in known_clients -> process here\n";
1101 } else {
1102 $not_found_in_known_clients_db = 1;
1103 }
1104 }
1106 # target ist own address with forward_to_gosa-tag not pointing to myself -> process here
1107 if (not $done) {
1108 my $forward_to_gosa = @{$msg_hash->{'forward_to_gosa'}}[0];
1109 my $gosa_at;
1110 my $gosa_session_id;
1111 if (($target eq $local_address) && (defined $forward_to_gosa)){
1112 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1113 if ($gosa_at ne $local_address) {
1114 $done = 1;
1115 print STDERR "target is own address with forward_to_gosa-tag not pointing to myself -> process here\n";
1116 }
1117 }
1118 }
1120 # if message should be processed here -> add message to incoming_db
1121 if ($done) {
1122 # if a job or a gosa message comes from a foreign server, fake module to GosaPackages
1123 # so gosa-si-server knows how to process this kind of messages
1124 if ($header =~ /^gosa_/ || $header =~ /job_/) {
1125 $module = "GosaPackages";
1126 }
1128 my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1129 primkey=>[],
1130 headertag=>$header,
1131 targettag=>$target,
1132 xmlmessage=>&encode_base64($msg),
1133 timestamp=>&get_time,
1134 module=>$module,
1135 sessionid=>$session_id,
1136 } );
1137 }
1139 # target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa
1140 if (not $done) {
1141 my $forward_to_gosa = @{$msg_hash->{'forward_to_gosa'}}[0];
1142 my $gosa_at;
1143 my $gosa_session_id;
1144 if (($target eq $local_address) && (defined $forward_to_gosa)){
1145 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1146 if ($gosa_at eq $local_address) {
1147 my $session_reference = $kernel->ID_id_to_session($gosa_session_id);
1148 if( defined $session_reference ) {
1149 $heap = $session_reference->get_heap();
1150 }
1151 if(exists $heap->{'client'}) {
1152 $msg = &encrypt_msg($msg, $GosaPackages_key);
1153 $heap->{'client'}->put($msg);
1154 }
1155 $done = 1;
1156 print STDERR "target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa\n";
1157 }
1158 }
1160 }
1162 # target is a client address in foreign_clients -> forward to registration server
1163 if (not $done) {
1164 $sql = "SELECT * FROM $foreign_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1165 $res = $foreign_clients_db->select_dbentry($sql);
1166 if (keys(%$res) > 0) {
1167 my $hostname = $res->{1}->{'hostname'};
1168 my $regserver = $res->{1}->{'regserver'};
1169 my $sql = "SELECT * FROM $known_server_tn WHERE hostname='$regserver'";
1170 my $res = $known_server_db->select_dbentry($sql);
1171 if (keys(%$res) > 0) {
1172 my $regserver_key = $res->{1}->{'hostkey'};
1173 $msg =~ s/<source>GOSA<\/source>/<source>$local_address<\/source>/;
1174 $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1175 if ($source eq "GOSA") {
1176 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1177 }
1178 &send_msg_to_target($msg, $regserver, $regserver_key, $header, $session_id);
1179 }
1180 $done = 1;
1181 print STDERR "target is a client address in foreign_clients -> forward to registration server\n";
1182 } else {
1183 $not_found_in_foreign_clients_db = 1;
1184 }
1185 }
1187 # target is a server address -> forward to server
1188 if (not $done) {
1189 $sql = "SELECT * FROM $known_server_tn WHERE hostname='$target'";
1190 $res = $known_server_db->select_dbentry($sql);
1191 if (keys(%$res) > 0) {
1192 my $hostkey = $res->{1}->{'hostkey'};
1194 if ($source eq "GOSA") {
1195 $msg =~ s/<source>GOSA<\/source>/<source>$local_address<\/source>/;
1196 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1198 }
1200 &send_msg_to_target($msg, $target, $hostkey, $header, $session_id);
1201 $done = 1;
1202 print STDERR "target is a server address -> forward to server\n";
1203 } else {
1204 $not_found_in_known_server_db = 1;
1205 }
1206 }
1209 # target is not in foreign_clients_db, known_server_db or known_clients_db, maybe it is a complete new one -> process here
1210 if ( $not_found_in_foreign_clients_db
1211 && $not_found_in_known_server_db
1212 && $not_found_in_known_clients_db) {
1213 my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1214 primkey=>[],
1215 headertag=>$header,
1216 targettag=>$target,
1217 xmlmessage=>&encode_base64($msg),
1218 timestamp=>&get_time,
1219 module=>$module,
1220 sessionid=>$session_id,
1221 } );
1222 $done = 1;
1223 }
1226 if (not $done) {
1227 daemon_log("$session_id ERROR: do not know what to do with this message: $msg", 1);
1228 if ($source eq "GOSA") {
1229 my %data = ('error_msg' => &encode_base64($msg), 'error_string' => "Do not know what to do with this message!");
1230 my $error_msg = &build_msg("error", $local_address, "GOSA", \%data );
1232 my $session_reference = $kernel->ID_id_to_session($session_id);
1233 if( defined $session_reference ) {
1234 $heap = $session_reference->get_heap();
1235 }
1236 if(exists $heap->{'client'}) {
1237 $error_msg = &encrypt_msg($error_msg, $GosaPackages_key);
1238 $heap->{'client'}->put($error_msg);
1239 }
1240 }
1241 }
1243 }
1245 return;
1246 }
1249 sub next_task {
1250 my ($session, $heap, $task) = @_[SESSION, HEAP, ARG0];
1251 my $running_task = POE::Wheel::Run->new(
1252 Program => sub { process_task($session, $heap, $task) },
1253 StdioFilter => POE::Filter::Reference->new(),
1254 StdoutEvent => "task_result",
1255 StderrEvent => "task_debug",
1256 CloseEvent => "task_done",
1257 );
1258 $heap->{task}->{ $running_task->ID } = $running_task;
1259 }
1261 sub handle_task_result {
1262 my ($kernel, $heap, $result) = @_[KERNEL, HEAP, ARG0];
1263 my $client_answer = $result->{'answer'};
1264 if( $client_answer =~ s/session_id=(\d+)$// ) {
1265 my $session_id = $1;
1266 if( defined $session_id ) {
1267 my $session_reference = $kernel->ID_id_to_session($session_id);
1268 if( defined $session_reference ) {
1269 $heap = $session_reference->get_heap();
1270 }
1271 }
1273 if(exists $heap->{'client'}) {
1274 $heap->{'client'}->put($client_answer);
1275 }
1276 }
1277 $kernel->sig(CHLD => "child_reap");
1278 }
1280 sub handle_task_debug {
1281 my $result = $_[ARG0];
1282 print STDERR "$result\n";
1283 }
1285 sub handle_task_done {
1286 my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1287 delete $heap->{task}->{$task_id};
1288 }
1290 sub process_task {
1291 no strict "refs";
1292 my ($session, $heap, $task) = @_;
1293 my $error = 0;
1294 my $answer_l;
1295 my ($answer_header, @answer_target_l, $answer_source);
1296 my $client_answer = "";
1298 # prepare all variables needed to process message
1299 #my $msg = $task->{'xmlmessage'};
1300 my $msg = &decode_base64($task->{'xmlmessage'});
1301 my $incoming_id = $task->{'id'};
1302 my $module = $task->{'module'};
1303 my $header = $task->{'headertag'};
1304 my $session_id = $task->{'sessionid'};
1305 my $msg_hash = $xml->XMLin($msg, ForceArray=>1);
1306 my $source = @{$msg_hash->{'source'}}[0];
1308 # set timestamp of incoming client uptodate, so client will not
1309 # be deleted from known_clients because of expiration
1310 my $act_time = &get_time();
1311 my $sql = "UPDATE $known_clients_tn SET timestamp='$act_time' WHERE hostname='$source'";
1312 my $res = $known_clients_db->exec_statement($sql);
1314 ######################
1315 # process incoming msg
1316 if( $error == 0) {
1317 daemon_log("$session_id INFO: Incoming msg (session_id=$session_id) with header '".@{$msg_hash->{'header'}}[0]."'", 5);
1318 daemon_log("$session_id DEBUG: Processing module ".$module, 7);
1319 $answer_l = &{ $module."::process_incoming_msg" }($msg, $msg_hash, $session_id);
1321 if ( 0 < @{$answer_l} ) {
1322 my $answer_str = join("\n", @{$answer_l});
1323 while ($answer_str =~ /<header>(\w+)<\/header>/g) {
1324 daemon_log("$session_id INFO: got answer message with header '$1'", 5);
1325 }
1326 daemon_log("$session_id DEBUG: $module: got answer from module: \n".$answer_str,8);
1327 } else {
1328 daemon_log("$session_id DEBUG: $module: got no answer from module!" ,8);
1329 }
1331 }
1332 if( !$answer_l ) { $error++ };
1334 ########
1335 # answer
1336 if( $error == 0 ) {
1338 foreach my $answer ( @{$answer_l} ) {
1339 # check outgoing msg to xml validity
1340 my $answer_hash = &check_outgoing_xml_validity($answer);
1341 if( not defined $answer_hash ) { next; }
1343 $answer_header = @{$answer_hash->{'header'}}[0];
1344 @answer_target_l = @{$answer_hash->{'target'}};
1345 $answer_source = @{$answer_hash->{'source'}}[0];
1347 # deliver msg to all targets
1348 foreach my $answer_target ( @answer_target_l ) {
1350 # targets of msg are all gosa-si-clients in known_clients_db
1351 if( $answer_target eq "*" ) {
1352 # answer is for all clients
1353 my $sql_statement= "SELECT * FROM known_clients";
1354 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1355 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1356 my $host_name = $hit->{hostname};
1357 my $host_key = $hit->{hostkey};
1358 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1359 &update_jobdb_status_for_send_msgs($answer, $error);
1360 }
1361 }
1363 # targets of msg are all gosa-si-server in known_server_db
1364 elsif( $answer_target eq "KNOWN_SERVER" ) {
1365 # answer is for all server in known_server
1366 my $sql_statement= "SELECT * FROM $known_server_tn";
1367 my $query_res = $known_server_db->select_dbentry( $sql_statement );
1368 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1369 my $host_name = $hit->{hostname};
1370 my $host_key = $hit->{hostkey};
1371 $answer =~ s/<target>\S+<\/target>/<target>$host_name<\/target>/g;
1372 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1373 &update_jobdb_status_for_send_msgs($answer, $error);
1374 }
1375 }
1377 # target of msg is GOsa
1378 elsif( $answer_target eq "GOSA" ) {
1379 my $session_id = ($1) if $answer =~ /<session_id>(\d+?)<\/session_id>/;
1380 my $add_on = "";
1381 if( defined $session_id ) {
1382 $add_on = ".session_id=$session_id";
1383 }
1384 # answer is for GOSA and has to returned to connected client
1385 my $gosa_answer = &encrypt_msg($answer, $GosaPackages_key);
1386 $client_answer = $gosa_answer.$add_on;
1387 }
1389 # target of msg is job queue at this host
1390 elsif( $answer_target eq "JOBDB") {
1391 $answer =~ /<header>(\S+)<\/header>/;
1392 my $header;
1393 if( defined $1 ) { $header = $1; }
1394 my $error = &send_msg_to_target($answer, $server_address, $GosaPackages_key, $header, $session_id);
1395 &update_jobdb_status_for_send_msgs($answer, $error);
1396 }
1398 # target of msg is a mac address
1399 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 ) {
1400 daemon_log("$session_id INFO: target is mac address '$answer_target', looking for host in known_clients", 5);
1401 my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$answer_target'";
1402 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1403 my $found_ip_flag = 0;
1404 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1405 my $host_name = $hit->{hostname};
1406 my $host_key = $hit->{hostkey};
1407 $answer =~ s/$answer_target/$host_name/g;
1408 daemon_log("$session_id INFO: found host '$host_name', associated to '$answer_target'", 5);
1409 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1410 &update_jobdb_status_for_send_msgs($answer, $error);
1411 $found_ip_flag++ ;
1412 }
1413 if( $found_ip_flag == 0) {
1414 daemon_log("$session_id WARNING: no host found in known_clients with mac address '$answer_target'", 3);
1415 }
1417 # answer is for one specific host
1418 } else {
1419 # get encrypt_key
1420 my $encrypt_key = &get_encrypt_key($answer_target);
1421 if( not defined $encrypt_key ) {
1422 # unknown target
1423 daemon_log("$session_id WARNING: unknown target '$answer_target'", 3);
1424 next;
1425 }
1426 my $error = &send_msg_to_target($answer, $answer_target, $encrypt_key, $answer_header,$session_id);
1427 &update_jobdb_status_for_send_msgs($answer, $error);
1428 }
1429 }
1430 }
1431 }
1433 my $filter = POE::Filter::Reference->new();
1434 my %result = (
1435 status => "seems ok to me",
1436 answer => $client_answer,
1437 );
1439 my $output = $filter->put( [ \%result ] );
1440 print @$output;
1443 }
1445 sub session_start {
1446 my ($kernel) = $_[KERNEL];
1447 $global_kernel = $kernel;
1448 $kernel->yield('register_at_foreign_servers');
1449 $kernel->yield('create_fai_server_db', $fai_server_tn );
1450 $kernel->yield('create_fai_release_db', $fai_release_tn );
1451 $kernel->yield('watch_for_next_tasks');
1452 $kernel->sig(USR1 => "sig_handler");
1453 $kernel->sig(USR2 => "create_packages_list_db");
1454 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1455 $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay);
1456 $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1457 $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1458 $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1459 $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1461 }
1464 sub watch_for_done_jobs {
1465 my ($kernel,$heap) = @_[KERNEL, HEAP];
1467 my $sql_statement = "SELECT * FROM ".$job_queue_tn." WHERE status='done'";
1468 my $res = $job_db->select_dbentry( $sql_statement );
1470 while( my ($id, $hit) = each %{$res} ) {
1471 my $jobdb_id = $hit->{id};
1472 my $sql_statement = "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1473 my $res = $job_db->del_dbentry($sql_statement);
1474 }
1476 $kernel->delay_set('watch_for_done_jobs',$job_queue_loop_delay);
1477 }
1480 sub watch_for_new_jobs {
1481 if($watch_for_new_jobs_in_progress == 0) {
1482 $watch_for_new_jobs_in_progress = 1;
1483 my ($kernel,$heap) = @_[KERNEL, HEAP];
1485 # check gosa job queue for jobs with executable timestamp
1486 my $timestamp = &get_time();
1487 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE status='waiting' AND (CAST (timestamp AS INTEGER)) < $timestamp ORDER BY timestamp";
1488 my $res = $job_db->exec_statement( $sql_statement );
1490 # Merge all new jobs that would do the same actions
1491 my @drops;
1492 my $hits;
1493 foreach my $hit (reverse @{$res} ) {
1494 my $macaddress= lc @{$hit}[8];
1495 my $headertag= @{$hit}[5];
1496 if(
1497 defined($hits->{$macaddress}) &&
1498 defined($hits->{$macaddress}->{$headertag}) &&
1499 defined($hits->{$macaddress}->{$headertag}[0])
1500 ) {
1501 push @drops, "DELETE FROM $job_queue_tn WHERE id = $hits->{$macaddress}->{$headertag}[0]";
1502 }
1503 $hits->{$macaddress}->{$headertag}= $hit;
1504 }
1506 # Delete new jobs with a matching job in state 'processing'
1507 foreach my $macaddress (keys %{$hits}) {
1508 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1509 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1510 if(defined($jobdb_id)) {
1511 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND headertag='$jobdb_headertag' AND status='processing'";
1512 my $res = $job_db->exec_statement( $sql_statement );
1513 foreach my $hit (@{$res}) {
1514 push @drops, "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1515 }
1516 } else {
1517 daemon_log("J ERROR: Job without id exists for macaddress $macaddress!", 1);
1518 }
1519 }
1520 }
1522 # Commit deletion
1523 $job_db->exec_statementlist(\@drops);
1525 # Look for new jobs that could be executed
1526 foreach my $macaddress (keys %{$hits}) {
1528 # Look if there is an executing job
1529 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='processing'";
1530 my $res = $job_db->exec_statement( $sql_statement );
1532 # Skip new jobs for host if there is a processing job
1533 if(defined($res) and defined @{$res}[0]) {
1534 next;
1535 }
1537 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1538 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1539 if(defined($jobdb_id)) {
1540 my $job_msg = @{$hits->{$macaddress}->{$jobdb_headertag}}[7];
1542 daemon_log("J DEBUG: its time to execute $job_msg", 7);
1543 my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$macaddress'";
1544 my $res_hash = $known_clients_db->select_dbentry( $sql_statement );
1546 # expect macaddress is unique!!!!!!
1547 my $target = $res_hash->{1}->{hostname};
1549 # change header
1550 $job_msg =~ s/<header>job_/<header>gosa_/;
1552 # add sqlite_id
1553 $job_msg =~ s/<\/xml>$/<jobdb_id>$jobdb_id<\/jobdb_id><\/xml>/;
1555 $job_msg =~ /<header>(\S+)<\/header>/;
1556 my $header = $1 ;
1557 my $func_error = &send_msg_to_target($job_msg, $server_address, $GosaPackages_key, $header, "J");
1559 # update status in job queue to 'processing'
1560 $sql_statement = "UPDATE $job_queue_tn SET status='processing' WHERE id=$jobdb_id";
1561 my $res = $job_db->update_dbentry($sql_statement);
1562 # TODO: abfangen ob alles in ordnung ist oder nicht, wenn nicht error schmeißen
1564 # We don't want parallel processing
1565 last;
1566 }
1567 }
1568 }
1570 $watch_for_new_jobs_in_progress = 0;
1571 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1572 }
1573 }
1576 sub watch_for_new_messages {
1577 my ($kernel,$heap) = @_[KERNEL, HEAP];
1578 my @coll_user_msg; # collection list of outgoing messages
1580 # check messaging_db for new incoming messages with executable timestamp
1581 my $timestamp = &get_time();
1582 my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( (CAST(timestamp AS INTEGER))<$timestamp AND flag='n' AND direction='in' )";
1583 my $res = $messaging_db->exec_statement( $sql_statement );
1584 foreach my $hit (@{$res}) {
1586 # create outgoing messages
1587 my $message_to = @{$hit}[3];
1588 # translate message_to to plain login name
1589 my @message_to_l = split(/,/, $message_to);
1590 my %receiver_h;
1591 foreach my $receiver (@message_to_l) {
1592 if ($receiver =~ /^u_([\s\S]*)$/) {
1593 $receiver_h{$1} = 0;
1594 } elsif ($receiver =~ /^g_([\s\S]*)$/) {
1595 my $group_name = $1;
1596 # fetch all group members from ldap and add them to receiver hash
1597 my $ldap_handle = &get_ldap_handle();
1598 if (defined $ldap_handle) {
1599 my $mesg = $ldap_handle->search(
1600 base => $ldap_base,
1601 scope => 'sub',
1602 attrs => ['memberUid'],
1603 filter => "cn=$group_name",
1604 );
1605 if ($mesg->count) {
1606 my @entries = $mesg->entries;
1607 foreach my $entry (@entries) {
1608 my @receivers= $entry->get_value("memberUid");
1609 foreach my $receiver (@receivers) {
1610 $receiver_h{$1} = 0;
1611 }
1612 }
1613 }
1614 # translating errors ?
1615 if ($mesg->code) {
1616 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: $mesg->error", 1);
1617 }
1618 # ldap handle error ?
1619 } else {
1620 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: no ldap handle available", 1);
1621 }
1622 } else {
1623 my $sbjct = &encode_base64(@{$hit}[1]);
1624 my $msg = &encode_base64(@{$hit}[7]);
1625 &daemon_log("M WARNING: unknown receiver '$receiver' for a user-message '$sbjct - $msg'", 3);
1626 }
1627 }
1628 my @receiver_l = keys(%receiver_h);
1630 my $message_id = @{$hit}[0];
1632 #add each outgoing msg to messaging_db
1633 my $receiver;
1634 foreach $receiver (@receiver_l) {
1635 my $sql_statement = "INSERT INTO $messaging_tn (id, subject, message_from, message_to, flag, direction, delivery_time, message, timestamp) ".
1636 "VALUES ('".
1637 $message_id."', '". # id
1638 @{$hit}[1]."', '". # subject
1639 @{$hit}[2]."', '". # message_from
1640 $receiver."', '". # message_to
1641 "none"."', '". # flag
1642 "out"."', '". # direction
1643 @{$hit}[6]."', '". # delivery_time
1644 @{$hit}[7]."', '". # message
1645 $timestamp."'". # timestamp
1646 ")";
1647 &daemon_log("M DEBUG: $sql_statement", 1);
1648 my $res = $messaging_db->exec_statement($sql_statement);
1649 &daemon_log("M INFO: message '".@{$hit}[0]."' is prepared for delivery to receiver '$receiver'", 5);
1650 }
1652 # set incoming message to flag d=deliverd
1653 $sql_statement = "UPDATE $messaging_tn SET flag='p' WHERE id='$message_id'";
1654 &daemon_log("M DEBUG: $sql_statement", 7);
1655 $res = $messaging_db->update_dbentry($sql_statement);
1656 &daemon_log("M INFO: message '$message_id' is set to flag 'p' (processed)", 5);
1657 }
1659 $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1660 return;
1661 }
1663 sub watch_for_delivery_messages {
1664 my ($kernel, $heap) = @_[KERNEL, HEAP];
1666 # select outgoing messages
1667 my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( flag='p' AND direction='out' )";
1668 #&daemon_log("0 DEBUG: $sql", 7);
1669 my $res = $messaging_db->exec_statement( $sql_statement );
1671 # build out msg for each usr
1672 foreach my $hit (@{$res}) {
1673 my $receiver = @{$hit}[3];
1674 my $msg_id = @{$hit}[0];
1675 my $subject = @{$hit}[1];
1676 my $message = @{$hit}[7];
1678 # resolve usr -> host where usr is logged in
1679 my $sql = "SELECT * FROM $login_users_tn WHERE (user='$receiver')";
1680 #&daemon_log("0 DEBUG: $sql", 7);
1681 my $res = $login_users_db->exec_statement($sql);
1683 # reciver is logged in nowhere
1684 if (not ref(@$res[0]) eq "ARRAY") { next; }
1686 my $send_succeed = 0;
1687 foreach my $hit (@$res) {
1688 my $receiver_host = @$hit[0];
1689 &daemon_log("M DEBUG: user '$receiver' is logged in at host '$receiver_host'", 7);
1691 # fetch key to encrypt msg propperly for usr/host
1692 my $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$receiver_host')";
1693 &daemon_log("0 DEBUG: $sql", 7);
1694 my $res = $known_clients_db->exec_statement($sql);
1696 # host is already down
1697 if (not ref(@$res[0]) eq "ARRAY") { next; }
1699 # host is on
1700 my $receiver_key = @{@{$res}[0]}[2];
1701 my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
1702 my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data );
1703 my $error = &send_msg_to_target($out_msg, $receiver_host, $receiver_key, "usr_msg", 0);
1704 if ($error == 0 ) {
1705 $send_succeed++ ;
1706 }
1707 }
1709 if ($send_succeed) {
1710 # set outgoing msg at db to deliverd
1711 my $sql = "UPDATE $messaging_tn SET flag='d' WHERE (id='$msg_id' AND direction='out' AND message_to='$receiver')";
1712 &daemon_log("0 DEBUG: $sql", 7);
1713 my $res = $messaging_db->exec_statement($sql);
1714 }
1715 }
1717 $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1718 return;
1719 }
1722 sub watch_for_done_messages {
1723 my ($kernel,$heap) = @_[KERNEL, HEAP];
1725 my $sql = "SELECT * FROM $messaging_tn WHERE (flag='p' AND direction='in')";
1726 #&daemon_log("0 DEBUG: $sql", 7);
1727 my $res = $messaging_db->exec_statement($sql);
1729 foreach my $hit (@{$res}) {
1730 my $msg_id = @{$hit}[0];
1732 my $sql = "SELECT * FROM $messaging_tn WHERE (id='$msg_id' AND direction='out' AND (NOT flag='s'))";
1733 #&daemon_log("0 DEBUG: $sql", 7);
1734 my $res = $messaging_db->exec_statement($sql);
1736 # not all usr msgs have been seen till now
1737 if ( ref(@$res[0]) eq "ARRAY") { next; }
1739 $sql = "DELETE FROM $messaging_tn WHERE (id='$msg_id')";
1740 #&daemon_log("0 DEBUG: $sql", 7);
1741 $res = $messaging_db->exec_statement($sql);
1743 }
1745 $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1746 return;
1747 }
1750 sub watch_for_old_known_clients {
1751 my ($kernel,$heap) = @_[KERNEL, HEAP];
1753 my $sql_statement = "SELECT * FROM $known_clients_tn";
1754 my $res = $known_clients_db->select_dbentry( $sql_statement );
1756 my $act_time = int(&get_time());
1758 while ( my ($hit_num, $hit) = each %$res) {
1759 my $expired_timestamp = int($hit->{'timestamp'});
1760 $expired_timestamp =~ /(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)/;
1761 my $dt = DateTime->new( year => $1,
1762 month => $2,
1763 day => $3,
1764 hour => $4,
1765 minute => $5,
1766 second => $6,
1767 );
1769 $dt->add( seconds => 2 * int($hit->{'keylifetime'}) );
1770 $expired_timestamp = $dt->ymd('').$dt->hms('')."\n";
1771 if ($act_time > $expired_timestamp) {
1772 my $hostname = $hit->{'hostname'};
1773 my $del_sql = "DELETE FROM $known_clients_tn WHERE hostname='$hostname'";
1774 my $del_res = $known_clients_db->exec_statement($del_sql);
1776 &main::daemon_log("0 INFO: timestamp '".$hit->{'timestamp'}."' of client '$hostname' is expired('$expired_timestamp'), client will be deleted from known_clients_db", 5);
1777 }
1779 }
1781 $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1782 }
1785 sub watch_for_next_tasks {
1786 my ($kernel,$heap) = @_[KERNEL, HEAP];
1788 my $sql = "SELECT * FROM $incoming_tn";
1789 my $res = $incoming_db->select_dbentry($sql);
1791 while ( my ($hit_num, $hit) = each %$res) {
1792 my $headertag = $hit->{'headertag'};
1793 if ($headertag =~ /^answer_(\d+)/) {
1794 # do not start processing, this message is for a still running POE::Wheel
1795 next;
1796 }
1797 my $message_id = $hit->{'id'};
1798 $kernel->yield('next_task', $hit);
1800 my $sql = "DELETE FROM $incoming_tn WHERE id=$message_id";
1801 my $res = $incoming_db->exec_statement($sql);
1802 }
1804 $kernel->delay_set('watch_for_next_tasks', 0.1);
1805 }
1808 sub get_ldap_handle {
1809 my ($session_id) = @_;
1810 my $heap;
1811 my $ldap_handle;
1813 if (not defined $session_id ) { $session_id = 0 };
1814 if ($session_id =~ /[^0-9]*/) { $session_id = 0 };
1816 if ($session_id == 0) {
1817 daemon_log("$session_id DEBUG: get_ldap_handle invoked without a session_id, create a new ldap_handle", 7);
1818 $ldap_handle = Net::LDAP->new( $ldap_uri );
1819 $ldap_handle->bind($ldap_admin_dn, password => $ldap_admin_password);
1821 } else {
1822 my $session_reference = $global_kernel->ID_id_to_session($session_id);
1823 if( defined $session_reference ) {
1824 $heap = $session_reference->get_heap();
1825 }
1827 if (not defined $heap) {
1828 daemon_log("$session_id DEBUG: cannot get heap for session_id '$session_id'", 7);
1829 return;
1830 }
1832 # TODO: This "if" is nonsense, because it doesn't prove that the
1833 # used handle is still valid - or if we've to reconnect...
1834 #if (not exists $heap->{ldap_handle}) {
1835 $ldap_handle = Net::LDAP->new( $ldap_uri );
1836 $ldap_handle->bind($ldap_admin_dn, password => $ldap_admin_password);
1837 $heap->{ldap_handle} = $ldap_handle;
1838 #}
1839 }
1840 return $ldap_handle;
1841 }
1844 sub change_fai_state {
1845 my ($st, $targets, $session_id) = @_;
1846 $session_id = 0 if not defined $session_id;
1847 # Set FAI state to localboot
1848 my %mapActions= (
1849 reboot => '',
1850 update => 'softupdate',
1851 localboot => 'localboot',
1852 reinstall => 'install',
1853 rescan => '',
1854 wake => '',
1855 memcheck => 'memcheck',
1856 sysinfo => 'sysinfo',
1857 install => 'install',
1858 );
1860 # Return if this is unknown
1861 if (!exists $mapActions{ $st }){
1862 daemon_log("$session_id ERROR: unknown action '$st', can not translate ot FAIstate", 1);
1863 return;
1864 }
1866 my $state= $mapActions{ $st };
1868 my $ldap_handle = &get_ldap_handle($session_id);
1869 if( defined($ldap_handle) ) {
1871 # Build search filter for hosts
1872 my $search= "(&(objectClass=GOhard)";
1873 foreach (@{$targets}){
1874 $search.= "(macAddress=$_)";
1875 }
1876 $search.= ")";
1878 # If there's any host inside of the search string, procress them
1879 if (!($search =~ /macAddress/)){
1880 daemon_log("$session_id ERROR: no macAddress found in filter statement for LDAP search: '$search'", 1);
1881 return;
1882 }
1884 # Perform search for Unit Tag
1885 my $mesg = $ldap_handle->search(
1886 base => $ldap_base,
1887 scope => 'sub',
1888 attrs => ['dn', 'FAIstate', 'objectClass'],
1889 filter => "$search"
1890 );
1892 if ($mesg->count) {
1893 my @entries = $mesg->entries;
1894 if (0 == @entries) {
1895 daemon_log("$session_id ERROR: ldap search failed: ldap_base=$ldap_base, filter=$search", 1);
1896 }
1898 foreach my $entry (@entries) {
1899 # Only modify entry if it is not set to '$state'
1900 if ($entry->get_value("FAIstate") ne "$state"){
1901 daemon_log("$session_id INFO: Setting FAIstate to '$state' for ".$entry->dn, 5);
1902 my $result;
1903 my %tmp = map { $_ => 1 } $entry->get_value("objectClass");
1904 if (exists $tmp{'FAIobject'}){
1905 if ($state eq ''){
1906 $result= $ldap_handle->modify($entry->dn, changes => [
1907 delete => [ FAIstate => [] ] ]);
1908 } else {
1909 $result= $ldap_handle->modify($entry->dn, changes => [
1910 replace => [ FAIstate => $state ] ]);
1911 }
1912 } elsif ($state ne ''){
1913 $result= $ldap_handle->modify($entry->dn, changes => [
1914 add => [ objectClass => 'FAIobject' ],
1915 add => [ FAIstate => $state ] ]);
1916 }
1918 # Errors?
1919 if ($result->code){
1920 daemon_log("$session_id Error: Setting FAIstate to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1921 }
1922 } else {
1923 daemon_log("$session_id DEBUG FAIstate at host '".$entry->dn."' already at state '$st'", 7);
1924 }
1925 }
1926 } else {
1927 daemon_log("$session_id ERROR: LDAP search failed: ldap_base=$ldap_base, filter=$search", 1);
1928 }
1930 # if no ldap handle defined
1931 } else {
1932 daemon_log("$session_id ERROR: no LDAP handle defined for update FAIstate", 1);
1933 }
1935 return;
1936 }
1939 sub change_goto_state {
1940 my ($st, $targets, $session_id) = @_;
1941 $session_id = 0 if not defined $session_id;
1943 # Switch on or off?
1944 my $state= $st eq 'active' ? 'active': 'locked';
1946 my $ldap_handle = &get_ldap_handle($session_id);
1947 if( defined($ldap_handle) ) {
1949 # Build search filter for hosts
1950 my $search= "(&(objectClass=GOhard)";
1951 foreach (@{$targets}){
1952 $search.= "(macAddress=$_)";
1953 }
1954 $search.= ")";
1956 # If there's any host inside of the search string, procress them
1957 if (!($search =~ /macAddress/)){
1958 return;
1959 }
1961 # Perform search for Unit Tag
1962 my $mesg = $ldap_handle->search(
1963 base => $ldap_base,
1964 scope => 'sub',
1965 attrs => ['dn', 'gotoMode'],
1966 filter => "$search"
1967 );
1969 if ($mesg->count) {
1970 my @entries = $mesg->entries;
1971 foreach my $entry (@entries) {
1973 # Only modify entry if it is not set to '$state'
1974 if ($entry->get_value("gotoMode") ne $state){
1976 daemon_log("$session_id INFO: Setting gotoMode to '$state' for ".$entry->dn, 5);
1977 my $result;
1978 $result= $ldap_handle->modify($entry->dn, changes => [
1979 replace => [ gotoMode => $state ] ]);
1981 # Errors?
1982 if ($result->code){
1983 &daemon_log("$session_id Error: Setting gotoMode to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1984 }
1986 }
1987 }
1988 } else {
1989 daemon_log("$session_id ERROR: LDAP search failed in function change_goto_state: ldap_base=$ldap_base, filter=$search", 1);
1990 }
1992 }
1993 }
1996 sub run_create_fai_server_db {
1997 my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
1998 my $session_id = $session->ID;
1999 my $task = POE::Wheel::Run->new(
2000 Program => sub { &create_fai_server_db($table_name,$kernel, undef, $session_id) },
2001 StdoutEvent => "session_run_result",
2002 StderrEvent => "session_run_debug",
2003 CloseEvent => "session_run_done",
2004 );
2006 $heap->{task}->{ $task->ID } = $task;
2007 return;
2008 }
2011 sub create_fai_server_db {
2012 my ($table_name, $kernel, $dont_create_packages_list, $session_id) = @_;
2013 my $result;
2015 if (not defined $session_id) { $session_id = 0; }
2016 my $ldap_handle = &get_ldap_handle();
2017 if(defined($ldap_handle)) {
2018 daemon_log("$session_id INFO: create_fai_server_db: start", 5);
2019 my $mesg= $ldap_handle->search(
2020 base => $ldap_base,
2021 scope => 'sub',
2022 attrs => ['FAIrepository', 'gosaUnitTag'],
2023 filter => "(&(FAIrepository=*)(objectClass=FAIrepositoryServer))",
2024 );
2025 if($mesg->{'resultCode'} == 0 &&
2026 $mesg->count != 0) {
2027 foreach my $entry (@{$mesg->{entries}}) {
2028 if($entry->exists('FAIrepository')) {
2029 # Add an entry for each Repository configured for server
2030 foreach my $repo(@{$entry->get_value('FAIrepository', asref => 1)}) {
2031 my($tmp_url,$tmp_server,$tmp_release,$tmp_sections) = split(/\|/, $repo);
2032 my $tmp_tag= $entry->get_value('gosaUnitTag') || "";
2033 $result= $fai_server_db->add_dbentry( {
2034 table => $table_name,
2035 primkey => ['server', 'release', 'tag'],
2036 server => $tmp_url,
2037 release => $tmp_release,
2038 sections => $tmp_sections,
2039 tag => (length($tmp_tag)>0)?$tmp_tag:"",
2040 } );
2041 }
2042 }
2043 }
2044 }
2045 daemon_log("$session_id INFO: create_fai_server_db: finished", 5);
2047 # TODO: Find a way to post the 'create_packages_list_db' event
2048 if(not defined($dont_create_packages_list)) {
2049 &create_packages_list_db(undef, undef, $session_id);
2050 }
2051 }
2053 $ldap_handle->disconnect;
2054 return $result;
2055 }
2058 sub run_create_fai_release_db {
2059 my ($session, $heap, $table_name) = @_[SESSION, HEAP, ARG0];
2060 my $session_id = $session->ID;
2061 my $task = POE::Wheel::Run->new(
2062 Program => sub { &create_fai_release_db($table_name, $session_id) },
2063 StdoutEvent => "session_run_result",
2064 StderrEvent => "session_run_debug",
2065 CloseEvent => "session_run_done",
2066 );
2068 $heap->{task}->{ $task->ID } = $task;
2069 return;
2070 }
2073 sub create_fai_release_db {
2074 my ($table_name, $session_id) = @_;
2075 my $result;
2077 # used for logging
2078 if (not defined $session_id) { $session_id = 0; }
2080 my $ldap_handle = &get_ldap_handle();
2081 if(defined($ldap_handle)) {
2082 daemon_log("$session_id INFO: create_fai_release_db: start",5);
2083 my $mesg= $ldap_handle->search(
2084 base => $ldap_base,
2085 scope => 'sub',
2086 attrs => [],
2087 filter => "(&(objectClass=organizationalUnit)(ou=fai))",
2088 );
2089 if($mesg->{'resultCode'} == 0 &&
2090 $mesg->count != 0) {
2091 # Walk through all possible FAI container ou's
2092 my @sql_list;
2093 my $timestamp= &get_time();
2094 foreach my $ou (@{$mesg->{entries}}) {
2095 my $tmp_classes= resolve_fai_classes($ou->dn, $ldap_handle, $session_id);
2096 if(defined($tmp_classes) && ref($tmp_classes) eq 'HASH') {
2097 my @tmp_array=get_fai_release_entries($tmp_classes);
2098 if(@tmp_array) {
2099 foreach my $entry (@tmp_array) {
2100 if(defined($entry) && ref($entry) eq 'HASH') {
2101 my $sql=
2102 "INSERT INTO $table_name "
2103 ."(timestamp, release, class, type, state) VALUES ("
2104 .$timestamp.","
2105 ."'".$entry->{'release'}."',"
2106 ."'".$entry->{'class'}."',"
2107 ."'".$entry->{'type'}."',"
2108 ."'".$entry->{'state'}."')";
2109 push @sql_list, $sql;
2110 }
2111 }
2112 }
2113 }
2114 }
2116 daemon_log("$session_id DEBUG: Inserting ".scalar @sql_list." entries to DB",8);
2117 if(@sql_list) {
2118 unshift @sql_list, "VACUUM";
2119 unshift @sql_list, "DELETE FROM $table_name";
2120 $fai_release_db->exec_statementlist(\@sql_list);
2121 }
2122 daemon_log("$session_id DEBUG: Done with inserting",7);
2123 }
2124 daemon_log("$session_id INFO: create_fai_release_db: finished",5);
2125 }
2126 $ldap_handle->disconnect;
2127 return $result;
2128 }
2130 sub get_fai_types {
2131 my $tmp_classes = shift || return undef;
2132 my @result;
2134 foreach my $type(keys %{$tmp_classes}) {
2135 if(defined($tmp_classes->{$type}[0]) && (!($tmp_classes->{$type}[0] =~ /^.*?removed.*?$/))) {
2136 my $entry = {
2137 type => $type,
2138 state => $tmp_classes->{$type}[0],
2139 };
2140 push @result, $entry;
2141 }
2142 }
2144 return @result;
2145 }
2147 sub get_fai_state {
2148 my $result = "";
2149 my $tmp_classes = shift || return $result;
2151 foreach my $type(keys %{$tmp_classes}) {
2152 if(defined($tmp_classes->{$type}[0])) {
2153 $result = $tmp_classes->{$type}[0];
2155 # State is equal for all types in class
2156 last;
2157 }
2158 }
2160 return $result;
2161 }
2163 sub resolve_fai_classes {
2164 my ($fai_base, $ldap_handle, $session_id) = @_;
2165 if (not defined $session_id) { $session_id = 0; }
2166 my $result;
2167 my @possible_fai_classes= ("FAIscript", "FAIhook", "FAIpartitionTable", "FAItemplate", "FAIvariable", "FAIprofile", "FAIpackageList");
2168 my $fai_filter= "(|(&(objectClass=FAIclass)(|(objectClass=".join(")(objectClass=", @possible_fai_classes).")))(objectClass=FAIbranch))";
2169 my $fai_classes;
2171 daemon_log("$session_id DEBUG: Searching for FAI entries in base $fai_base",7);
2172 my $mesg= $ldap_handle->search(
2173 base => $fai_base,
2174 scope => 'sub',
2175 attrs => ['cn','objectClass','FAIstate'],
2176 filter => $fai_filter,
2177 );
2178 daemon_log("$session_id DEBUG: Found ".$mesg->count()." FAI entries",7);
2180 if($mesg->{'resultCode'} == 0 &&
2181 $mesg->count != 0) {
2182 foreach my $entry (@{$mesg->{entries}}) {
2183 if($entry->exists('cn')) {
2184 my $tmp_dn= $entry->dn();
2186 # Skip classname and ou dn parts for class
2187 my $tmp_release = ($1) if $tmp_dn =~ /^[^,]+,[^,]+,(.*?),$fai_base$/;
2189 # Skip classes without releases
2190 if((!defined($tmp_release)) || length($tmp_release)==0) {
2191 next;
2192 }
2194 my $tmp_cn= $entry->get_value('cn');
2195 my $tmp_state= $entry->get_value('FAIstate');
2197 my $tmp_type;
2198 # Get FAI type
2199 for my $oclass(@{$entry->get_value('objectClass', asref => 1)}) {
2200 if(grep $_ eq $oclass, @possible_fai_classes) {
2201 $tmp_type= $oclass;
2202 last;
2203 }
2204 }
2206 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2207 # A Subrelease
2208 my @sub_releases = split(/,/, $tmp_release);
2210 # Walk through subreleases and build hash tree
2211 my $hash;
2212 while(my $tmp_sub_release = pop @sub_releases) {
2213 $hash .= "\{'$tmp_sub_release'\}->";
2214 }
2215 eval('push @{$fai_classes->'.$hash.'{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";');
2216 } else {
2217 # A branch, no subrelease
2218 push @{$fai_classes->{$tmp_release}->{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";
2219 }
2220 } elsif (!$entry->exists('cn')) {
2221 my $tmp_dn= $entry->dn();
2222 my $tmp_release = ($1) if $tmp_dn =~ /^(.*?),$fai_base$/;
2224 # Skip classes without releases
2225 if((!defined($tmp_release)) || length($tmp_release)==0) {
2226 next;
2227 }
2229 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2230 # A Subrelease
2231 my @sub_releases= split(/,/, $tmp_release);
2233 # Walk through subreleases and build hash tree
2234 my $hash;
2235 while(my $tmp_sub_release = pop @sub_releases) {
2236 $hash .= "\{'$tmp_sub_release'\}->";
2237 }
2238 # Remove the last two characters
2239 chop($hash);
2240 chop($hash);
2242 eval('$fai_classes->'.$hash.'= {}');
2243 } else {
2244 # A branch, no subrelease
2245 if(!exists($fai_classes->{$tmp_release})) {
2246 $fai_classes->{$tmp_release} = {};
2247 }
2248 }
2249 }
2250 }
2252 # The hash is complete, now we can honor the copy-on-write based missing entries
2253 foreach my $release (keys %$fai_classes) {
2254 $result->{$release}= deep_copy(apply_fai_inheritance($fai_classes->{$release}));
2255 }
2256 }
2257 return $result;
2258 }
2260 sub apply_fai_inheritance {
2261 my $fai_classes = shift || return {};
2262 my $tmp_classes;
2264 # Get the classes from the branch
2265 foreach my $class (keys %{$fai_classes}) {
2266 # Skip subreleases
2267 if($class =~ /^ou=.*$/) {
2268 next;
2269 } else {
2270 $tmp_classes->{$class}= deep_copy($fai_classes->{$class});
2271 }
2272 }
2274 # Apply to each subrelease
2275 foreach my $subrelease (keys %{$fai_classes}) {
2276 if($subrelease =~ /ou=/) {
2277 foreach my $tmp_class (keys %{$tmp_classes}) {
2278 if(!exists($fai_classes->{$subrelease}->{$tmp_class})) {
2279 $fai_classes->{$subrelease}->{$tmp_class} =
2280 deep_copy($tmp_classes->{$tmp_class});
2281 } else {
2282 foreach my $type (keys %{$tmp_classes->{$tmp_class}}) {
2283 if(!exists($fai_classes->{$subrelease}->{$tmp_class}->{$type})) {
2284 $fai_classes->{$subrelease}->{$tmp_class}->{$type}=
2285 deep_copy($tmp_classes->{$tmp_class}->{$type});
2286 }
2287 }
2288 }
2289 }
2290 }
2291 }
2293 # Find subreleases in deeper levels
2294 foreach my $subrelease (keys %{$fai_classes}) {
2295 if($subrelease =~ /ou=/) {
2296 foreach my $subsubrelease (keys %{$fai_classes->{$subrelease}}) {
2297 if($subsubrelease =~ /ou=/) {
2298 apply_fai_inheritance($fai_classes->{$subrelease});
2299 }
2300 }
2301 }
2302 }
2304 return $fai_classes;
2305 }
2307 sub get_fai_release_entries {
2308 my $tmp_classes = shift || return;
2309 my $parent = shift || "";
2310 my @result = shift || ();
2312 foreach my $entry (keys %{$tmp_classes}) {
2313 if(defined($entry)) {
2314 if($entry =~ /^ou=.*$/) {
2315 my $release_name = $entry;
2316 $release_name =~ s/ou=//g;
2317 if(length($parent)>0) {
2318 $release_name = $parent."/".$release_name;
2319 }
2320 my @bufentries = get_fai_release_entries($tmp_classes->{$entry}, $release_name, @result);
2321 foreach my $bufentry(@bufentries) {
2322 push @result, $bufentry;
2323 }
2324 } else {
2325 my @types = get_fai_types($tmp_classes->{$entry});
2326 foreach my $type (@types) {
2327 push @result,
2328 {
2329 'class' => $entry,
2330 'type' => $type->{'type'},
2331 'release' => $parent,
2332 'state' => $type->{'state'},
2333 };
2334 }
2335 }
2336 }
2337 }
2339 return @result;
2340 }
2342 sub deep_copy {
2343 my $this = shift;
2344 if (not ref $this) {
2345 $this;
2346 } elsif (ref $this eq "ARRAY") {
2347 [map deep_copy($_), @$this];
2348 } elsif (ref $this eq "HASH") {
2349 +{map { $_ => deep_copy($this->{$_}) } keys %$this};
2350 } else { die "what type is $_?" }
2351 }
2354 sub session_run_result {
2355 my ($kernel, $heap, $client_answer) = @_[KERNEL, HEAP, ARG0];
2356 $kernel->sig(CHLD => "child_reap");
2357 }
2359 sub session_run_debug {
2360 my $result = $_[ARG0];
2361 print STDERR "$result\n";
2362 }
2364 sub session_run_done {
2365 my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
2366 delete $heap->{task}->{$task_id};
2367 }
2370 sub create_sources_list {
2371 my $session_id = shift;
2372 my $ldap_handle = &main::get_ldap_handle;
2373 my $result="/tmp/gosa_si_tmp_sources_list";
2375 # Remove old file
2376 if(stat($result)) {
2377 unlink($result);
2378 &main::daemon_log("$session_id DEBUG: remove an old version of '$result'", 7);
2379 }
2381 my $fh;
2382 open($fh, ">$result");
2383 if (not defined $fh) {
2384 &main::daemon_log("$session_id DEBUG: cannot open '$result' for writing", 7);
2385 return undef;
2386 }
2387 if(defined($main::ldap_server_dn) and length($main::ldap_server_dn) > 0) {
2388 my $mesg=$ldap_handle->search(
2389 base => $main::ldap_server_dn,
2390 scope => 'base',
2391 attrs => 'FAIrepository',
2392 filter => 'objectClass=FAIrepositoryServer'
2393 );
2394 if($mesg->count) {
2395 foreach my $entry(@{$mesg->{'entries'}}) {
2396 foreach my $value(@{$entry->get_value('FAIrepository', asref => 1)}) {
2397 my ($server, $tag, $release, $sections)= split /\|/, $value;
2398 my $line = "deb $server $release";
2399 $sections =~ s/,/ /g;
2400 $line.= " $sections";
2401 print $fh $line."\n";
2402 }
2403 }
2404 }
2405 } else {
2406 if (defined $main::ldap_server_dn){
2407 &main::daemon_log("$session_id ERROR: something wrong with ldap_server_dn '$main::ldap_server_dn', abort create_sources_list", 1);
2408 } else {
2409 &main::daemon_log("$session_id ERROR: no ldap_server_dn found, abort create_sources_list", 1);
2410 }
2411 }
2412 close($fh);
2414 return $result;
2415 }
2418 sub run_create_packages_list_db {
2419 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2420 my $session_id = $session->ID;
2422 my $task = POE::Wheel::Run->new(
2423 Priority => +20,
2424 Program => sub {&create_packages_list_db(undef, undef, $session_id)},
2425 StdoutEvent => "session_run_result",
2426 StderrEvent => "session_run_debug",
2427 CloseEvent => "session_run_done",
2428 );
2429 $heap->{task}->{ $task->ID } = $task;
2430 }
2433 sub create_packages_list_db {
2434 my ($ldap_handle, $sources_file, $session_id) = @_;
2436 # it should not be possible to trigger a recreation of packages_list_db
2437 # while packages_list_db is under construction, so set flag packages_list_under_construction
2438 # which is tested befor recreation can be started
2439 if (-r $packages_list_under_construction) {
2440 daemon_log("$session_id WARNING: packages_list_db is right now under construction, please wait until this process is finished", 3);
2441 return;
2442 } else {
2443 daemon_log("$session_id INFO: create_packages_list_db: start", 5);
2444 # set packages_list_under_construction to true
2445 system("touch $packages_list_under_construction");
2446 @packages_list_statements=();
2447 }
2449 if (not defined $session_id) { $session_id = 0; }
2450 if (not defined $ldap_handle) {
2451 $ldap_handle= &get_ldap_handle();
2453 if (not defined $ldap_handle) {
2454 daemon_log("$session_id ERROR: no ldap_handle available to create_packages_list_db", 1);
2455 unlink($packages_list_under_construction);
2456 return;
2457 }
2458 }
2459 if (not defined $sources_file) {
2460 &main::daemon_log("$session_id INFO: no sources_file given for creating packages list so trigger creation of it", 5);
2461 $sources_file = &create_sources_list($session_id);
2462 }
2464 if (not defined $sources_file) {
2465 &main::daemon_log("$session_id ERROR: no sources_file given under '$sources_file', skip create_packages_list_db", 1);
2466 unlink($packages_list_under_construction);
2467 return;
2468 }
2470 my $line;
2472 open(CONFIG, "<$sources_file") or do {
2473 daemon_log( "$session_id ERROR: create_packages_list_db: Failed to open '$sources_file'", 1);
2474 unlink($packages_list_under_construction);
2475 return;
2476 };
2478 # Read lines
2479 while ($line = <CONFIG>){
2480 # Unify
2481 chop($line);
2482 $line =~ s/^\s+//;
2483 $line =~ s/^\s+/ /;
2485 # Strip comments
2486 $line =~ s/#.*$//g;
2488 # Skip empty lines
2489 if ($line =~ /^\s*$/){
2490 next;
2491 }
2493 # Interpret deb line
2494 if ($line =~ /^deb [^\s]+\s[^\s]+\s[^\s]+/){
2495 my( $baseurl, $dist, $sections ) = ($line =~ /^deb\s([^\s]+)\s+([^\s]+)\s+(.*)$/);
2496 my $section;
2497 foreach $section (split(' ', $sections)){
2498 &parse_package_info( $baseurl, $dist, $section, $session_id );
2499 }
2500 }
2501 }
2503 close (CONFIG);
2505 find(\&cleanup_and_extract, keys( %repo_dirs ));
2506 &main::strip_packages_list_statements();
2507 unshift @packages_list_statements, "VACUUM";
2508 $packages_list_db->exec_statementlist(\@packages_list_statements);
2509 unlink($packages_list_under_construction);
2510 daemon_log("$session_id INFO: create_packages_list_db: finished", 5);
2511 return;
2512 }
2514 # This function should do some intensive task to minimize the db-traffic
2515 sub strip_packages_list_statements {
2516 my @existing_entries= @{$packages_list_db->exec_statement("SELECT * FROM $main::packages_list_tn")};
2517 my @new_statement_list=();
2518 my $hash;
2519 my $insert_hash;
2520 my $update_hash;
2521 my $delete_hash;
2522 my $local_timestamp=get_time();
2524 foreach my $existing_entry (@existing_entries) {
2525 $hash->{@{$existing_entry}[0]}->{@{$existing_entry}[1]}->{@{$existing_entry}[2]}= $existing_entry;
2526 }
2528 foreach my $statement (@packages_list_statements) {
2529 if($statement =~ /^INSERT/i) {
2530 # Assign the values from the insert statement
2531 my ($distribution,$package,$version,$section,$description,$template,$timestamp) = ($1,$2,$3,$4,$5,$6,$7) if $statement =~
2532 /^INSERT\s+?INTO\s+?$main::packages_list_tn\s+?VALUES\s*?\('(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)'\s*?\)$/si;
2533 if(exists($hash->{$distribution}->{$package}->{$version})) {
2534 # If section or description has changed, update the DB
2535 if(
2536 (! (@{$hash->{$distribution}->{$package}->{$version}}[3] eq $section)) or
2537 (! (@{$hash->{$distribution}->{$package}->{$version}}[4] eq $description))
2538 ) {
2539 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,undef);
2540 }
2541 } else {
2542 # Insert a non-existing entry to db
2543 @{$insert_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2544 }
2545 } elsif ($statement =~ /^UPDATE/i) {
2546 my ($template,$package,$version) = ($1,$2,$3) if $statement =~
2547 /^update\s+?$main::packages_list_tn\s+?set\s+?template\s*?=\s*?'(.*?)'\s+?where\s+?package\s*?=\s*?'(.*?)'\s+?and\s+?version\s*?=\s*?'(.*?)'\s*?;$/si;
2548 foreach my $distribution (keys %{$hash}) {
2549 if(exists($insert_hash->{$distribution}->{$package}->{$version})) {
2550 # update the insertion hash to execute only one query per package (insert instead insert+update)
2551 @{$insert_hash->{$distribution}->{$package}->{$version}}[5]= $template;
2552 } elsif(exists($hash->{$distribution}->{$package}->{$version})) {
2553 if( ! (@{$hash->{$distribution}->{$package}->{$version}}[5] eq $template)) {
2554 my $section;
2555 my $description;
2556 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) and
2557 length(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) > 0 ) {
2558 $section= @{$update_hash->{$distribution}->{$package}->{$version}}[3];
2559 }
2560 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2561 $description= @{$update_hash->{$distribution}->{$package}->{$version}}[4];
2562 }
2563 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2564 }
2565 }
2566 }
2567 }
2568 }
2570 # TODO: Check for orphaned entries
2572 # unroll the insert_hash
2573 foreach my $distribution (keys %{$insert_hash}) {
2574 foreach my $package (keys %{$insert_hash->{$distribution}}) {
2575 foreach my $version (keys %{$insert_hash->{$distribution}->{$package}}) {
2576 push @new_statement_list, "INSERT INTO $main::packages_list_tn VALUES ('$distribution','$package','$version',"
2577 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[3]',"
2578 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[4]',"
2579 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[5]',"
2580 ."'$local_timestamp')";
2581 }
2582 }
2583 }
2585 # unroll the update hash
2586 foreach my $distribution (keys %{$update_hash}) {
2587 foreach my $package (keys %{$update_hash->{$distribution}}) {
2588 foreach my $version (keys %{$update_hash->{$distribution}->{$package}}) {
2589 my $set = "";
2590 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3])) {
2591 $set .= "section = '@{$update_hash->{$distribution}->{$package}->{$version}}[3]', ";
2592 }
2593 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2594 $set .= "description = '@{$update_hash->{$distribution}->{$package}->{$version}}[4]', ";
2595 }
2596 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[5])) {
2597 $set .= "template = '@{$update_hash->{$distribution}->{$package}->{$version}}[5]', ";
2598 }
2599 if(defined($set) and length($set) > 0) {
2600 $set .= "timestamp = '$local_timestamp'";
2601 } else {
2602 next;
2603 }
2604 push @new_statement_list,
2605 "UPDATE $main::packages_list_tn SET $set WHERE"
2606 ." distribution = '$distribution'"
2607 ." AND package = '$package'"
2608 ." AND version = '$version'";
2609 }
2610 }
2611 }
2613 @packages_list_statements = @new_statement_list;
2614 }
2617 sub parse_package_info {
2618 my ($baseurl, $dist, $section, $session_id)= @_;
2619 my ($package);
2620 if (not defined $session_id) { $session_id = 0; }
2621 my ($path) = ($baseurl =~ m%://[^/]*(.*)$%);
2622 $repo_dirs{ "${repo_path}/pool" } = 1;
2624 foreach $package ("Packages.gz"){
2625 daemon_log("$session_id DEBUG: create_packages_list: fetch $baseurl, $dist, $section", 7);
2626 get_package( "$baseurl/dists/$dist/$section/binary-$arch/$package", "$outdir/$dist/$section", $session_id );
2627 parse_package( "$outdir/$dist/$section", $dist, $path, $session_id );
2628 }
2630 }
2633 sub get_package {
2634 my ($url, $dest, $session_id)= @_;
2635 if (not defined $session_id) { $session_id = 0; }
2637 my $tpath = dirname($dest);
2638 -d "$tpath" || mkpath "$tpath";
2640 # This is ugly, but I've no time to take a look at "how it works in perl"
2641 if(0 == system("wget '$url' -O '$dest' 2>/dev/null") ) {
2642 system("gunzip -cd '$dest' > '$dest.in'");
2643 daemon_log("$session_id DEBUG: run command: gunzip -cd '$dest' > '$dest.in'", 5);
2644 unlink($dest);
2645 daemon_log("$session_id DEBUG: delete file '$dest'", 5);
2646 } else {
2647 daemon_log("$session_id ERROR: create_packages_list_db: get_packages: fetching '$url' failed!", 1);
2648 }
2649 return 0;
2650 }
2653 sub parse_package {
2654 my ($path, $dist, $srv_path, $session_id)= @_;
2655 if (not defined $session_id) { $session_id = 0;}
2656 my ($package, $version, $section, $description);
2657 my $PACKAGES;
2658 my $timestamp = &get_time();
2660 if(not stat("$path.in")) {
2661 daemon_log("$session_id ERROR: create_packages_list: parse_package: file '$path.in' is not readable",1);
2662 return;
2663 }
2665 open($PACKAGES, "<$path.in");
2666 if(not defined($PACKAGES)) {
2667 daemon_log("$session_id ERROR: create_packages_list_db: parse_package: cannot open '$path.in'",1);
2668 return;
2669 }
2671 # Read lines
2672 while (<$PACKAGES>){
2673 my $line = $_;
2674 # Unify
2675 chop($line);
2677 # Use empty lines as a trigger
2678 if ($line =~ /^\s*$/){
2679 my $sql = "INSERT INTO packages_list VALUES ('$dist', '$package', '$version', '$section', '$description', '', '$timestamp')";
2680 push(@packages_list_statements, $sql);
2681 $package = "none";
2682 $version = "none";
2683 $section = "none";
2684 $description = "none";
2685 next;
2686 }
2688 # Trigger for package name
2689 if ($line =~ /^Package:\s/){
2690 ($package)= ($line =~ /^Package: (.*)$/);
2691 next;
2692 }
2694 # Trigger for version
2695 if ($line =~ /^Version:\s/){
2696 ($version)= ($line =~ /^Version: (.*)$/);
2697 next;
2698 }
2700 # Trigger for description
2701 if ($line =~ /^Description:\s/){
2702 ($description)= &encode_base64(($line =~ /^Description: (.*)$/));
2703 next;
2704 }
2706 # Trigger for section
2707 if ($line =~ /^Section:\s/){
2708 ($section)= ($line =~ /^Section: (.*)$/);
2709 next;
2710 }
2712 # Trigger for filename
2713 if ($line =~ /^Filename:\s/){
2714 my ($filename) = ($line =~ /^Filename: (.*)$/);
2715 store_fileinfo( $package, $filename, $dist, $srv_path, $version, $repo_path );
2716 next;
2717 }
2718 }
2720 close( $PACKAGES );
2721 unlink( "$path.in" );
2722 &main::daemon_log("$session_id DEBUG: unlink '$path.in'", 1);
2723 }
2726 sub store_fileinfo {
2727 my( $package, $file, $dist, $path, $vers, $srvdir) = @_;
2729 my %fileinfo = (
2730 'package' => $package,
2731 'dist' => $dist,
2732 'version' => $vers,
2733 );
2735 $repo_files{ "${srvdir}/$file" } = \%fileinfo;
2736 }
2739 sub cleanup_and_extract {
2740 my $fileinfo = $repo_files{ $File::Find::name };
2742 if( defined $fileinfo ) {
2744 my $dir = "$outdir/$fileinfo->{ 'dist' }/debconf.d";
2745 my $sql;
2746 my $package = $fileinfo->{ 'package' };
2747 my $newver = $fileinfo->{ 'version' };
2749 mkpath($dir);
2750 system( "dpkg -e '$File::Find::name' '$dir/DEBIAN'" );
2752 if( -f "$dir/DEBIAN/templates" ) {
2754 daemon_log("DEBUG: Found debconf templates in '$package' - $newver", 5);
2756 my $tmpl= "";
2757 {
2758 local $/=undef;
2759 open FILE, "$dir/DEBIAN/templates";
2760 $tmpl = &encode_base64(<FILE>);
2761 close FILE;
2762 }
2763 rmtree("$dir/DEBIAN/templates");
2765 $sql= "update $main::packages_list_tn set template = '$tmpl' where package = '$package' and version = '$newver';";
2766 push @packages_list_statements, $sql;
2767 }
2768 }
2770 return;
2771 }
2774 sub register_at_foreign_servers {
2775 my ($kernel) = $_[KERNEL];
2777 # hole alle bekannten server aus known_server_db
2778 my $server_sql = "SELECT * FROM $known_server_tn";
2779 my $server_res = $known_server_db->exec_statement($server_sql);
2781 # no entries in known_server_db
2782 if (not ref(@$server_res[0]) eq "ARRAY") {
2783 # TODO
2784 }
2786 # detect already connected clients
2787 my $client_sql = "SELECT * FROM $known_clients_tn";
2788 my $client_res = $known_clients_db->exec_statement($client_sql);
2790 # send my server details to all other gosa-si-server within the network
2791 foreach my $hit (@$server_res) {
2792 my $hostname = @$hit[0];
2793 my $hostkey = &create_passwd;
2795 # add already connected clients to registration message
2796 my $myhash = &create_xml_hash('new_server', $server_address, $hostname);
2797 &add_content2xml_hash($myhash, 'key', $hostkey);
2798 map(&add_content2xml_hash($myhash, 'client', @{$_}[0].",".@{$_}[4]), @$client_res);
2800 # build registration message and send it
2801 my $foreign_server_msg = &create_xml_string($myhash);
2802 my $error = &send_msg_to_target($foreign_server_msg, $hostname, $ServerPackages_key, "new_server", 0);
2803 }
2805 $kernel->delay_set("register_at_foreign_servers", $foreign_servers_register_delay);
2806 return;
2807 }
2810 #==== MAIN = main ==============================================================
2811 # parse commandline options
2812 Getopt::Long::Configure( "bundling" );
2813 GetOptions("h|help" => \&usage,
2814 "c|config=s" => \$cfg_file,
2815 "f|foreground" => \$foreground,
2816 "v|verbose+" => \$verbose,
2817 "no-arp+" => \$no_arp,
2818 );
2820 # read and set config parameters
2821 &check_cmdline_param ;
2822 &read_configfile;
2823 &check_pid;
2825 $SIG{CHLD} = 'IGNORE';
2827 # forward error messages to logfile
2828 if( ! $foreground ) {
2829 open( STDIN, '+>/dev/null' );
2830 open( STDOUT, '+>&STDIN' );
2831 open( STDERR, '+>&STDIN' );
2832 }
2834 # Just fork, if we are not in foreground mode
2835 if( ! $foreground ) {
2836 chdir '/' or die "Can't chdir to /: $!";
2837 $pid = fork;
2838 setsid or die "Can't start a new session: $!";
2839 umask 0;
2840 } else {
2841 $pid = $$;
2842 }
2844 # Do something useful - put our PID into the pid_file
2845 if( 0 != $pid ) {
2846 open( LOCK_FILE, ">$pid_file" );
2847 print LOCK_FILE "$pid\n";
2848 close( LOCK_FILE );
2849 if( !$foreground ) {
2850 exit( 0 )
2851 };
2852 }
2854 # parse head url and revision from svn
2855 my $server_status_hash = { 'developmental'=>'revision', 'stable'=>'release'};
2856 $server_version =~ /^\$HeadURL: (\S+) \$:\$Rev: (\d+) \$$/;
2857 $server_headURL = defined $1 ? $1 : 'unknown' ;
2858 $server_revision = defined $2 ? $2 : 'unknown' ;
2859 if ($server_headURL =~ /\/tag\// ||
2860 $server_headURL =~ /\/branches\// ) {
2861 $server_status = "stable";
2862 } else {
2863 $server_status = "developmental" ;
2864 }
2867 daemon_log(" ", 1);
2868 daemon_log("$0 started!", 1);
2869 daemon_log("status: $server_status", 1);
2870 daemon_log($server_status_hash->{$server_status}.": $server_revision", 1);
2872 # connect to incoming_db
2873 unlink($incoming_file_name);
2874 $incoming_db = GOSA::DBsqlite->new($incoming_file_name);
2875 $incoming_db->create_table($incoming_tn, \@incoming_col_names);
2877 # connect to gosa-si job queue
2878 $job_db = GOSA::DBsqlite->new($job_queue_file_name);
2879 $job_db->create_table($job_queue_tn, \@job_queue_col_names);
2881 # connect to known_clients_db
2882 $known_clients_db = GOSA::DBsqlite->new($known_clients_file_name);
2883 $known_clients_db->create_table($known_clients_tn, \@known_clients_col_names);
2885 # connect to foreign_clients_db
2886 $foreign_clients_db = GOSA::DBsqlite->new($foreign_clients_file_name);
2887 $foreign_clients_db->create_table($foreign_clients_tn, \@foreign_clients_col_names);
2889 # connect to known_server_db
2890 unlink($known_server_file_name);
2891 $known_server_db = GOSA::DBsqlite->new($known_server_file_name);
2892 $known_server_db->create_table($known_server_tn, \@known_server_col_names);
2894 # connect to login_usr_db
2895 $login_users_db = GOSA::DBsqlite->new($login_users_file_name);
2896 $login_users_db->create_table($login_users_tn, \@login_users_col_names);
2898 # connect to fai_server_db and fai_release_db
2899 unlink($fai_server_file_name);
2900 $fai_server_db = GOSA::DBsqlite->new($fai_server_file_name);
2901 $fai_server_db->create_table($fai_server_tn, \@fai_server_col_names);
2903 unlink($fai_release_file_name);
2904 $fai_release_db = GOSA::DBsqlite->new($fai_release_file_name);
2905 $fai_release_db->create_table($fai_release_tn, \@fai_release_col_names);
2907 # connect to packages_list_db
2908 #unlink($packages_list_file_name);
2909 unlink($packages_list_under_construction);
2910 $packages_list_db = GOSA::DBsqlite->new($packages_list_file_name);
2911 $packages_list_db->create_table($packages_list_tn, \@packages_list_col_names);
2913 # connect to messaging_db
2914 $messaging_db = GOSA::DBsqlite->new($messaging_file_name);
2915 $messaging_db->create_table($messaging_tn, \@messaging_col_names);
2918 # create xml object used for en/decrypting
2919 $xml = new XML::Simple();
2922 # foreign servers
2923 my @foreign_server_list;
2925 # add foreign server from cfg file
2926 if ($foreign_server_string ne "") {
2927 my @cfg_foreign_server_list = split(",", $foreign_server_string);
2928 foreach my $foreign_server (@cfg_foreign_server_list) {
2929 push(@foreign_server_list, $foreign_server);
2930 }
2931 }
2933 # add foreign server from dns
2934 my @tmp_servers;
2935 if ( !$server_domain) {
2936 # Try our DNS Searchlist
2937 for my $domain(get_dns_domains()) {
2938 chomp($domain);
2939 my @tmp_domains= &get_server_addresses($domain);
2940 if(@tmp_domains) {
2941 for my $tmp_server(@tmp_domains) {
2942 push @tmp_servers, $tmp_server;
2943 }
2944 }
2945 }
2946 if(@tmp_servers && length(@tmp_servers)==0) {
2947 daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
2948 }
2949 } else {
2950 @tmp_servers = &get_server_addresses($server_domain);
2951 if( 0 == @tmp_servers ) {
2952 daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
2953 }
2954 }
2955 foreach my $server (@tmp_servers) {
2956 unshift(@foreign_server_list, $server);
2957 }
2958 # eliminate duplicate entries
2959 @foreign_server_list = &del_doubles(@foreign_server_list);
2960 my $all_foreign_server = join(", ", @foreign_server_list);
2961 daemon_log("0 INFO: found foreign server in config file and DNS: $all_foreign_server", 5);
2963 # add all found foreign servers to known_server
2964 my $act_timestamp = &get_time();
2965 foreach my $foreign_server (@foreign_server_list) {
2966 my $res = $known_server_db->add_dbentry( {table=>$known_server_tn,
2967 primkey=>['hostname'],
2968 hostname=>$foreign_server,
2969 status=>'not_jet_registered',
2970 hostkey=>"none",
2971 timestamp=>$act_timestamp,
2972 } );
2973 }
2976 POE::Component::Server::TCP->new(
2977 Alias => "TCP_SERVER",
2978 Port => $server_port,
2979 ClientInput => sub {
2980 my ($kernel, $input) = @_[KERNEL, ARG0];
2981 push(@tasks, $input);
2982 push(@msgs_to_decrypt, $input);
2983 $kernel->yield("msg_to_decrypt");
2984 },
2985 InlineStates => {
2986 msg_to_decrypt => \&msg_to_decrypt,
2987 next_task => \&next_task,
2988 task_result => \&handle_task_result,
2989 task_done => \&handle_task_done,
2990 task_debug => \&handle_task_debug,
2991 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!" },
2992 }
2993 );
2995 daemon_log("start socket for incoming xml messages at port '$server_port' ", 1);
2997 # create session for repeatedly checking the job queue for jobs
2998 POE::Session->create(
2999 inline_states => {
3000 _start => \&session_start,
3001 register_at_foreign_servers => \®ister_at_foreign_servers,
3002 sig_handler => \&sig_handler,
3003 next_task => \&next_task,
3004 task_result => \&handle_task_result,
3005 task_done => \&handle_task_done,
3006 task_debug => \&handle_task_debug,
3007 watch_for_next_tasks => \&watch_for_next_tasks,
3008 watch_for_new_messages => \&watch_for_new_messages,
3009 watch_for_delivery_messages => \&watch_for_delivery_messages,
3010 watch_for_done_messages => \&watch_for_done_messages,
3011 watch_for_new_jobs => \&watch_for_new_jobs,
3012 watch_for_done_jobs => \&watch_for_done_jobs,
3013 watch_for_old_known_clients => \&watch_for_old_known_clients,
3014 create_packages_list_db => \&run_create_packages_list_db,
3015 create_fai_server_db => \&run_create_fai_server_db,
3016 create_fai_release_db => \&run_create_fai_release_db,
3017 session_run_result => \&session_run_result,
3018 session_run_debug => \&session_run_debug,
3019 session_run_done => \&session_run_done,
3020 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!" },
3021 }
3022 );
3025 # import all modules
3026 &import_modules;
3028 # TODO
3029 # check wether all modules are gosa-si valid passwd check
3033 POE::Kernel->run();
3034 exit;