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