c9cbfa3ad914dc5e27d0e074e5dce46e4846f66b
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, $session_id) = @_;
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("$session_id WARNING: outgoing msg is not gosa-si envelope conform: ", 5);
596 daemon_log("$@ ".(defined($msg) && length($msg)>0)?$msg:"Empty Message", 5);
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 ($host_ip, $host_port) = split(/:/, $hostname);
1180 my $local_address = &get_local_ip_for_remote_ip($host_ip).":$server_port";
1181 my $regserver = $res->{1}->{'regserver'};
1182 my $sql = "SELECT * FROM $known_server_tn WHERE hostname='$regserver'";
1183 my $res = $known_server_db->select_dbentry($sql);
1184 if (keys(%$res) > 0) {
1185 my $regserver_key = $res->{1}->{'hostkey'};
1186 $msg =~ s/<source>GOSA<\/source>/<source>$local_address<\/source>/;
1187 $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1188 if ($source eq "GOSA") {
1189 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1190 }
1191 &send_msg_to_target($msg, $regserver, $regserver_key, $header, $session_id);
1192 }
1193 $done = 1;
1194 #print STDERR "target is a client address in foreign_clients -> forward to registration server\n";
1195 } else {
1196 $not_found_in_foreign_clients_db = 1;
1197 }
1198 }
1200 # target is a server address -> forward to server
1201 if (not $done) {
1202 $sql = "SELECT * FROM $known_server_tn WHERE hostname='$target'";
1203 $res = $known_server_db->select_dbentry($sql);
1204 if (keys(%$res) > 0) {
1205 my $hostkey = $res->{1}->{'hostkey'};
1207 if ($source eq "GOSA") {
1208 $msg =~ s/<source>GOSA<\/source>/<source>$local_address<\/source>/;
1209 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1211 }
1213 &send_msg_to_target($msg, $target, $hostkey, $header, $session_id);
1214 $done = 1;
1215 #print STDERR "target is a server address -> forward to server\n";
1216 } else {
1217 $not_found_in_known_server_db = 1;
1218 }
1219 }
1222 # target is not in foreign_clients_db, known_server_db or known_clients_db, maybe it is a complete new one -> process here
1223 if ( $not_found_in_foreign_clients_db
1224 && $not_found_in_known_server_db
1225 && $not_found_in_known_clients_db) {
1226 my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1227 primkey=>[],
1228 headertag=>$header,
1229 targettag=>$target,
1230 xmlmessage=>&encode_base64($msg),
1231 timestamp=>&get_time,
1232 module=>$module,
1233 sessionid=>$session_id,
1234 } );
1235 $done = 1;
1236 }
1239 if (not $done) {
1240 daemon_log("$session_id ERROR: do not know what to do with this message: $msg", 1);
1241 if ($source eq "GOSA") {
1242 my %data = ('error_msg' => &encode_base64($msg), 'error_string' => "Do not know what to do with this message!");
1243 my $error_msg = &build_msg("error", $local_address, "GOSA", \%data );
1245 my $session_reference = $kernel->ID_id_to_session($session_id);
1246 if( defined $session_reference ) {
1247 $heap = $session_reference->get_heap();
1248 }
1249 if(exists $heap->{'client'}) {
1250 $error_msg = &encrypt_msg($error_msg, $GosaPackages_key);
1251 $heap->{'client'}->put($error_msg);
1252 }
1253 }
1254 }
1256 }
1258 return;
1259 }
1262 sub next_task {
1263 my ($session, $heap, $task) = @_[SESSION, HEAP, ARG0];
1264 my $running_task = POE::Wheel::Run->new(
1265 Program => sub { process_task($session, $heap, $task) },
1266 StdioFilter => POE::Filter::Reference->new(),
1267 StdoutEvent => "task_result",
1268 StderrEvent => "task_debug",
1269 CloseEvent => "task_done",
1270 );
1271 $heap->{task}->{ $running_task->ID } = $running_task;
1272 }
1274 sub handle_task_result {
1275 my ($kernel, $heap, $result) = @_[KERNEL, HEAP, ARG0];
1276 my $client_answer = $result->{'answer'};
1277 if( $client_answer =~ s/session_id=(\d+)$// ) {
1278 my $session_id = $1;
1279 if( defined $session_id ) {
1280 my $session_reference = $kernel->ID_id_to_session($session_id);
1281 if( defined $session_reference ) {
1282 $heap = $session_reference->get_heap();
1283 }
1284 }
1286 if(exists $heap->{'client'}) {
1287 $heap->{'client'}->put($client_answer);
1288 }
1289 }
1290 $kernel->sig(CHLD => "child_reap");
1291 }
1293 sub handle_task_debug {
1294 my $result = $_[ARG0];
1295 print STDERR "$result\n";
1296 }
1298 sub handle_task_done {
1299 my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1300 delete $heap->{task}->{$task_id};
1301 }
1303 sub process_task {
1304 no strict "refs";
1305 my ($session, $heap, $task) = @_;
1306 my $error = 0;
1307 my $answer_l;
1308 my ($answer_header, @answer_target_l, $answer_source);
1309 my $client_answer = "";
1311 # prepare all variables needed to process message
1312 #my $msg = $task->{'xmlmessage'};
1313 my $msg = &decode_base64($task->{'xmlmessage'});
1314 my $incoming_id = $task->{'id'};
1315 my $module = $task->{'module'};
1316 my $header = $task->{'headertag'};
1317 my $session_id = $task->{'sessionid'};
1318 my $msg_hash = $xml->XMLin($msg, ForceArray=>1);
1319 my $source = @{$msg_hash->{'source'}}[0];
1321 # set timestamp of incoming client uptodate, so client will not
1322 # be deleted from known_clients because of expiration
1323 my $act_time = &get_time();
1324 my $sql = "UPDATE $known_clients_tn SET timestamp='$act_time' WHERE hostname='$source'";
1325 my $res = $known_clients_db->exec_statement($sql);
1327 ######################
1328 # process incoming msg
1329 if( $error == 0) {
1330 daemon_log("$session_id INFO: Incoming msg (session_id=$session_id) with header '".@{$msg_hash->{'header'}}[0]."'", 5);
1331 daemon_log("$session_id DEBUG: Processing module ".$module, 7);
1332 $answer_l = &{ $module."::process_incoming_msg" }($msg, $msg_hash, $session_id);
1334 if ( 0 < @{$answer_l} ) {
1335 my $answer_str = join("\n", @{$answer_l});
1336 while ($answer_str =~ /<header>(\w+)<\/header>/g) {
1337 daemon_log("$session_id INFO: got answer message with header '$1'", 5);
1338 }
1339 daemon_log("$session_id DEBUG: $module: got answer from module: \n".$answer_str,8);
1340 } else {
1341 daemon_log("$session_id DEBUG: $module: got no answer from module!" ,8);
1342 }
1344 }
1345 if( !$answer_l ) { $error++ };
1347 ########
1348 # answer
1349 if( $error == 0 ) {
1351 foreach my $answer ( @{$answer_l} ) {
1352 # check outgoing msg to xml validity
1353 my $answer_hash = &check_outgoing_xml_validity($answer, $session_id);
1354 if( not defined $answer_hash ) { next; }
1356 $answer_header = @{$answer_hash->{'header'}}[0];
1357 @answer_target_l = @{$answer_hash->{'target'}};
1358 $answer_source = @{$answer_hash->{'source'}}[0];
1360 # deliver msg to all targets
1361 foreach my $answer_target ( @answer_target_l ) {
1363 # targets of msg are all gosa-si-clients in known_clients_db
1364 if( $answer_target eq "*" ) {
1365 # answer is for all clients
1366 my $sql_statement= "SELECT * FROM known_clients";
1367 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1368 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1369 my $host_name = $hit->{hostname};
1370 my $host_key = $hit->{hostkey};
1371 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1372 &update_jobdb_status_for_send_msgs($answer, $error);
1373 }
1374 }
1376 # targets of msg are all gosa-si-server in known_server_db
1377 elsif( $answer_target eq "KNOWN_SERVER" ) {
1378 # answer is for all server in known_server
1379 my $sql_statement= "SELECT * FROM $known_server_tn";
1380 my $query_res = $known_server_db->select_dbentry( $sql_statement );
1381 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1382 my $host_name = $hit->{hostname};
1383 my $host_key = $hit->{hostkey};
1384 $answer =~ s/<target>\S+<\/target>/<target>$host_name<\/target>/g;
1385 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1386 &update_jobdb_status_for_send_msgs($answer, $error);
1387 }
1388 }
1390 # target of msg is GOsa
1391 elsif( $answer_target eq "GOSA" ) {
1392 my $session_id = ($1) if $answer =~ /<session_id>(\d+?)<\/session_id>/;
1393 my $add_on = "";
1394 if( defined $session_id ) {
1395 $add_on = ".session_id=$session_id";
1396 }
1397 # answer is for GOSA and has to returned to connected client
1398 my $gosa_answer = &encrypt_msg($answer, $GosaPackages_key);
1399 $client_answer = $gosa_answer.$add_on;
1400 }
1402 # target of msg is job queue at this host
1403 elsif( $answer_target eq "JOBDB") {
1404 $answer =~ /<header>(\S+)<\/header>/;
1405 my $header;
1406 if( defined $1 ) { $header = $1; }
1407 my $error = &send_msg_to_target($answer, $server_address, $GosaPackages_key, $header, $session_id);
1408 &update_jobdb_status_for_send_msgs($answer, $error);
1409 }
1411 # target of msg is a mac address
1412 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 ) {
1413 daemon_log("$session_id INFO: target is mac address '$answer_target', looking for host in known_clients", 5);
1414 my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$answer_target'";
1415 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1416 my $found_ip_flag = 0;
1417 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1418 my $host_name = $hit->{hostname};
1419 my $host_key = $hit->{hostkey};
1420 $answer =~ s/$answer_target/$host_name/g;
1421 daemon_log("$session_id INFO: found host '$host_name', associated to '$answer_target'", 5);
1422 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1423 &update_jobdb_status_for_send_msgs($answer, $error);
1424 $found_ip_flag++ ;
1425 }
1426 if( $found_ip_flag == 0) {
1427 daemon_log("$session_id WARNING: no host found in known_clients with mac address '$answer_target'", 3);
1428 }
1430 # answer is for one specific host
1431 } else {
1432 # get encrypt_key
1433 my $encrypt_key = &get_encrypt_key($answer_target);
1434 if( not defined $encrypt_key ) {
1435 # unknown target
1436 daemon_log("$session_id WARNING: unknown target '$answer_target'", 3);
1437 next;
1438 }
1439 my $error = &send_msg_to_target($answer, $answer_target, $encrypt_key, $answer_header,$session_id);
1440 &update_jobdb_status_for_send_msgs($answer, $error);
1441 }
1442 }
1443 }
1444 }
1446 my $filter = POE::Filter::Reference->new();
1447 my %result = (
1448 status => "seems ok to me",
1449 answer => $client_answer,
1450 );
1452 my $output = $filter->put( [ \%result ] );
1453 print @$output;
1456 }
1458 sub session_start {
1459 my ($kernel) = $_[KERNEL];
1460 $global_kernel = $kernel;
1461 $kernel->yield('register_at_foreign_servers');
1462 $kernel->yield('create_fai_server_db', $fai_server_tn );
1463 $kernel->yield('create_fai_release_db', $fai_release_tn );
1464 $kernel->yield('watch_for_next_tasks');
1465 $kernel->sig(USR1 => "sig_handler");
1466 $kernel->sig(USR2 => "recreate_packages_db");
1467 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1468 $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay);
1469 $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1470 $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1471 $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1472 $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1474 }
1477 sub watch_for_done_jobs {
1478 my ($kernel,$heap) = @_[KERNEL, HEAP];
1480 my $sql_statement = "SELECT * FROM ".$job_queue_tn." WHERE status='done'";
1481 my $res = $job_db->select_dbentry( $sql_statement );
1483 while( my ($id, $hit) = each %{$res} ) {
1484 my $jobdb_id = $hit->{id};
1485 my $sql_statement = "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1486 my $res = $job_db->del_dbentry($sql_statement);
1487 }
1489 $kernel->delay_set('watch_for_done_jobs',$job_queue_loop_delay);
1490 }
1493 sub watch_for_new_jobs {
1494 if($watch_for_new_jobs_in_progress == 0) {
1495 $watch_for_new_jobs_in_progress = 1;
1496 my ($kernel,$heap) = @_[KERNEL, HEAP];
1498 # check gosa job queue for jobs with executable timestamp
1499 my $timestamp = &get_time();
1500 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE status='waiting' AND (CAST (timestamp AS INTEGER)) < $timestamp ORDER BY timestamp";
1501 my $res = $job_db->exec_statement( $sql_statement );
1503 # Merge all new jobs that would do the same actions
1504 my @drops;
1505 my $hits;
1506 foreach my $hit (reverse @{$res} ) {
1507 my $macaddress= lc @{$hit}[8];
1508 my $headertag= @{$hit}[5];
1509 if(
1510 defined($hits->{$macaddress}) &&
1511 defined($hits->{$macaddress}->{$headertag}) &&
1512 defined($hits->{$macaddress}->{$headertag}[0])
1513 ) {
1514 push @drops, "DELETE FROM $job_queue_tn WHERE id = $hits->{$macaddress}->{$headertag}[0]";
1515 }
1516 $hits->{$macaddress}->{$headertag}= $hit;
1517 }
1519 # Delete new jobs with a matching job in state 'processing'
1520 foreach my $macaddress (keys %{$hits}) {
1521 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1522 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1523 if(defined($jobdb_id)) {
1524 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND headertag='$jobdb_headertag' AND status='processing'";
1525 my $res = $job_db->exec_statement( $sql_statement );
1526 foreach my $hit (@{$res}) {
1527 push @drops, "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1528 }
1529 } else {
1530 daemon_log("J ERROR: Job without id exists for macaddress $macaddress!", 1);
1531 }
1532 }
1533 }
1535 # Commit deletion
1536 $job_db->exec_statementlist(\@drops);
1538 # Look for new jobs that could be executed
1539 foreach my $macaddress (keys %{$hits}) {
1541 # Look if there is an executing job
1542 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='processing'";
1543 my $res = $job_db->exec_statement( $sql_statement );
1545 # Skip new jobs for host if there is a processing job
1546 if(defined($res) and defined @{$res}[0]) {
1547 next;
1548 }
1550 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1551 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1552 if(defined($jobdb_id)) {
1553 my $job_msg = @{$hits->{$macaddress}->{$jobdb_headertag}}[7];
1555 daemon_log("J DEBUG: its time to execute $job_msg", 7);
1556 my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$macaddress'";
1557 my $res_hash = $known_clients_db->select_dbentry( $sql_statement );
1559 # expect macaddress is unique!!!!!!
1560 my $target = $res_hash->{1}->{hostname};
1562 # change header
1563 $job_msg =~ s/<header>job_/<header>gosa_/;
1565 # add sqlite_id
1566 $job_msg =~ s/<\/xml>$/<jobdb_id>$jobdb_id<\/jobdb_id><\/xml>/;
1568 $job_msg =~ /<header>(\S+)<\/header>/;
1569 my $header = $1 ;
1570 my $func_error = &send_msg_to_target($job_msg, $server_address, $GosaPackages_key, $header, "J");
1572 # update status in job queue to 'processing'
1573 $sql_statement = "UPDATE $job_queue_tn SET status='processing' WHERE id=$jobdb_id";
1574 my $res = $job_db->update_dbentry($sql_statement);
1575 # TODO: abfangen ob alles in ordnung ist oder nicht, wenn nicht error schmeißen
1577 # We don't want parallel processing
1578 last;
1579 }
1580 }
1581 }
1583 $watch_for_new_jobs_in_progress = 0;
1584 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1585 }
1586 }
1589 sub watch_for_new_messages {
1590 my ($kernel,$heap) = @_[KERNEL, HEAP];
1591 my @coll_user_msg; # collection list of outgoing messages
1593 # check messaging_db for new incoming messages with executable timestamp
1594 my $timestamp = &get_time();
1595 my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( (CAST(timestamp AS INTEGER))<$timestamp AND flag='n' AND direction='in' )";
1596 my $res = $messaging_db->exec_statement( $sql_statement );
1597 foreach my $hit (@{$res}) {
1599 # create outgoing messages
1600 my $message_to = @{$hit}[3];
1601 # translate message_to to plain login name
1602 my @message_to_l = split(/,/, $message_to);
1603 my %receiver_h;
1604 foreach my $receiver (@message_to_l) {
1605 if ($receiver =~ /^u_([\s\S]*)$/) {
1606 $receiver_h{$1} = 0;
1607 } elsif ($receiver =~ /^g_([\s\S]*)$/) {
1608 my $group_name = $1;
1609 # fetch all group members from ldap and add them to receiver hash
1610 my $ldap_handle = &get_ldap_handle();
1611 if (defined $ldap_handle) {
1612 my $mesg = $ldap_handle->search(
1613 base => $ldap_base,
1614 scope => 'sub',
1615 attrs => ['memberUid'],
1616 filter => "cn=$group_name",
1617 );
1618 if ($mesg->count) {
1619 my @entries = $mesg->entries;
1620 foreach my $entry (@entries) {
1621 my @receivers= $entry->get_value("memberUid");
1622 foreach my $receiver (@receivers) {
1623 $receiver_h{$1} = 0;
1624 }
1625 }
1626 }
1627 # translating errors ?
1628 if ($mesg->code) {
1629 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: $mesg->error", 1);
1630 }
1631 # ldap handle error ?
1632 } else {
1633 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: no ldap handle available", 1);
1634 }
1635 } else {
1636 my $sbjct = &encode_base64(@{$hit}[1]);
1637 my $msg = &encode_base64(@{$hit}[7]);
1638 &daemon_log("M WARNING: unknown receiver '$receiver' for a user-message '$sbjct - $msg'", 3);
1639 }
1640 }
1641 my @receiver_l = keys(%receiver_h);
1643 my $message_id = @{$hit}[0];
1645 #add each outgoing msg to messaging_db
1646 my $receiver;
1647 foreach $receiver (@receiver_l) {
1648 my $sql_statement = "INSERT INTO $messaging_tn (id, subject, message_from, message_to, flag, direction, delivery_time, message, timestamp) ".
1649 "VALUES ('".
1650 $message_id."', '". # id
1651 @{$hit}[1]."', '". # subject
1652 @{$hit}[2]."', '". # message_from
1653 $receiver."', '". # message_to
1654 "none"."', '". # flag
1655 "out"."', '". # direction
1656 @{$hit}[6]."', '". # delivery_time
1657 @{$hit}[7]."', '". # message
1658 $timestamp."'". # timestamp
1659 ")";
1660 &daemon_log("M DEBUG: $sql_statement", 1);
1661 my $res = $messaging_db->exec_statement($sql_statement);
1662 &daemon_log("M INFO: message '".@{$hit}[0]."' is prepared for delivery to receiver '$receiver'", 5);
1663 }
1665 # set incoming message to flag d=deliverd
1666 $sql_statement = "UPDATE $messaging_tn SET flag='p' WHERE id='$message_id'";
1667 &daemon_log("M DEBUG: $sql_statement", 7);
1668 $res = $messaging_db->update_dbentry($sql_statement);
1669 &daemon_log("M INFO: message '$message_id' is set to flag 'p' (processed)", 5);
1670 }
1672 $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1673 return;
1674 }
1676 sub watch_for_delivery_messages {
1677 my ($kernel, $heap) = @_[KERNEL, HEAP];
1679 # select outgoing messages
1680 my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( flag='p' AND direction='out' )";
1681 #&daemon_log("0 DEBUG: $sql", 7);
1682 my $res = $messaging_db->exec_statement( $sql_statement );
1684 # build out msg for each usr
1685 foreach my $hit (@{$res}) {
1686 my $receiver = @{$hit}[3];
1687 my $msg_id = @{$hit}[0];
1688 my $subject = @{$hit}[1];
1689 my $message = @{$hit}[7];
1691 # resolve usr -> host where usr is logged in
1692 my $sql = "SELECT * FROM $login_users_tn WHERE (user='$receiver')";
1693 #&daemon_log("0 DEBUG: $sql", 7);
1694 my $res = $login_users_db->exec_statement($sql);
1696 # reciver is logged in nowhere
1697 if (not ref(@$res[0]) eq "ARRAY") { next; }
1699 my $send_succeed = 0;
1700 foreach my $hit (@$res) {
1701 my $receiver_host = @$hit[0];
1702 &daemon_log("M DEBUG: user '$receiver' is logged in at host '$receiver_host'", 7);
1704 # fetch key to encrypt msg propperly for usr/host
1705 my $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$receiver_host')";
1706 &daemon_log("0 DEBUG: $sql", 7);
1707 my $res = $known_clients_db->exec_statement($sql);
1709 # host is already down
1710 if (not ref(@$res[0]) eq "ARRAY") { next; }
1712 # host is on
1713 my $receiver_key = @{@{$res}[0]}[2];
1714 my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
1715 my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data );
1716 my $error = &send_msg_to_target($out_msg, $receiver_host, $receiver_key, "usr_msg", 0);
1717 if ($error == 0 ) {
1718 $send_succeed++ ;
1719 }
1720 }
1722 if ($send_succeed) {
1723 # set outgoing msg at db to deliverd
1724 my $sql = "UPDATE $messaging_tn SET flag='d' WHERE (id='$msg_id' AND direction='out' AND message_to='$receiver')";
1725 &daemon_log("0 DEBUG: $sql", 7);
1726 my $res = $messaging_db->exec_statement($sql);
1727 }
1728 }
1730 $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1731 return;
1732 }
1735 sub watch_for_done_messages {
1736 my ($kernel,$heap) = @_[KERNEL, HEAP];
1738 my $sql = "SELECT * FROM $messaging_tn WHERE (flag='p' AND direction='in')";
1739 #&daemon_log("0 DEBUG: $sql", 7);
1740 my $res = $messaging_db->exec_statement($sql);
1742 foreach my $hit (@{$res}) {
1743 my $msg_id = @{$hit}[0];
1745 my $sql = "SELECT * FROM $messaging_tn WHERE (id='$msg_id' AND direction='out' AND (NOT flag='s'))";
1746 #&daemon_log("0 DEBUG: $sql", 7);
1747 my $res = $messaging_db->exec_statement($sql);
1749 # not all usr msgs have been seen till now
1750 if ( ref(@$res[0]) eq "ARRAY") { next; }
1752 $sql = "DELETE FROM $messaging_tn WHERE (id='$msg_id')";
1753 #&daemon_log("0 DEBUG: $sql", 7);
1754 $res = $messaging_db->exec_statement($sql);
1756 }
1758 $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1759 return;
1760 }
1763 sub watch_for_old_known_clients {
1764 my ($kernel,$heap) = @_[KERNEL, HEAP];
1766 my $sql_statement = "SELECT * FROM $known_clients_tn";
1767 my $res = $known_clients_db->select_dbentry( $sql_statement );
1769 my $act_time = int(&get_time());
1771 while ( my ($hit_num, $hit) = each %$res) {
1772 my $expired_timestamp = int($hit->{'timestamp'});
1773 $expired_timestamp =~ /(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)/;
1774 my $dt = DateTime->new( year => $1,
1775 month => $2,
1776 day => $3,
1777 hour => $4,
1778 minute => $5,
1779 second => $6,
1780 );
1782 $dt->add( seconds => 2 * int($hit->{'keylifetime'}) );
1783 $expired_timestamp = $dt->ymd('').$dt->hms('')."\n";
1784 if ($act_time > $expired_timestamp) {
1785 my $hostname = $hit->{'hostname'};
1786 my $del_sql = "DELETE FROM $known_clients_tn WHERE hostname='$hostname'";
1787 my $del_res = $known_clients_db->exec_statement($del_sql);
1789 &main::daemon_log("0 INFO: timestamp '".$hit->{'timestamp'}."' of client '$hostname' is expired('$expired_timestamp'), client will be deleted from known_clients_db", 5);
1790 }
1792 }
1794 $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1795 }
1798 sub watch_for_next_tasks {
1799 my ($kernel,$heap) = @_[KERNEL, HEAP];
1801 my $sql = "SELECT * FROM $incoming_tn";
1802 my $res = $incoming_db->select_dbentry($sql);
1804 while ( my ($hit_num, $hit) = each %$res) {
1805 my $headertag = $hit->{'headertag'};
1806 if ($headertag =~ /^answer_(\d+)/) {
1807 # do not start processing, this message is for a still running POE::Wheel
1808 next;
1809 }
1810 my $message_id = $hit->{'id'};
1811 $kernel->yield('next_task', $hit);
1813 my $sql = "DELETE FROM $incoming_tn WHERE id=$message_id";
1814 my $res = $incoming_db->exec_statement($sql);
1815 }
1817 $kernel->delay_set('watch_for_next_tasks', 0.1);
1818 }
1821 sub get_ldap_handle {
1822 my ($session_id) = @_;
1823 my $heap;
1824 my $ldap_handle;
1826 if (not defined $session_id ) { $session_id = 0 };
1827 if ($session_id =~ /[^0-9]*/) { $session_id = 0 };
1829 if ($session_id == 0) {
1830 daemon_log("$session_id DEBUG: get_ldap_handle invoked without a session_id, create a new ldap_handle", 7);
1831 $ldap_handle = Net::LDAP->new( $ldap_uri );
1832 $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!");
1834 } else {
1835 my $session_reference = $global_kernel->ID_id_to_session($session_id);
1836 if( defined $session_reference ) {
1837 $heap = $session_reference->get_heap();
1838 }
1840 if (not defined $heap) {
1841 daemon_log("$session_id DEBUG: cannot get heap for session_id '$session_id'", 7);
1842 return;
1843 }
1845 # TODO: This "if" is nonsense, because it doesn't prove that the
1846 # used handle is still valid - or if we've to reconnect...
1847 #if (not exists $heap->{ldap_handle}) {
1848 $ldap_handle = Net::LDAP->new( $ldap_uri );
1849 $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!");
1850 $heap->{ldap_handle} = $ldap_handle;
1851 #}
1852 }
1853 return $ldap_handle;
1854 }
1857 sub change_fai_state {
1858 my ($st, $targets, $session_id) = @_;
1859 $session_id = 0 if not defined $session_id;
1860 # Set FAI state to localboot
1861 my %mapActions= (
1862 reboot => '',
1863 update => 'softupdate',
1864 localboot => 'localboot',
1865 reinstall => 'install',
1866 rescan => '',
1867 wake => '',
1868 memcheck => 'memcheck',
1869 sysinfo => 'sysinfo',
1870 install => 'install',
1871 );
1873 # Return if this is unknown
1874 if (!exists $mapActions{ $st }){
1875 daemon_log("$session_id ERROR: unknown action '$st', can not translate ot FAIstate", 1);
1876 return;
1877 }
1879 my $state= $mapActions{ $st };
1881 my $ldap_handle = &get_ldap_handle($session_id);
1882 if( defined($ldap_handle) ) {
1884 # Build search filter for hosts
1885 my $search= "(&(objectClass=GOhard)";
1886 foreach (@{$targets}){
1887 $search.= "(macAddress=$_)";
1888 }
1889 $search.= ")";
1891 # If there's any host inside of the search string, procress them
1892 if (!($search =~ /macAddress/)){
1893 daemon_log("$session_id ERROR: no macAddress found in filter statement for LDAP search: '$search'", 1);
1894 return;
1895 }
1897 # Perform search for Unit Tag
1898 my $mesg = $ldap_handle->search(
1899 base => $ldap_base,
1900 scope => 'sub',
1901 attrs => ['dn', 'FAIstate', 'objectClass'],
1902 filter => "$search"
1903 );
1905 if ($mesg->count) {
1906 my @entries = $mesg->entries;
1907 if (0 == @entries) {
1908 daemon_log("$session_id ERROR: ldap search failed: ldap_base=$ldap_base, filter=$search", 1);
1909 }
1911 foreach my $entry (@entries) {
1912 # Only modify entry if it is not set to '$state'
1913 if ($entry->get_value("FAIstate") ne "$state"){
1914 daemon_log("$session_id INFO: Setting FAIstate to '$state' for ".$entry->dn, 5);
1915 my $result;
1916 my %tmp = map { $_ => 1 } $entry->get_value("objectClass");
1917 if (exists $tmp{'FAIobject'}){
1918 if ($state eq ''){
1919 $result= $ldap_handle->modify($entry->dn, changes => [
1920 delete => [ FAIstate => [] ] ]);
1921 } else {
1922 $result= $ldap_handle->modify($entry->dn, changes => [
1923 replace => [ FAIstate => $state ] ]);
1924 }
1925 } elsif ($state ne ''){
1926 $result= $ldap_handle->modify($entry->dn, changes => [
1927 add => [ objectClass => 'FAIobject' ],
1928 add => [ FAIstate => $state ] ]);
1929 }
1931 # Errors?
1932 if ($result->code){
1933 daemon_log("$session_id Error: Setting FAIstate to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1934 }
1935 } else {
1936 daemon_log("$session_id DEBUG FAIstate at host '".$entry->dn."' already at state '$st'", 7);
1937 }
1938 }
1939 } else {
1940 daemon_log("$session_id ERROR: LDAP search failed: ldap_base=$ldap_base, filter=$search", 1);
1941 }
1943 # if no ldap handle defined
1944 } else {
1945 daemon_log("$session_id ERROR: no LDAP handle defined for update FAIstate", 1);
1946 }
1948 return;
1949 }
1952 sub change_goto_state {
1953 my ($st, $targets, $session_id) = @_;
1954 $session_id = 0 if not defined $session_id;
1956 # Switch on or off?
1957 my $state= $st eq 'active' ? 'active': 'locked';
1959 my $ldap_handle = &get_ldap_handle($session_id);
1960 if( defined($ldap_handle) ) {
1962 # Build search filter for hosts
1963 my $search= "(&(objectClass=GOhard)";
1964 foreach (@{$targets}){
1965 $search.= "(macAddress=$_)";
1966 }
1967 $search.= ")";
1969 # If there's any host inside of the search string, procress them
1970 if (!($search =~ /macAddress/)){
1971 return;
1972 }
1974 # Perform search for Unit Tag
1975 my $mesg = $ldap_handle->search(
1976 base => $ldap_base,
1977 scope => 'sub',
1978 attrs => ['dn', 'gotoMode'],
1979 filter => "$search"
1980 );
1982 if ($mesg->count) {
1983 my @entries = $mesg->entries;
1984 foreach my $entry (@entries) {
1986 # Only modify entry if it is not set to '$state'
1987 if ($entry->get_value("gotoMode") ne $state){
1989 daemon_log("$session_id INFO: Setting gotoMode to '$state' for ".$entry->dn, 5);
1990 my $result;
1991 $result= $ldap_handle->modify($entry->dn, changes => [
1992 replace => [ gotoMode => $state ] ]);
1994 # Errors?
1995 if ($result->code){
1996 &daemon_log("$session_id Error: Setting gotoMode to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1997 }
1999 }
2000 }
2001 } else {
2002 daemon_log("$session_id ERROR: LDAP search failed in function change_goto_state: ldap_base=$ldap_base, filter=$search", 1);
2003 }
2005 }
2006 }
2009 sub run_recreate_packages_db {
2010 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2011 my $session_id = $session->ID;
2012 &main::daemon_log("$session_id INFO: Recreating FAI Packages DB ('$fai_release_tn', '$fai_server_tn', '$packages_list_tn')", 4);
2013 $kernel->yield('create_fai_release_db');
2014 $kernel->yield('create_fai_server_db');
2015 return;
2016 }
2019 sub run_create_fai_server_db {
2020 my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
2021 my $session_id = $session->ID;
2022 my $task = POE::Wheel::Run->new(
2023 Program => sub { &create_fai_server_db($table_name,$kernel, undef, $session_id) },
2024 StdoutEvent => "session_run_result",
2025 StderrEvent => "session_run_debug",
2026 CloseEvent => "session_run_done",
2027 );
2029 $heap->{task}->{ $task->ID } = $task;
2030 return;
2031 }
2034 sub create_fai_server_db {
2035 my ($table_name, $kernel, $dont_create_packages_list, $session_id) = @_;
2036 my $result;
2038 if (not defined $session_id) { $session_id = 0; }
2039 my $ldap_handle = &get_ldap_handle();
2040 if(defined($ldap_handle)) {
2041 daemon_log("$session_id INFO: create_fai_server_db: start", 5);
2042 my $mesg= $ldap_handle->search(
2043 base => $ldap_base,
2044 scope => 'sub',
2045 attrs => ['FAIrepository', 'gosaUnitTag'],
2046 filter => "(&(FAIrepository=*)(objectClass=FAIrepositoryServer))",
2047 );
2048 if($mesg->{'resultCode'} == 0 &&
2049 $mesg->count != 0) {
2050 foreach my $entry (@{$mesg->{entries}}) {
2051 if($entry->exists('FAIrepository')) {
2052 # Add an entry for each Repository configured for server
2053 foreach my $repo(@{$entry->get_value('FAIrepository', asref => 1)}) {
2054 my($tmp_url,$tmp_server,$tmp_release,$tmp_sections) = split(/\|/, $repo);
2055 my $tmp_tag= $entry->get_value('gosaUnitTag') || "";
2056 $result= $fai_server_db->add_dbentry( {
2057 table => $table_name,
2058 primkey => ['server', 'release', 'tag'],
2059 server => $tmp_url,
2060 release => $tmp_release,
2061 sections => $tmp_sections,
2062 tag => (length($tmp_tag)>0)?$tmp_tag:"",
2063 } );
2064 }
2065 }
2066 }
2067 }
2068 daemon_log("$session_id INFO: create_fai_server_db: finished", 5);
2070 # TODO: Find a way to post the 'create_packages_list_db' event
2071 if(not defined($dont_create_packages_list)) {
2072 &create_packages_list_db(undef, undef, $session_id);
2073 }
2074 }
2076 $ldap_handle->disconnect;
2077 return $result;
2078 }
2081 sub run_create_fai_release_db {
2082 my ($session, $heap, $table_name) = @_[SESSION, HEAP, ARG0];
2083 my $session_id = $session->ID;
2084 my $task = POE::Wheel::Run->new(
2085 Program => sub { &create_fai_release_db($table_name, $session_id) },
2086 StdoutEvent => "session_run_result",
2087 StderrEvent => "session_run_debug",
2088 CloseEvent => "session_run_done",
2089 );
2091 $heap->{task}->{ $task->ID } = $task;
2092 return;
2093 }
2096 sub create_fai_release_db {
2097 my ($table_name, $session_id) = @_;
2098 my $result;
2100 # used for logging
2101 if (not defined $session_id) { $session_id = 0; }
2103 my $ldap_handle = &get_ldap_handle();
2104 if(defined($ldap_handle)) {
2105 daemon_log("$session_id INFO: create_fai_release_db: start",5);
2106 my $mesg= $ldap_handle->search(
2107 base => $ldap_base,
2108 scope => 'sub',
2109 attrs => [],
2110 filter => "(&(objectClass=organizationalUnit)(ou=fai))",
2111 );
2112 if($mesg->{'resultCode'} == 0 &&
2113 $mesg->count != 0) {
2114 # Walk through all possible FAI container ou's
2115 my @sql_list;
2116 my $timestamp= &get_time();
2117 foreach my $ou (@{$mesg->{entries}}) {
2118 my $tmp_classes= resolve_fai_classes($ou->dn, $ldap_handle, $session_id);
2119 if(defined($tmp_classes) && ref($tmp_classes) eq 'HASH') {
2120 my @tmp_array=get_fai_release_entries($tmp_classes);
2121 if(@tmp_array) {
2122 foreach my $entry (@tmp_array) {
2123 if(defined($entry) && ref($entry) eq 'HASH') {
2124 my $sql=
2125 "INSERT INTO $table_name "
2126 ."(timestamp, release, class, type, state) VALUES ("
2127 .$timestamp.","
2128 ."'".$entry->{'release'}."',"
2129 ."'".$entry->{'class'}."',"
2130 ."'".$entry->{'type'}."',"
2131 ."'".$entry->{'state'}."')";
2132 push @sql_list, $sql;
2133 }
2134 }
2135 }
2136 }
2137 }
2139 daemon_log("$session_id DEBUG: Inserting ".scalar @sql_list." entries to DB",8);
2140 if(@sql_list) {
2141 unshift @sql_list, "VACUUM";
2142 unshift @sql_list, "DELETE FROM $table_name";
2143 $fai_release_db->exec_statementlist(\@sql_list);
2144 }
2145 daemon_log("$session_id DEBUG: Done with inserting",7);
2146 }
2147 daemon_log("$session_id INFO: create_fai_release_db: finished",5);
2148 }
2149 $ldap_handle->disconnect;
2150 return $result;
2151 }
2153 sub get_fai_types {
2154 my $tmp_classes = shift || return undef;
2155 my @result;
2157 foreach my $type(keys %{$tmp_classes}) {
2158 if(defined($tmp_classes->{$type}[0]) && (!($tmp_classes->{$type}[0] =~ /^.*?removed.*?$/))) {
2159 my $entry = {
2160 type => $type,
2161 state => $tmp_classes->{$type}[0],
2162 };
2163 push @result, $entry;
2164 }
2165 }
2167 return @result;
2168 }
2170 sub get_fai_state {
2171 my $result = "";
2172 my $tmp_classes = shift || return $result;
2174 foreach my $type(keys %{$tmp_classes}) {
2175 if(defined($tmp_classes->{$type}[0])) {
2176 $result = $tmp_classes->{$type}[0];
2178 # State is equal for all types in class
2179 last;
2180 }
2181 }
2183 return $result;
2184 }
2186 sub resolve_fai_classes {
2187 my ($fai_base, $ldap_handle, $session_id) = @_;
2188 if (not defined $session_id) { $session_id = 0; }
2189 my $result;
2190 my @possible_fai_classes= ("FAIscript", "FAIhook", "FAIpartitionTable", "FAItemplate", "FAIvariable", "FAIprofile", "FAIpackageList");
2191 my $fai_filter= "(|(&(objectClass=FAIclass)(|(objectClass=".join(")(objectClass=", @possible_fai_classes).")))(objectClass=FAIbranch))";
2192 my $fai_classes;
2194 daemon_log("$session_id DEBUG: Searching for FAI entries in base $fai_base",7);
2195 my $mesg= $ldap_handle->search(
2196 base => $fai_base,
2197 scope => 'sub',
2198 attrs => ['cn','objectClass','FAIstate'],
2199 filter => $fai_filter,
2200 );
2201 daemon_log("$session_id DEBUG: Found ".$mesg->count()." FAI entries",7);
2203 if($mesg->{'resultCode'} == 0 &&
2204 $mesg->count != 0) {
2205 foreach my $entry (@{$mesg->{entries}}) {
2206 if($entry->exists('cn')) {
2207 my $tmp_dn= $entry->dn();
2209 # Skip classname and ou dn parts for class
2210 my $tmp_release = ($1) if $tmp_dn =~ /^[^,]+,[^,]+,(.*?),$fai_base$/;
2212 # Skip classes without releases
2213 if((!defined($tmp_release)) || length($tmp_release)==0) {
2214 next;
2215 }
2217 my $tmp_cn= $entry->get_value('cn');
2218 my $tmp_state= $entry->get_value('FAIstate');
2220 my $tmp_type;
2221 # Get FAI type
2222 for my $oclass(@{$entry->get_value('objectClass', asref => 1)}) {
2223 if(grep $_ eq $oclass, @possible_fai_classes) {
2224 $tmp_type= $oclass;
2225 last;
2226 }
2227 }
2229 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2230 # A Subrelease
2231 my @sub_releases = split(/,/, $tmp_release);
2233 # Walk through subreleases and build hash tree
2234 my $hash;
2235 while(my $tmp_sub_release = pop @sub_releases) {
2236 $hash .= "\{'$tmp_sub_release'\}->";
2237 }
2238 eval('push @{$fai_classes->'.$hash.'{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";');
2239 } else {
2240 # A branch, no subrelease
2241 push @{$fai_classes->{$tmp_release}->{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";
2242 }
2243 } elsif (!$entry->exists('cn')) {
2244 my $tmp_dn= $entry->dn();
2245 my $tmp_release = ($1) if $tmp_dn =~ /^(.*?),$fai_base$/;
2247 # Skip classes without releases
2248 if((!defined($tmp_release)) || length($tmp_release)==0) {
2249 next;
2250 }
2252 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2253 # A Subrelease
2254 my @sub_releases= split(/,/, $tmp_release);
2256 # Walk through subreleases and build hash tree
2257 my $hash;
2258 while(my $tmp_sub_release = pop @sub_releases) {
2259 $hash .= "\{'$tmp_sub_release'\}->";
2260 }
2261 # Remove the last two characters
2262 chop($hash);
2263 chop($hash);
2265 eval('$fai_classes->'.$hash.'= {}');
2266 } else {
2267 # A branch, no subrelease
2268 if(!exists($fai_classes->{$tmp_release})) {
2269 $fai_classes->{$tmp_release} = {};
2270 }
2271 }
2272 }
2273 }
2275 # The hash is complete, now we can honor the copy-on-write based missing entries
2276 foreach my $release (keys %$fai_classes) {
2277 $result->{$release}= deep_copy(apply_fai_inheritance($fai_classes->{$release}));
2278 }
2279 }
2280 return $result;
2281 }
2283 sub apply_fai_inheritance {
2284 my $fai_classes = shift || return {};
2285 my $tmp_classes;
2287 # Get the classes from the branch
2288 foreach my $class (keys %{$fai_classes}) {
2289 # Skip subreleases
2290 if($class =~ /^ou=.*$/) {
2291 next;
2292 } else {
2293 $tmp_classes->{$class}= deep_copy($fai_classes->{$class});
2294 }
2295 }
2297 # Apply to each subrelease
2298 foreach my $subrelease (keys %{$fai_classes}) {
2299 if($subrelease =~ /ou=/) {
2300 foreach my $tmp_class (keys %{$tmp_classes}) {
2301 if(!exists($fai_classes->{$subrelease}->{$tmp_class})) {
2302 $fai_classes->{$subrelease}->{$tmp_class} =
2303 deep_copy($tmp_classes->{$tmp_class});
2304 } else {
2305 foreach my $type (keys %{$tmp_classes->{$tmp_class}}) {
2306 if(!exists($fai_classes->{$subrelease}->{$tmp_class}->{$type})) {
2307 $fai_classes->{$subrelease}->{$tmp_class}->{$type}=
2308 deep_copy($tmp_classes->{$tmp_class}->{$type});
2309 }
2310 }
2311 }
2312 }
2313 }
2314 }
2316 # Find subreleases in deeper levels
2317 foreach my $subrelease (keys %{$fai_classes}) {
2318 if($subrelease =~ /ou=/) {
2319 foreach my $subsubrelease (keys %{$fai_classes->{$subrelease}}) {
2320 if($subsubrelease =~ /ou=/) {
2321 apply_fai_inheritance($fai_classes->{$subrelease});
2322 }
2323 }
2324 }
2325 }
2327 return $fai_classes;
2328 }
2330 sub get_fai_release_entries {
2331 my $tmp_classes = shift || return;
2332 my $parent = shift || "";
2333 my @result = shift || ();
2335 foreach my $entry (keys %{$tmp_classes}) {
2336 if(defined($entry)) {
2337 if($entry =~ /^ou=.*$/) {
2338 my $release_name = $entry;
2339 $release_name =~ s/ou=//g;
2340 if(length($parent)>0) {
2341 $release_name = $parent."/".$release_name;
2342 }
2343 my @bufentries = get_fai_release_entries($tmp_classes->{$entry}, $release_name, @result);
2344 foreach my $bufentry(@bufentries) {
2345 push @result, $bufentry;
2346 }
2347 } else {
2348 my @types = get_fai_types($tmp_classes->{$entry});
2349 foreach my $type (@types) {
2350 push @result,
2351 {
2352 'class' => $entry,
2353 'type' => $type->{'type'},
2354 'release' => $parent,
2355 'state' => $type->{'state'},
2356 };
2357 }
2358 }
2359 }
2360 }
2362 return @result;
2363 }
2365 sub deep_copy {
2366 my $this = shift;
2367 if (not ref $this) {
2368 $this;
2369 } elsif (ref $this eq "ARRAY") {
2370 [map deep_copy($_), @$this];
2371 } elsif (ref $this eq "HASH") {
2372 +{map { $_ => deep_copy($this->{$_}) } keys %$this};
2373 } else { die "what type is $_?" }
2374 }
2377 sub session_run_result {
2378 my ($kernel, $heap, $client_answer) = @_[KERNEL, HEAP, ARG0];
2379 $kernel->sig(CHLD => "child_reap");
2380 }
2382 sub session_run_debug {
2383 my $result = $_[ARG0];
2384 print STDERR "$result\n";
2385 }
2387 sub session_run_done {
2388 my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
2389 delete $heap->{task}->{$task_id};
2390 }
2393 sub create_sources_list {
2394 my $session_id = shift;
2395 my $ldap_handle = &main::get_ldap_handle;
2396 my $result="/tmp/gosa_si_tmp_sources_list";
2398 # Remove old file
2399 if(stat($result)) {
2400 unlink($result);
2401 &main::daemon_log("$session_id DEBUG: remove an old version of '$result'", 7);
2402 }
2404 my $fh;
2405 open($fh, ">$result");
2406 if (not defined $fh) {
2407 &main::daemon_log("$session_id DEBUG: cannot open '$result' for writing", 7);
2408 return undef;
2409 }
2410 if(defined($main::ldap_server_dn) and length($main::ldap_server_dn) > 0) {
2411 my $mesg=$ldap_handle->search(
2412 base => $main::ldap_server_dn,
2413 scope => 'base',
2414 attrs => 'FAIrepository',
2415 filter => 'objectClass=FAIrepositoryServer'
2416 );
2417 if($mesg->count) {
2418 foreach my $entry(@{$mesg->{'entries'}}) {
2419 foreach my $value(@{$entry->get_value('FAIrepository', asref => 1)}) {
2420 my ($server, $tag, $release, $sections)= split /\|/, $value;
2421 my $line = "deb $server $release";
2422 $sections =~ s/,/ /g;
2423 $line.= " $sections";
2424 print $fh $line."\n";
2425 }
2426 }
2427 }
2428 } else {
2429 if (defined $main::ldap_server_dn){
2430 &main::daemon_log("$session_id ERROR: something wrong with ldap_server_dn '$main::ldap_server_dn', abort create_sources_list", 1);
2431 } else {
2432 &main::daemon_log("$session_id ERROR: no ldap_server_dn found, abort create_sources_list", 1);
2433 }
2434 }
2435 close($fh);
2437 return $result;
2438 }
2441 sub run_create_packages_list_db {
2442 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2443 my $session_id = $session->ID;
2445 my $task = POE::Wheel::Run->new(
2446 Priority => +20,
2447 Program => sub {&create_packages_list_db(undef, undef, $session_id)},
2448 StdoutEvent => "session_run_result",
2449 StderrEvent => "session_run_debug",
2450 CloseEvent => "session_run_done",
2451 );
2452 $heap->{task}->{ $task->ID } = $task;
2453 }
2456 sub create_packages_list_db {
2457 my ($ldap_handle, $sources_file, $session_id) = @_;
2459 # it should not be possible to trigger a recreation of packages_list_db
2460 # while packages_list_db is under construction, so set flag packages_list_under_construction
2461 # which is tested befor recreation can be started
2462 if (-r $packages_list_under_construction) {
2463 daemon_log("$session_id WARNING: packages_list_db is right now under construction, please wait until this process is finished", 3);
2464 return;
2465 } else {
2466 daemon_log("$session_id INFO: create_packages_list_db: start", 5);
2467 # set packages_list_under_construction to true
2468 system("touch $packages_list_under_construction");
2469 @packages_list_statements=();
2470 }
2472 if (not defined $session_id) { $session_id = 0; }
2473 if (not defined $ldap_handle) {
2474 $ldap_handle= &get_ldap_handle();
2476 if (not defined $ldap_handle) {
2477 daemon_log("$session_id ERROR: no ldap_handle available to create_packages_list_db", 1);
2478 unlink($packages_list_under_construction);
2479 return;
2480 }
2481 }
2482 if (not defined $sources_file) {
2483 &main::daemon_log("$session_id INFO: no sources_file given for creating packages list so trigger creation of it", 5);
2484 $sources_file = &create_sources_list($session_id);
2485 }
2487 if (not defined $sources_file) {
2488 &main::daemon_log("$session_id ERROR: no sources_file given under '$sources_file', skip create_packages_list_db", 1);
2489 unlink($packages_list_under_construction);
2490 return;
2491 }
2493 my $line;
2495 open(CONFIG, "<$sources_file") or do {
2496 daemon_log( "$session_id ERROR: create_packages_list_db: Failed to open '$sources_file'", 1);
2497 unlink($packages_list_under_construction);
2498 return;
2499 };
2501 # Read lines
2502 while ($line = <CONFIG>){
2503 # Unify
2504 chop($line);
2505 $line =~ s/^\s+//;
2506 $line =~ s/^\s+/ /;
2508 # Strip comments
2509 $line =~ s/#.*$//g;
2511 # Skip empty lines
2512 if ($line =~ /^\s*$/){
2513 next;
2514 }
2516 # Interpret deb line
2517 if ($line =~ /^deb [^\s]+\s[^\s]+\s[^\s]+/){
2518 my( $baseurl, $dist, $sections ) = ($line =~ /^deb\s([^\s]+)\s+([^\s]+)\s+(.*)$/);
2519 my $section;
2520 foreach $section (split(' ', $sections)){
2521 &parse_package_info( $baseurl, $dist, $section, $session_id );
2522 }
2523 }
2524 }
2526 close (CONFIG);
2528 find(\&cleanup_and_extract, keys( %repo_dirs ));
2529 &main::strip_packages_list_statements();
2530 unshift @packages_list_statements, "VACUUM";
2531 $packages_list_db->exec_statementlist(\@packages_list_statements);
2532 unlink($packages_list_under_construction);
2533 daemon_log("$session_id INFO: create_packages_list_db: finished", 5);
2534 return;
2535 }
2537 # This function should do some intensive task to minimize the db-traffic
2538 sub strip_packages_list_statements {
2539 my @existing_entries= @{$packages_list_db->exec_statement("SELECT * FROM $main::packages_list_tn")};
2540 my @new_statement_list=();
2541 my $hash;
2542 my $insert_hash;
2543 my $update_hash;
2544 my $delete_hash;
2545 my $local_timestamp=get_time();
2547 foreach my $existing_entry (@existing_entries) {
2548 $hash->{@{$existing_entry}[0]}->{@{$existing_entry}[1]}->{@{$existing_entry}[2]}= $existing_entry;
2549 }
2551 foreach my $statement (@packages_list_statements) {
2552 if($statement =~ /^INSERT/i) {
2553 # Assign the values from the insert statement
2554 my ($distribution,$package,$version,$section,$description,$template,$timestamp) = ($1,$2,$3,$4,$5,$6,$7) if $statement =~
2555 /^INSERT\s+?INTO\s+?$main::packages_list_tn\s+?VALUES\s*?\('(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)'\s*?\)$/si;
2556 if(exists($hash->{$distribution}->{$package}->{$version})) {
2557 # If section or description has changed, update the DB
2558 if(
2559 (! (@{$hash->{$distribution}->{$package}->{$version}}[3] eq $section)) or
2560 (! (@{$hash->{$distribution}->{$package}->{$version}}[4] eq $description))
2561 ) {
2562 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,undef);
2563 }
2564 } else {
2565 # Insert a non-existing entry to db
2566 @{$insert_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2567 }
2568 } elsif ($statement =~ /^UPDATE/i) {
2569 my ($template,$package,$version) = ($1,$2,$3) if $statement =~
2570 /^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;
2571 foreach my $distribution (keys %{$hash}) {
2572 if(exists($insert_hash->{$distribution}->{$package}->{$version})) {
2573 # update the insertion hash to execute only one query per package (insert instead insert+update)
2574 @{$insert_hash->{$distribution}->{$package}->{$version}}[5]= $template;
2575 } elsif(exists($hash->{$distribution}->{$package}->{$version})) {
2576 if( ! (@{$hash->{$distribution}->{$package}->{$version}}[5] eq $template)) {
2577 my $section;
2578 my $description;
2579 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) and
2580 length(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) > 0 ) {
2581 $section= @{$update_hash->{$distribution}->{$package}->{$version}}[3];
2582 }
2583 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2584 $description= @{$update_hash->{$distribution}->{$package}->{$version}}[4];
2585 }
2586 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2587 }
2588 }
2589 }
2590 }
2591 }
2593 # TODO: Check for orphaned entries
2595 # unroll the insert_hash
2596 foreach my $distribution (keys %{$insert_hash}) {
2597 foreach my $package (keys %{$insert_hash->{$distribution}}) {
2598 foreach my $version (keys %{$insert_hash->{$distribution}->{$package}}) {
2599 push @new_statement_list, "INSERT INTO $main::packages_list_tn VALUES ('$distribution','$package','$version',"
2600 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[3]',"
2601 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[4]',"
2602 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[5]',"
2603 ."'$local_timestamp')";
2604 }
2605 }
2606 }
2608 # unroll the update hash
2609 foreach my $distribution (keys %{$update_hash}) {
2610 foreach my $package (keys %{$update_hash->{$distribution}}) {
2611 foreach my $version (keys %{$update_hash->{$distribution}->{$package}}) {
2612 my $set = "";
2613 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3])) {
2614 $set .= "section = '@{$update_hash->{$distribution}->{$package}->{$version}}[3]', ";
2615 }
2616 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2617 $set .= "description = '@{$update_hash->{$distribution}->{$package}->{$version}}[4]', ";
2618 }
2619 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[5])) {
2620 $set .= "template = '@{$update_hash->{$distribution}->{$package}->{$version}}[5]', ";
2621 }
2622 if(defined($set) and length($set) > 0) {
2623 $set .= "timestamp = '$local_timestamp'";
2624 } else {
2625 next;
2626 }
2627 push @new_statement_list,
2628 "UPDATE $main::packages_list_tn SET $set WHERE"
2629 ." distribution = '$distribution'"
2630 ." AND package = '$package'"
2631 ." AND version = '$version'";
2632 }
2633 }
2634 }
2636 @packages_list_statements = @new_statement_list;
2637 }
2640 sub parse_package_info {
2641 my ($baseurl, $dist, $section, $session_id)= @_;
2642 my ($package);
2643 if (not defined $session_id) { $session_id = 0; }
2644 my ($path) = ($baseurl =~ m%://[^/]*(.*)$%);
2645 $repo_dirs{ "${repo_path}/pool" } = 1;
2647 foreach $package ("Packages.gz"){
2648 daemon_log("$session_id DEBUG: create_packages_list: fetch $baseurl, $dist, $section", 7);
2649 get_package( "$baseurl/dists/$dist/$section/binary-$arch/$package", "$outdir/$dist/$section", $session_id );
2650 parse_package( "$outdir/$dist/$section", $dist, $path, $session_id );
2651 }
2653 }
2656 sub get_package {
2657 my ($url, $dest, $session_id)= @_;
2658 if (not defined $session_id) { $session_id = 0; }
2660 my $tpath = dirname($dest);
2661 -d "$tpath" || mkpath "$tpath";
2663 # This is ugly, but I've no time to take a look at "how it works in perl"
2664 if(0 == system("wget '$url' -O '$dest' 2>/dev/null") ) {
2665 system("gunzip -cd '$dest' > '$dest.in'");
2666 daemon_log("$session_id DEBUG: run command: gunzip -cd '$dest' > '$dest.in'", 5);
2667 unlink($dest);
2668 daemon_log("$session_id DEBUG: delete file '$dest'", 5);
2669 } else {
2670 daemon_log("$session_id ERROR: create_packages_list_db: get_packages: fetching '$url' failed!", 1);
2671 }
2672 return 0;
2673 }
2676 sub parse_package {
2677 my ($path, $dist, $srv_path, $session_id)= @_;
2678 if (not defined $session_id) { $session_id = 0;}
2679 my ($package, $version, $section, $description);
2680 my $PACKAGES;
2681 my $timestamp = &get_time();
2683 if(not stat("$path.in")) {
2684 daemon_log("$session_id ERROR: create_packages_list: parse_package: file '$path.in' is not readable",1);
2685 return;
2686 }
2688 open($PACKAGES, "<$path.in");
2689 if(not defined($PACKAGES)) {
2690 daemon_log("$session_id ERROR: create_packages_list_db: parse_package: cannot open '$path.in'",1);
2691 return;
2692 }
2694 # Read lines
2695 while (<$PACKAGES>){
2696 my $line = $_;
2697 # Unify
2698 chop($line);
2700 # Use empty lines as a trigger
2701 if ($line =~ /^\s*$/){
2702 my $sql = "INSERT INTO packages_list VALUES ('$dist', '$package', '$version', '$section', '$description', '', '$timestamp')";
2703 push(@packages_list_statements, $sql);
2704 $package = "none";
2705 $version = "none";
2706 $section = "none";
2707 $description = "none";
2708 next;
2709 }
2711 # Trigger for package name
2712 if ($line =~ /^Package:\s/){
2713 ($package)= ($line =~ /^Package: (.*)$/);
2714 next;
2715 }
2717 # Trigger for version
2718 if ($line =~ /^Version:\s/){
2719 ($version)= ($line =~ /^Version: (.*)$/);
2720 next;
2721 }
2723 # Trigger for description
2724 if ($line =~ /^Description:\s/){
2725 ($description)= &encode_base64(($line =~ /^Description: (.*)$/));
2726 next;
2727 }
2729 # Trigger for section
2730 if ($line =~ /^Section:\s/){
2731 ($section)= ($line =~ /^Section: (.*)$/);
2732 next;
2733 }
2735 # Trigger for filename
2736 if ($line =~ /^Filename:\s/){
2737 my ($filename) = ($line =~ /^Filename: (.*)$/);
2738 store_fileinfo( $package, $filename, $dist, $srv_path, $version, $repo_path );
2739 next;
2740 }
2741 }
2743 close( $PACKAGES );
2744 unlink( "$path.in" );
2745 }
2748 sub store_fileinfo {
2749 my( $package, $file, $dist, $path, $vers, $srvdir) = @_;
2751 my %fileinfo = (
2752 'package' => $package,
2753 'dist' => $dist,
2754 'version' => $vers,
2755 );
2757 $repo_files{ "${srvdir}/$file" } = \%fileinfo;
2758 }
2761 sub cleanup_and_extract {
2762 my $fileinfo = $repo_files{ $File::Find::name };
2764 if( defined $fileinfo ) {
2766 my $dir = "$outdir/$fileinfo->{ 'dist' }/debconf.d";
2767 my $sql;
2768 my $package = $fileinfo->{ 'package' };
2769 my $newver = $fileinfo->{ 'version' };
2771 mkpath($dir);
2772 system( "dpkg -e '$File::Find::name' '$dir/DEBIAN'" );
2774 if( -f "$dir/DEBIAN/templates" ) {
2776 daemon_log("DEBUG: Found debconf templates in '$package' - $newver", 5);
2778 my $tmpl= "";
2779 {
2780 local $/=undef;
2781 open FILE, "$dir/DEBIAN/templates";
2782 $tmpl = &encode_base64(<FILE>);
2783 close FILE;
2784 }
2785 rmtree("$dir/DEBIAN/templates");
2787 $sql= "update $main::packages_list_tn set template = '$tmpl' where package = '$package' and version = '$newver';";
2788 push @packages_list_statements, $sql;
2789 }
2790 }
2792 return;
2793 }
2796 sub register_at_foreign_servers {
2797 my ($kernel) = $_[KERNEL];
2799 # hole alle bekannten server aus known_server_db
2800 my $server_sql = "SELECT * FROM $known_server_tn";
2801 my $server_res = $known_server_db->exec_statement($server_sql);
2803 # no entries in known_server_db
2804 if (not ref(@$server_res[0]) eq "ARRAY") {
2805 # TODO
2806 }
2808 # detect already connected clients
2809 my $client_sql = "SELECT * FROM $known_clients_tn";
2810 my $client_res = $known_clients_db->exec_statement($client_sql);
2812 # send my server details to all other gosa-si-server within the network
2813 foreach my $hit (@$server_res) {
2814 my $hostname = @$hit[0];
2815 my $hostkey = &create_passwd;
2817 # add already connected clients to registration message
2818 my $myhash = &create_xml_hash('new_server', $server_address, $hostname);
2819 &add_content2xml_hash($myhash, 'key', $hostkey);
2820 map(&add_content2xml_hash($myhash, 'client', @{$_}[0].",".@{$_}[4]), @$client_res);
2822 # build registration message and send it
2823 my $foreign_server_msg = &create_xml_string($myhash);
2824 my $error = &send_msg_to_target($foreign_server_msg, $hostname, $ServerPackages_key, "new_server", 0);
2825 }
2827 $kernel->delay_set("register_at_foreign_servers", $foreign_servers_register_delay);
2828 return;
2829 }
2832 #==== MAIN = main ==============================================================
2833 # parse commandline options
2834 Getopt::Long::Configure( "bundling" );
2835 GetOptions("h|help" => \&usage,
2836 "c|config=s" => \$cfg_file,
2837 "f|foreground" => \$foreground,
2838 "v|verbose+" => \$verbose,
2839 "no-arp+" => \$no_arp,
2840 );
2842 # read and set config parameters
2843 &check_cmdline_param ;
2844 &read_configfile;
2845 &check_pid;
2847 $SIG{CHLD} = 'IGNORE';
2849 # forward error messages to logfile
2850 if( ! $foreground ) {
2851 open( STDIN, '+>/dev/null' );
2852 open( STDOUT, '+>&STDIN' );
2853 open( STDERR, '+>&STDIN' );
2854 }
2856 # Just fork, if we are not in foreground mode
2857 if( ! $foreground ) {
2858 chdir '/' or die "Can't chdir to /: $!";
2859 $pid = fork;
2860 setsid or die "Can't start a new session: $!";
2861 umask 0;
2862 } else {
2863 $pid = $$;
2864 }
2866 # Do something useful - put our PID into the pid_file
2867 if( 0 != $pid ) {
2868 open( LOCK_FILE, ">$pid_file" );
2869 print LOCK_FILE "$pid\n";
2870 close( LOCK_FILE );
2871 if( !$foreground ) {
2872 exit( 0 )
2873 };
2874 }
2876 # parse head url and revision from svn
2877 my $server_status_hash = { 'developmental'=>'revision', 'stable'=>'release'};
2878 $server_version =~ /^\$HeadURL: (\S+) \$:\$Rev: (\d+) \$$/;
2879 $server_headURL = defined $1 ? $1 : 'unknown' ;
2880 $server_revision = defined $2 ? $2 : 'unknown' ;
2881 if ($server_headURL =~ /\/tag\// ||
2882 $server_headURL =~ /\/branches\// ) {
2883 $server_status = "stable";
2884 } else {
2885 $server_status = "developmental" ;
2886 }
2889 daemon_log(" ", 1);
2890 daemon_log("$0 started!", 1);
2891 daemon_log("status: $server_status", 1);
2892 daemon_log($server_status_hash->{$server_status}.": $server_revision", 1);
2894 # connect to incoming_db
2895 unlink($incoming_file_name);
2896 $incoming_db = GOSA::DBsqlite->new($incoming_file_name);
2897 $incoming_db->create_table($incoming_tn, \@incoming_col_names);
2899 # connect to gosa-si job queue
2900 $job_db = GOSA::DBsqlite->new($job_queue_file_name);
2901 $job_db->create_table($job_queue_tn, \@job_queue_col_names);
2903 # connect to known_clients_db
2904 $known_clients_db = GOSA::DBsqlite->new($known_clients_file_name);
2905 $known_clients_db->create_table($known_clients_tn, \@known_clients_col_names);
2907 # connect to foreign_clients_db
2908 $foreign_clients_db = GOSA::DBsqlite->new($foreign_clients_file_name);
2909 $foreign_clients_db->create_table($foreign_clients_tn, \@foreign_clients_col_names);
2911 # connect to known_server_db
2912 unlink($known_server_file_name);
2913 $known_server_db = GOSA::DBsqlite->new($known_server_file_name);
2914 $known_server_db->create_table($known_server_tn, \@known_server_col_names);
2916 # connect to login_usr_db
2917 $login_users_db = GOSA::DBsqlite->new($login_users_file_name);
2918 $login_users_db->create_table($login_users_tn, \@login_users_col_names);
2920 # connect to fai_server_db and fai_release_db
2921 unlink($fai_server_file_name);
2922 $fai_server_db = GOSA::DBsqlite->new($fai_server_file_name);
2923 $fai_server_db->create_table($fai_server_tn, \@fai_server_col_names);
2925 unlink($fai_release_file_name);
2926 $fai_release_db = GOSA::DBsqlite->new($fai_release_file_name);
2927 $fai_release_db->create_table($fai_release_tn, \@fai_release_col_names);
2929 # connect to packages_list_db
2930 #unlink($packages_list_file_name);
2931 unlink($packages_list_under_construction);
2932 $packages_list_db = GOSA::DBsqlite->new($packages_list_file_name);
2933 $packages_list_db->create_table($packages_list_tn, \@packages_list_col_names);
2935 # connect to messaging_db
2936 $messaging_db = GOSA::DBsqlite->new($messaging_file_name);
2937 $messaging_db->create_table($messaging_tn, \@messaging_col_names);
2940 # create xml object used for en/decrypting
2941 $xml = new XML::Simple();
2944 # foreign servers
2945 my @foreign_server_list;
2947 # add foreign server from cfg file
2948 if ($foreign_server_string ne "") {
2949 my @cfg_foreign_server_list = split(",", $foreign_server_string);
2950 foreach my $foreign_server (@cfg_foreign_server_list) {
2951 push(@foreign_server_list, $foreign_server);
2952 }
2953 }
2955 # add foreign server from dns
2956 my @tmp_servers;
2957 if ( !$server_domain) {
2958 # Try our DNS Searchlist
2959 for my $domain(get_dns_domains()) {
2960 chomp($domain);
2961 my @tmp_domains= &get_server_addresses($domain);
2962 if(@tmp_domains) {
2963 for my $tmp_server(@tmp_domains) {
2964 push @tmp_servers, $tmp_server;
2965 }
2966 }
2967 }
2968 if(@tmp_servers && length(@tmp_servers)==0) {
2969 daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
2970 }
2971 } else {
2972 @tmp_servers = &get_server_addresses($server_domain);
2973 if( 0 == @tmp_servers ) {
2974 daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
2975 }
2976 }
2977 foreach my $server (@tmp_servers) {
2978 unshift(@foreign_server_list, $server);
2979 }
2980 # eliminate duplicate entries
2981 @foreign_server_list = &del_doubles(@foreign_server_list);
2982 my $all_foreign_server = join(", ", @foreign_server_list);
2983 daemon_log("0 INFO: found foreign server in config file and DNS: $all_foreign_server", 5);
2985 # add all found foreign servers to known_server
2986 my $act_timestamp = &get_time();
2987 foreach my $foreign_server (@foreign_server_list) {
2989 # do not add myself to known_server_db
2990 if (&is_local($foreign_server)) { next; }
2991 ######################################
2993 my $res = $known_server_db->add_dbentry( {table=>$known_server_tn,
2994 primkey=>['hostname'],
2995 hostname=>$foreign_server,
2996 status=>'not_jet_registered',
2997 hostkey=>"none",
2998 timestamp=>$act_timestamp,
2999 } );
3000 }
3003 POE::Component::Server::TCP->new(
3004 Alias => "TCP_SERVER",
3005 Port => $server_port,
3006 ClientInput => sub {
3007 my ($kernel, $input) = @_[KERNEL, ARG0];
3008 push(@tasks, $input);
3009 push(@msgs_to_decrypt, $input);
3010 $kernel->yield("msg_to_decrypt");
3011 },
3012 InlineStates => {
3013 msg_to_decrypt => \&msg_to_decrypt,
3014 next_task => \&next_task,
3015 task_result => \&handle_task_result,
3016 task_done => \&handle_task_done,
3017 task_debug => \&handle_task_debug,
3018 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!" },
3019 }
3020 );
3022 daemon_log("start socket for incoming xml messages at port '$server_port' ", 1);
3024 # create session for repeatedly checking the job queue for jobs
3025 POE::Session->create(
3026 inline_states => {
3027 _start => \&session_start,
3028 register_at_foreign_servers => \®ister_at_foreign_servers,
3029 sig_handler => \&sig_handler,
3030 next_task => \&next_task,
3031 task_result => \&handle_task_result,
3032 task_done => \&handle_task_done,
3033 task_debug => \&handle_task_debug,
3034 watch_for_next_tasks => \&watch_for_next_tasks,
3035 watch_for_new_messages => \&watch_for_new_messages,
3036 watch_for_delivery_messages => \&watch_for_delivery_messages,
3037 watch_for_done_messages => \&watch_for_done_messages,
3038 watch_for_new_jobs => \&watch_for_new_jobs,
3039 watch_for_done_jobs => \&watch_for_done_jobs,
3040 watch_for_old_known_clients => \&watch_for_old_known_clients,
3041 create_packages_list_db => \&run_create_packages_list_db,
3042 create_fai_server_db => \&run_create_fai_server_db,
3043 create_fai_release_db => \&run_create_fai_release_db,
3044 recreate_packages_db => \&run_recreate_packages_db,
3045 session_run_result => \&session_run_result,
3046 session_run_debug => \&session_run_debug,
3047 session_run_done => \&session_run_done,
3048 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!" },
3049 }
3050 );
3053 # import all modules
3054 &import_modules;
3056 # TODO
3057 # check wether all modules are gosa-si valid passwd check
3061 POE::Kernel->run();
3062 exit;