9c0c9776b202582cb7dcea01c9d78b809effef08
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::GosaSupportDaemon;
52 use POE qw(Component::Server::TCP Wheel::Run Filter::Reference);
53 use Net::LDAP;
54 use Net::LDAP::Util qw(:escape);
55 use Time::HiRes qw( usleep);
57 my $db_module = "DBsqlite";
58 {
59 no strict "refs";
60 require ("GOSA/".$db_module.".pm");
61 ("GOSA/".$db_module)->import;
62 daemon_log("0 INFO: importing database module '$db_module'", 1);
63 }
65 my $modules_path = "/usr/lib/gosa-si/modules";
66 use lib "/usr/lib/gosa-si/modules";
68 # revision number of server and program name
69 my $server_version = '$HeadURL: https://oss.gonicus.de/repositories/gosa/trunk/gosa-si/gosa-si-server $:$Rev: 10826 $';
70 my $server_headURL;
71 my $server_revision;
72 my $server_status;
73 our $prg= basename($0);
75 our $global_kernel;
76 my ($foreground, $ping_timeout);
77 my ($server);
78 my ($gosa_server, $job_queue_timeout, $job_queue_loop_delay);
79 my ($messaging_db_loop_delay);
80 my ($procid, $pid);
81 my ($arp_fifo);
82 my ($xml);
83 my $sources_list;
84 my $max_clients;
85 my %repo_files=();
86 my $repo_path;
87 my %repo_dirs=();
89 # Variables declared in config file are always set to 'our'
90 our (%cfg_defaults, $log_file, $pid_file,
91 $server_ip, $server_port, $ClientPackages_key, $dns_lookup,
92 $arp_activ, $gosa_unit_tag,
93 $GosaPackages_key, $gosa_timeout,
94 $foreign_server_string, $server_domain, $ServerPackages_key, $foreign_servers_register_delay,
95 $wake_on_lan_passwd, $job_synchronization, $modified_jobs_loop_delay,
96 $arp_enabled, $arp_interface,
97 $opsi_enabled, $opsi_server, $opsi_admin, $opsi_password,
98 $new_systems_ou,
99 );
101 # additional variable which should be globaly accessable
102 our $server_address;
103 our $server_mac_address;
104 our $gosa_address;
105 our $no_arp;
106 our $verbose;
107 our $forground;
108 our $cfg_file;
109 our ($ldap_uri, $ldap_base, $ldap_admin_dn, $ldap_admin_password, $ldap_server_dn);
110 our ($mysql_username, $mysql_password, $mysql_database, $mysql_host);
111 our $known_modules;
112 our $root_uid;
113 our $adm_gid;
116 # specifies the verbosity of the daemon_log
117 $verbose = 0 ;
119 # if foreground is not null, script will be not forked to background
120 $foreground = 0 ;
122 # specifies the timeout seconds while checking the online status of a registrating client
123 $ping_timeout = 5;
125 $no_arp = 0;
126 my $packages_list_under_construction = "/tmp/packages_list_creation_in_progress";
127 my @packages_list_statements;
128 my $watch_for_new_jobs_in_progress = 0;
130 # holds all incoming decrypted messages
131 our $incoming_db;
132 our $incoming_tn = 'incoming';
133 my $incoming_file_name;
134 my @incoming_col_names = ("id INTEGER PRIMARY KEY auto_increment",
135 "timestamp VARCHAR(14) DEFAULT 'none'",
136 "headertag VARCHAR(255) DEFAULT 'none'",
137 "targettag VARCHAR(255) DEFAULT 'none'",
138 "xmlmessage TEXT",
139 "module VARCHAR(255) DEFAULT 'none'",
140 "sessionid VARCHAR(255) DEFAULT '0'",
141 );
143 # holds all gosa jobs
144 our $job_db;
145 our $job_queue_tn = 'jobs';
146 my $job_queue_file_name;
147 my @job_queue_col_names = ("id INTEGER PRIMARY KEY auto_increment",
148 "timestamp VARCHAR(14) DEFAULT 'none'",
149 "status VARCHAR(255) DEFAULT 'none'",
150 "result TEXT",
151 "progress VARCHAR(255) DEFAULT 'none'",
152 "headertag VARCHAR(255) DEFAULT 'none'",
153 "targettag VARCHAR(255) DEFAULT 'none'",
154 "xmlmessage TEXT",
155 "macaddress VARCHAR(17) DEFAULT 'none'",
156 "plainname VARCHAR(255) DEFAULT 'none'",
157 "siserver VARCHAR(255) DEFAULT 'none'",
158 "modified INTEGER DEFAULT '0'",
159 );
161 # holds all other gosa-si-server
162 our $known_server_db;
163 our $known_server_tn = "known_server";
164 my $known_server_file_name;
165 my @known_server_col_names = ("hostname VARCHAR(255)", "macaddress VARCHAR(17)", "status VARCHAR(255)", "hostkey VARCHAR(255)", "loaded_modules TEXT", "timestamp VARCHAR(14)");
167 # holds all registrated clients
168 our $known_clients_db;
169 our $known_clients_tn = "known_clients";
170 my $known_clients_file_name;
171 my @known_clients_col_names = ("hostname VARCHAR(255)", "status VARCHAR(255)", "hostkey VARCHAR(255)", "timestamp VARCHAR(14)", "macaddress VARCHAR(17)", "events TEXT", "keylifetime VARCHAR(255)");
173 # holds all registered clients at a foreign server
174 our $foreign_clients_db;
175 our $foreign_clients_tn = "foreign_clients";
176 my $foreign_clients_file_name;
177 my @foreign_clients_col_names = ("hostname VARCHAR(255)", "macaddress VARCHAR(17)", "regserver VARCHAR(255)", "timestamp VARCHAR(14)");
179 # holds all logged in user at each client
180 our $login_users_db;
181 our $login_users_tn = "login_users";
182 my $login_users_file_name;
183 my @login_users_col_names = ("client VARCHAR(255)", "user VARCHAR(255)", "timestamp VARCHAR(14)");
185 # holds all fai server, the debian release and tag
186 our $fai_server_db;
187 our $fai_server_tn = "fai_server";
188 my $fai_server_file_name;
189 our @fai_server_col_names = ("timestamp VARCHAR(14)", "server VARCHAR(255)", "fai_release VARCHAR(255)", "sections VARCHAR(255)", "tag VARCHAR(255)");
191 our $fai_release_db;
192 our $fai_release_tn = "fai_release";
193 my $fai_release_file_name;
194 our @fai_release_col_names = ("timestamp VARCHAR(14)", "fai_release VARCHAR(255)", "class VARCHAR(255)", "type VARCHAR(255)", "state VARCHAR(255)");
196 # holds all packages available from different repositories
197 our $packages_list_db;
198 our $packages_list_tn = "packages_list";
199 my $packages_list_file_name;
200 our @packages_list_col_names = ("distribution VARCHAR(255)", "package VARCHAR(255)", "version VARCHAR(255)", "section VARCHAR(255)", "description TEXT", "template LONGBLOB", "timestamp VARCHAR(14)");
201 my $outdir = "/tmp/packages_list_db";
202 my $arch = "i386";
204 # holds all messages which should be delivered to a user
205 our $messaging_db;
206 our $messaging_tn = "messaging";
207 our @messaging_col_names = ("id INTEGER", "subject TEXT", "message_from VARCHAR(255)", "message_to VARCHAR(255)",
208 "flag VARCHAR(255)", "direction VARCHAR(255)", "delivery_time VARCHAR(255)", "message TEXT", "timestamp VARCHAR(14)" );
209 my $messaging_file_name;
211 # path to directory to store client install log files
212 our $client_fai_log_dir = "/var/log/fai";
214 # queue which stores taskes until one of the $max_children children are ready to process the task
215 #my @tasks = qw();
216 my @msgs_to_decrypt = qw();
217 my $max_children = 2;
220 # loop delay for job queue to look for opsi jobs
221 my $job_queue_opsi_delay = 10;
222 our $opsi_client;
223 our $opsi_url;
225 # Lifetime of logged in user information. If no update information comes after n seconds,
226 # the user is expeceted to be no longer logged in or the host is no longer running. Because
227 # of this, the user is deleted from login_users_db
228 our $logged_in_user_date_of_expiry = 600;
231 %cfg_defaults = (
232 "general" => {
233 "log-file" => [\$log_file, "/var/run/".$prg.".log"],
234 "pid-file" => [\$pid_file, "/var/run/".$prg.".pid"],
235 },
236 "server" => {
237 "ip" => [\$server_ip, "0.0.0.0"],
238 "port" => [\$server_port, "20081"],
239 "known-clients" => [\$known_clients_file_name, '/var/lib/gosa-si/clients.db' ],
240 "known-servers" => [\$known_server_file_name, '/var/lib/gosa-si/servers.db'],
241 "incoming" => [\$incoming_file_name, '/var/lib/gosa-si/incoming.db'],
242 "login-users" => [\$login_users_file_name, '/var/lib/gosa-si/users.db'],
243 "fai-server" => [\$fai_server_file_name, '/var/lib/gosa-si/fai_server.db'],
244 "fai-release" => [\$fai_release_file_name, '/var/lib/gosa-si/fai_release.db'],
245 "packages-list" => [\$packages_list_file_name, '/var/lib/gosa-si/packages.db'],
246 "messaging" => [\$messaging_file_name, '/var/lib/gosa-si/messaging.db'],
247 "foreign-clients" => [\$foreign_clients_file_name, '/var/lib/gosa-si/foreign_clients.db'],
248 "source-list" => [\$sources_list, '/etc/apt/sources.list'],
249 "repo-path" => [\$repo_path, '/srv/www/repository'],
250 "ldap-uri" => [\$ldap_uri, ""],
251 "ldap-base" => [\$ldap_base, ""],
252 "ldap-admin-dn" => [\$ldap_admin_dn, ""],
253 "ldap-admin-password" => [\$ldap_admin_password, ""],
254 "gosa-unit-tag" => [\$gosa_unit_tag, ""],
255 "max-clients" => [\$max_clients, 10],
256 "wol-password" => [\$wake_on_lan_passwd, ""],
257 "mysql-username" => [\$mysql_username, "gosa_si"],
258 "mysql-password" => [\$mysql_password, ""],
259 "mysql-database" => [\$mysql_database, "gosa_si"],
260 "mysql-host" => [\$mysql_host, "127.0.0.1"],
261 },
262 "GOsaPackages" => {
263 "job-queue" => [\$job_queue_file_name, '/var/lib/gosa-si/jobs.db'],
264 "job-queue-loop-delay" => [\$job_queue_loop_delay, 3],
265 "messaging-db-loop-delay" => [\$messaging_db_loop_delay, 3],
266 "key" => [\$GosaPackages_key, "none"],
267 "new-systems-ou" => [\$new_systems_ou, 'ou=workstations,ou=systems'],
268 },
269 "ClientPackages" => {
270 "key" => [\$ClientPackages_key, "none"],
271 "user-date-of-expiry" => [\$logged_in_user_date_of_expiry, 600],
272 },
273 "ServerPackages"=> {
274 "address" => [\$foreign_server_string, ""],
275 "dns-lookup" => [\$dns_lookup, "true"],
276 "domain" => [\$server_domain, ""],
277 "key" => [\$ServerPackages_key, "none"],
278 "key-lifetime" => [\$foreign_servers_register_delay, 120],
279 "job-synchronization-enabled" => [\$job_synchronization, "true"],
280 "synchronization-loop" => [\$modified_jobs_loop_delay, 5],
281 },
282 "ArpHandler" => {
283 "enabled" => [\$arp_enabled, "true"],
284 "interface" => [\$arp_interface, "all"],
285 },
286 "Opsi" => {
287 "enabled" => [\$opsi_enabled, "false"],
288 "server" => [\$opsi_server, "localhost"],
289 "admin" => [\$opsi_admin, "opsi-admin"],
290 "password" => [\$opsi_password, "secret"],
291 },
293 );
296 #=== FUNCTION ================================================================
297 # NAME: usage
298 # PARAMETERS: nothing
299 # RETURNS: nothing
300 # DESCRIPTION: print out usage text to STDERR
301 #===============================================================================
302 sub usage {
303 print STDERR << "EOF" ;
304 usage: $prg [-hvf] [-c config]
306 -h : this (help) message
307 -c <file> : config file
308 -f : foreground, process will not be forked to background
309 -v : be verbose (multiple to increase verbosity)
310 -no-arp : starts $prg without connection to arp module
312 EOF
313 print "\n" ;
314 }
317 #=== FUNCTION ================================================================
318 # NAME: logging
319 # PARAMETERS: level - string - default 'info'
320 # msg - string -
321 # facility - string - default 'LOG_DAEMON'
322 # RETURNS: nothing
323 # DESCRIPTION: function for logging
324 #===============================================================================
325 sub daemon_log {
326 # log into log_file
327 my( $msg, $level ) = @_;
328 if(not defined $msg) { return }
329 if(not defined $level) { $level = 1 }
330 if(defined $log_file){
331 open(LOG_HANDLE, ">>$log_file");
332 if(not defined open( LOG_HANDLE, ">>$log_file" )) {
333 print STDERR "cannot open $log_file: $!";
334 return
335 }
336 chomp($msg);
337 #$msg =~s/\n//g; # no newlines are allowed in log messages, this is important for later log parsing
338 if($level <= $verbose){
339 my ($seconds, $minutes, $hours, $monthday, $month,
340 $year, $weekday, $yearday, $sommertime) = localtime(time);
341 $hours = $hours < 10 ? $hours = "0".$hours : $hours;
342 $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes;
343 $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds;
344 my @monthnames = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
345 $month = $monthnames[$month];
346 $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday;
347 $year+=1900;
348 my $name = $prg;
350 my $log_msg = "$month $monthday $hours:$minutes:$seconds $name $msg\n";
351 print LOG_HANDLE $log_msg;
352 if( $foreground ) {
353 print STDERR $log_msg;
354 }
355 }
356 close( LOG_HANDLE );
357 }
358 }
361 #=== FUNCTION ================================================================
362 # NAME: check_cmdline_param
363 # PARAMETERS: nothing
364 # RETURNS: nothing
365 # DESCRIPTION: validates commandline parameter
366 #===============================================================================
367 sub check_cmdline_param () {
368 my $err_config;
369 my $err_counter = 0;
370 if(not defined($cfg_file)) {
371 $cfg_file = "/etc/gosa-si/server.conf";
372 if(! -r $cfg_file) {
373 $err_config = "please specify a config file";
374 $err_counter += 1;
375 }
376 }
377 if( $err_counter > 0 ) {
378 &usage( "", 1 );
379 if( defined( $err_config)) { print STDERR "$err_config\n"}
380 print STDERR "\n";
381 exit( -1 );
382 }
383 }
386 #=== FUNCTION ================================================================
387 # NAME: check_pid
388 # PARAMETERS: nothing
389 # RETURNS: nothing
390 # DESCRIPTION: handels pid processing
391 #===============================================================================
392 sub check_pid {
393 $pid = -1;
394 # Check, if we are already running
395 if( open(LOCK_FILE, "<$pid_file") ) {
396 $pid = <LOCK_FILE>;
397 if( defined $pid ) {
398 chomp( $pid );
399 if( -f "/proc/$pid/stat" ) {
400 my($stat) = `cat /proc/$pid/stat` =~ m/$pid \((.+)\).*/;
401 if( $stat ) {
402 daemon_log("ERROR: Already running",1);
403 close( LOCK_FILE );
404 exit -1;
405 }
406 }
407 }
408 close( LOCK_FILE );
409 unlink( $pid_file );
410 }
412 # create a syslog msg if it is not to possible to open PID file
413 if (not sysopen(LOCK_FILE, $pid_file, O_WRONLY|O_CREAT|O_EXCL, 0644)) {
414 my($msg) = "Couldn't obtain lockfile '$pid_file' ";
415 if (open(LOCK_FILE, '<', $pid_file)
416 && ($pid = <LOCK_FILE>))
417 {
418 chomp($pid);
419 $msg .= "(PID $pid)\n";
420 } else {
421 $msg .= "(unable to read PID)\n";
422 }
423 if( ! ($foreground) ) {
424 openlog( $0, "cons,pid", "daemon" );
425 syslog( "warning", $msg );
426 closelog();
427 }
428 else {
429 print( STDERR " $msg " );
430 }
431 exit( -1 );
432 }
433 }
435 #=== FUNCTION ================================================================
436 # NAME: import_modules
437 # PARAMETERS: module_path - string - abs. path to the directory the modules
438 # are stored
439 # RETURNS: nothing
440 # DESCRIPTION: each file in module_path which ends with '.pm' and activation
441 # state is on is imported by "require 'file';"
442 #===============================================================================
443 sub import_modules {
444 daemon_log(" ", 1);
446 if (not -e $modules_path) {
447 daemon_log("0 ERROR: cannot find directory or directory is not readable: $modules_path", 1);
448 }
450 opendir (DIR, $modules_path) or die "ERROR while loading modules from directory $modules_path : $!\n";
451 while (defined (my $file = readdir (DIR))) {
452 if (not $file =~ /(\S*?).pm$/) {
453 next;
454 }
455 my $mod_name = $1;
457 # ArpHandler switch
458 if( $file =~ /ArpHandler.pm/ ) {
459 if( $arp_enabled eq "false" ) { next; }
460 }
462 eval { require $file; };
463 if ($@) {
464 daemon_log("0 ERROR: gosa-si-server could not load module $file", 1);
465 daemon_log("$@", 1);
466 exit;
467 } else {
468 my $info = eval($mod_name.'::get_module_info()');
469 # Only load module if get_module_info() returns a non-null object
470 if( $info ) {
471 my ($input_address, $input_key, $event_hash) = @{$info};
472 $known_modules->{$mod_name} = $info;
473 daemon_log("0 INFO: module $mod_name loaded", 5);
474 }
475 }
476 }
478 close (DIR);
479 }
481 #=== FUNCTION ================================================================
482 # NAME: password_check
483 # PARAMETERS: nothing
484 # RETURNS: nothing
485 # DESCRIPTION: escalates an critical error if two modules exist which are avaialable by
486 # the same password
487 #===============================================================================
488 sub password_check {
489 my $passwd_hash = {};
490 while (my ($mod_name, $mod_info) = each %$known_modules) {
491 my $mod_passwd = @$mod_info[1];
492 if (not defined $mod_passwd) { next; }
493 if (not exists $passwd_hash->{$mod_passwd}) {
494 $passwd_hash->{$mod_passwd} = $mod_name;
496 # escalates critical error
497 } else {
498 &daemon_log("0 ERROR: two loaded modules do have the same password. Please modify the 'key'-parameter in config file");
499 &daemon_log("0 ERROR: module='$mod_name' and module='".$passwd_hash->{$mod_passwd}."'");
500 exit( -1 );
501 }
502 }
504 }
507 #=== FUNCTION ================================================================
508 # NAME: sig_int_handler
509 # PARAMETERS: signal - string - signal arose from system
510 # RETURNS: nothing
511 # DESCRIPTION: handels tasks to be done befor signal becomes active
512 #===============================================================================
513 sub sig_int_handler {
514 my ($signal) = @_;
516 # if (defined($ldap_handle)) {
517 # $ldap_handle->disconnect;
518 # }
519 # TODO alle verbliebenden ldap verbindungen aus allen heaps beenden
522 daemon_log("shutting down gosa-si-server", 1);
523 system("kill `ps -C gosa-si-server -o pid=`");
524 }
525 $SIG{INT} = \&sig_int_handler;
528 sub check_key_and_xml_validity {
529 my ($crypted_msg, $module_key, $session_id) = @_;
530 my $msg;
531 my $msg_hash;
532 my $error_string;
533 eval{
534 $msg = &decrypt_msg($crypted_msg, $module_key);
536 if ($msg =~ /<xml>/i){
537 $msg =~ s/\s+/ /g; # just for better daemon_log
538 daemon_log("$session_id DEBUG: decrypted_msg: \n$msg", 9);
539 $msg_hash = $xml->XMLin($msg, ForceArray=>1);
541 ##############
542 # check header
543 if( not exists $msg_hash->{'header'} ) { die "no header specified"; }
544 my $header_l = $msg_hash->{'header'};
545 if( (1 > @{$header_l}) || ( ( 'HASH' eq ref @{$header_l}[0]) && (1 > keys %{@{$header_l}[0]}) ) ) { die 'empty header tag'; }
546 if( 1 < @{$header_l} ) { die 'more than one header specified'; }
547 my $header = @{$header_l}[0];
548 if( 0 == length $header) { die 'empty string in header tag'; }
550 ##############
551 # check source
552 if( not exists $msg_hash->{'source'} ) { die "no source specified"; }
553 my $source_l = $msg_hash->{'source'};
554 if( (1 > @{$source_l}) || ( ( 'HASH' eq ref @{$source_l}[0]) && (1 > keys %{@{$source_l}[0]}) ) ) { die 'empty source tag'; }
555 if( 1 < @{$source_l} ) { die 'more than one source specified'; }
556 my $source = @{$source_l}[0];
557 if( 0 == length $source) { die 'source error'; }
559 ##############
560 # check target
561 if( not exists $msg_hash->{'target'} ) { die "no target specified"; }
562 my $target_l = $msg_hash->{'target'};
563 if( (1 > @{$target_l}) || ( ('HASH' eq ref @{$target_l}[0]) && (1 > keys %{@{$target_l}[0]}) ) ) { die 'empty target tag'; }
564 }
565 };
566 if($@) {
567 daemon_log("$session_id ERROR: do not understand the message: $@", 1);
568 $msg = undef;
569 $msg_hash = undef;
570 }
572 return ($msg, $msg_hash);
573 }
576 sub check_outgoing_xml_validity {
577 my ($msg, $session_id) = @_;
579 my $msg_hash;
580 eval{
581 $msg_hash = $xml->XMLin($msg, ForceArray=>1);
583 ##############
584 # check header
585 my $header_l = $msg_hash->{'header'};
586 if( 1 != @{$header_l} ) {
587 die 'no or more than one headers specified';
588 }
589 my $header = @{$header_l}[0];
590 if( 0 == length $header) {
591 die 'header has length 0';
592 }
594 ##############
595 # check source
596 my $source_l = $msg_hash->{'source'};
597 if( 1 != @{$source_l} ) {
598 die 'no or more than 1 sources specified';
599 }
600 my $source = @{$source_l}[0];
601 if( 0 == length $source) {
602 die 'source has length 0';
603 }
605 # Check if source contains hostname instead of ip address
606 if(not $source =~ /^[a-z0-9\.]+:\d+$/i) {
607 my ($hostname,$port) = split(/:/, $source);
608 my $ip_address = inet_ntoa(scalar gethostbyname($hostname));
609 if(defined($ip_address) && $ip_address =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/ && $port =~ /^\d+$/) {
610 # Write ip address to $source variable
611 $source = "$ip_address:$port";
612 }
613 }
614 unless( $source =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
615 $source =~ /^GOSA$/i) {
616 die "source '$source' is neither a complete ip-address with port nor 'GOSA'";
617 }
619 ##############
620 # check target
621 my $target_l = $msg_hash->{'target'};
622 if( 0 == @{$target_l} ) {
623 die "no targets specified";
624 }
625 foreach my $target (@$target_l) {
626 if( 0 == length $target) {
627 die "target has length 0";
628 }
629 unless( $target =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
630 $target =~ /^GOSA$/i ||
631 $target =~ /^\*$/ ||
632 $target =~ /KNOWN_SERVER/i ||
633 $target =~ /JOBDB/i ||
634 $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 ){
635 die "target '$target' is not a complete ip-address with port or a valid target name or a mac-address";
636 }
637 }
638 };
639 if($@) {
640 daemon_log("$session_id ERROR: outgoing msg is not gosa-si envelope conform: $@", 1);
641 daemon_log("$@ ".(defined($msg) && length($msg)>0)?$msg:"Empty Message", 1);
642 $msg_hash = undef;
643 }
645 return ($msg_hash);
646 }
649 sub input_from_known_server {
650 my ($input, $remote_ip, $session_id) = @_ ;
651 my ($msg, $msg_hash, $module);
653 my $sql_statement= "SELECT * FROM known_server";
654 my $query_res = $known_server_db->select_dbentry( $sql_statement );
656 while( my ($hit_num, $hit) = each %{ $query_res } ) {
657 my $host_name = $hit->{hostname};
658 if( not $host_name =~ "^$remote_ip") {
659 next;
660 }
661 my $host_key = $hit->{hostkey};
662 daemon_log("$session_id DEBUG: input_from_known_server: host_name: $host_name", 7);
663 daemon_log("$session_id DEBUG: input_from_known_server: host_key: $host_key", 7);
665 # check if module can open msg envelope with module key
666 my ($tmp_msg, $tmp_msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
667 if( (!$tmp_msg) || (!$tmp_msg_hash) ) {
668 daemon_log("$session_id DEBUG: input_from_known_server: deciphering raise error", 7);
669 daemon_log("$@", 8);
670 next;
671 }
672 else {
673 $msg = $tmp_msg;
674 $msg_hash = $tmp_msg_hash;
675 $module = "ServerPackages";
676 daemon_log("$session_id DEBUG: check_key_and_xml_validity... ok", 7);
677 last;
678 }
679 }
681 if( (!$msg) || (!$msg_hash) || (!$module) ) {
682 daemon_log("$session_id DEBUG: Incoming message is not from a known server", 7);
683 }
685 return ($msg, $msg_hash, $module);
686 }
689 sub input_from_known_client {
690 my ($input, $remote_ip, $session_id) = @_ ;
691 my ($msg, $msg_hash, $module);
693 my $sql_statement= "SELECT * FROM known_clients";
694 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
695 while( my ($hit_num, $hit) = each %{ $query_res } ) {
696 my $host_name = $hit->{hostname};
697 if( not $host_name =~ /^$remote_ip:\d*$/) {
698 next;
699 }
700 my $host_key = $hit->{hostkey};
701 &daemon_log("$session_id DEBUG: input_from_known_client: host_name: $host_name", 7);
702 &daemon_log("$session_id DEBUG: input_from_known_client: host_key: $host_key", 7);
704 # check if module can open msg envelope with module key
705 ($msg, $msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
707 if( (!$msg) || (!$msg_hash) ) {
708 &daemon_log("$session_id DEGUG: input_from_known_client: deciphering raise error", 7);
709 &daemon_log("$@", 8);
710 next;
711 }
712 else {
713 $module = "ClientPackages";
714 daemon_log("$session_id DEBUG: check_key_and_xml_validity... ok", 7);
715 last;
716 }
717 }
719 if( (!$msg) || (!$msg_hash) || (!$module) ) {
720 &daemon_log("$session_id DEBUG: Incoming message is not from a known client", 7);
721 }
723 return ($msg, $msg_hash, $module);
724 }
727 sub input_from_unknown_host {
728 no strict "refs";
729 my ($input, $session_id) = @_ ;
730 my ($msg, $msg_hash, $module);
731 my $error_string;
733 my %act_modules = %$known_modules;
735 while( my ($mod, $info) = each(%act_modules)) {
737 # check a key exists for this module
738 my $module_key = ${$mod."_key"};
739 if( not defined $module_key ) {
740 if( $mod eq 'ArpHandler' ) {
741 next;
742 }
743 daemon_log("$session_id ERROR: no key specified in config file for $mod", 1);
744 next;
745 }
746 daemon_log("$session_id DEBUG: $mod: $module_key", 7);
748 # check if module can open msg envelope with module key
749 ($msg, $msg_hash) = &check_key_and_xml_validity($input, $module_key, $session_id);
750 if( (not defined $msg) || (not defined $msg_hash) ) {
751 next;
752 } else {
753 $module = $mod;
754 daemon_log("$session_id DEBUG: check_key_and_xml_validity... ok", 7);
755 last;
756 }
757 }
759 if( (!$msg) || (!$msg_hash) || (!$module)) {
760 daemon_log("$session_id DEBUG: Incoming message is not from an unknown host", 7);
761 }
763 return ($msg, $msg_hash, $module);
764 }
767 sub create_ciphering {
768 my ($passwd) = @_;
769 if((!defined($passwd)) || length($passwd)==0) {
770 $passwd = "";
771 }
772 $passwd = substr(md5_hex("$passwd") x 32, 0, 32);
773 my $iv = substr(md5_hex('GONICUS GmbH'),0, 16);
774 my $my_cipher = Crypt::Rijndael->new($passwd , Crypt::Rijndael::MODE_CBC());
775 $my_cipher->set_iv($iv);
776 return $my_cipher;
777 }
780 sub encrypt_msg {
781 my ($msg, $key) = @_;
782 my $my_cipher = &create_ciphering($key);
783 my $len;
784 {
785 use bytes;
786 $len= 16-length($msg)%16;
787 }
788 $msg = "\0"x($len).$msg;
789 $msg = $my_cipher->encrypt($msg);
790 chomp($msg = &encode_base64($msg));
791 # there are no newlines allowed inside msg
792 $msg=~ s/\n//g;
793 return $msg;
794 }
797 sub decrypt_msg {
799 my ($msg, $key) = @_ ;
800 $msg = &decode_base64($msg);
801 my $my_cipher = &create_ciphering($key);
802 $msg = $my_cipher->decrypt($msg);
803 $msg =~ s/\0*//g;
804 return $msg;
805 }
808 sub get_encrypt_key {
809 my ($target) = @_ ;
810 my $encrypt_key;
811 my $error = 0;
813 # target can be in known_server
814 if( not defined $encrypt_key ) {
815 my $sql_statement= "SELECT * FROM known_server WHERE hostname='$target'";
816 my $query_res = $known_server_db->select_dbentry( $sql_statement );
817 while( my ($hit_num, $hit) = each %{ $query_res } ) {
818 my $host_name = $hit->{hostname};
819 if( $host_name ne $target ) {
820 next;
821 }
822 $encrypt_key = $hit->{hostkey};
823 last;
824 }
825 }
827 # target can be in known_client
828 if( not defined $encrypt_key ) {
829 my $sql_statement= "SELECT * FROM known_clients WHERE hostname='$target'";
830 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
831 while( my ($hit_num, $hit) = each %{ $query_res } ) {
832 my $host_name = $hit->{hostname};
833 if( $host_name ne $target ) {
834 next;
835 }
836 $encrypt_key = $hit->{hostkey};
837 last;
838 }
839 }
841 return $encrypt_key;
842 }
845 #=== FUNCTION ================================================================
846 # NAME: open_socket
847 # PARAMETERS: PeerAddr string something like 192.168.1.1 or 192.168.1.1:10000
848 # [PeerPort] string necessary if port not appended by PeerAddr
849 # RETURNS: socket IO::Socket::INET
850 # DESCRIPTION: open a socket to PeerAddr
851 #===============================================================================
852 sub open_socket {
853 my ($PeerAddr, $PeerPort) = @_ ;
854 if(defined($PeerPort)){
855 $PeerAddr = $PeerAddr.":".$PeerPort;
856 }
857 my $socket;
858 $socket = new IO::Socket::INET(PeerAddr => $PeerAddr,
859 Porto => "tcp",
860 Type => SOCK_STREAM,
861 Timeout => 5,
862 );
863 if(not defined $socket) {
864 return;
865 }
866 # &daemon_log("DEBUG: open_socket: $PeerAddr", 7);
867 return $socket;
868 }
871 #sub get_local_ip_for_remote_ip {
872 # my $remote_ip= shift;
873 # my $result="0.0.0.0";
874 #
875 # if($remote_ip =~ /^(\d\d?\d?\.){3}\d\d?\d?$/) {
876 # if($remote_ip eq "127.0.0.1") {
877 # $result = "127.0.0.1";
878 # } else {
879 # my $PROC_NET_ROUTE= ('/proc/net/route');
880 #
881 # open(PROC_NET_ROUTE, "<$PROC_NET_ROUTE")
882 # or die "Could not open $PROC_NET_ROUTE";
883 #
884 # my @ifs = <PROC_NET_ROUTE>;
885 #
886 # close(PROC_NET_ROUTE);
887 #
888 # # Eat header line
889 # shift @ifs;
890 # chomp @ifs;
891 # foreach my $line(@ifs) {
892 # my ($Iface,$Destination,$Gateway,$Flags,$RefCnt,$Use,$Metric,$Mask,$MTU,$Window,$IRTT)=split(/\s/, $line);
893 # my $destination;
894 # my $mask;
895 # my ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Destination);
896 # $destination= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
897 # ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Mask);
898 # $mask= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
899 # if(new NetAddr::IP($remote_ip)->within(new NetAddr::IP($destination, $mask))) {
900 # # destination matches route, save mac and exit
901 # $result= &get_ip($Iface);
902 # last;
903 # }
904 # }
905 # }
906 # } else {
907 # daemon_log("0 WARNING: get_local_ip_for_remote_ip() was called with a non-ip parameter: '$remote_ip'", 1);
908 # }
909 # return $result;
910 #}
913 sub send_msg_to_target {
914 my ($msg, $address, $encrypt_key, $msg_header, $session_id) = @_ ;
915 my $error = 0;
916 my $header;
917 my $timestamp = &get_time();
918 my $new_status;
919 my $act_status;
920 my ($sql_statement, $res);
922 if( $msg_header ) {
923 $header = "'$msg_header'-";
924 } else {
925 $header = "";
926 }
928 # Patch the source ip
929 if($msg =~ /<source>0\.0\.0\.0:\d*?<\/source>/) {
930 my $remote_ip = &get_local_ip_for_remote_ip(sprintf("%s", $address =~ /^([0-9\.]*?):.*$/));
931 $msg =~ s/<source>(0\.0\.0\.0):(\d*?)<\/source>/<source>$remote_ip:$2<\/source>/s;
932 }
934 # encrypt xml msg
935 my $crypted_msg = &encrypt_msg($msg, $encrypt_key);
937 # opensocket
938 my $socket = &open_socket($address);
939 if( !$socket ) {
940 daemon_log("$session_id WARNING: cannot send ".$header."msg to $address , host not reachable", 3);
941 $error++;
942 }
944 if( $error == 0 ) {
945 # send xml msg
946 print $socket $crypted_msg."\n";
948 daemon_log("$session_id INFO: send ".$header."msg to $address", 5);
949 daemon_log("$session_id DEBUG: message:\n$msg", 9);
951 }
953 # close socket in any case
954 if( $socket ) {
955 close $socket;
956 }
958 if( $error > 0 ) { $new_status = "down"; }
959 else { $new_status = $msg_header; }
962 # known_clients
963 $sql_statement = "SELECT * FROM $known_clients_tn WHERE hostname='$address'";
964 $res = $known_clients_db->select_dbentry($sql_statement);
965 if( keys(%$res) == 1) {
966 $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
967 if ($act_status eq "down" && $new_status eq "down") {
968 $sql_statement = "DELETE FROM known_clients WHERE hostname='$address'";
969 $res = $known_clients_db->del_dbentry($sql_statement);
970 daemon_log("$session_id WARNING: failed 2x to send msg to host '$address', delete host from known_clients", 3);
971 } else {
972 $sql_statement = "UPDATE known_clients SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
973 $res = $known_clients_db->update_dbentry($sql_statement);
974 if($new_status eq "down"){
975 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
976 } else {
977 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
978 }
979 }
980 }
982 # known_server
983 $sql_statement = "SELECT * FROM $known_server_tn WHERE hostname='$address'";
984 $res = $known_server_db->select_dbentry($sql_statement);
985 if( keys(%$res) == 1) {
986 $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
987 if ($act_status eq "down" && $new_status eq "down") {
988 $sql_statement = "DELETE FROM known_server WHERE hostname='$address'";
989 $res = $known_server_db->del_dbentry($sql_statement);
990 daemon_log("$session_id WARNING: failed 2x to send a message to host '$address', delete host from known_server", 3);
991 }
992 else {
993 $sql_statement = "UPDATE known_server SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
994 $res = $known_server_db->update_dbentry($sql_statement);
995 if($new_status eq "down"){
996 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
997 } else {
998 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
999 }
1000 }
1001 }
1002 return $error;
1003 }
1006 sub update_jobdb_status_for_send_msgs {
1007 my ($answer, $error) = @_;
1008 if( $answer =~ /<jobdb_id>(\d+)<\/jobdb_id>/ ) {
1009 my $jobdb_id = $1;
1011 # sending msg faild
1012 if( $error ) {
1013 if (not $answer =~ /<header>trigger_action_reinstall<\/header>/) {
1014 my $sql_statement = "UPDATE $job_queue_tn ".
1015 "SET status='error', result='can not deliver msg, please consult log file' ".
1016 "WHERE id=$jobdb_id";
1017 my $res = $job_db->update_dbentry($sql_statement);
1018 }
1020 # sending msg was successful
1021 } else {
1022 my $sql_statement = "UPDATE $job_queue_tn ".
1023 "SET status='done' ".
1024 "WHERE id=$jobdb_id AND status='processed'";
1025 my $res = $job_db->update_dbentry($sql_statement);
1026 }
1027 }
1028 }
1031 sub sig_handler {
1032 my ($kernel, $signal) = @_[KERNEL, ARG0] ;
1033 daemon_log("0 INFO got signal '$signal'", 1);
1034 $kernel->sig_handled();
1035 return;
1036 }
1039 sub msg_to_decrypt {
1040 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
1041 my $session_id = $session->ID;
1042 my ($msg, $msg_hash, $module);
1043 my $error = 0;
1045 # hole neue msg aus @msgs_to_decrypt
1046 my $next_msg = shift @msgs_to_decrypt;
1048 # msg is from a new client or gosa
1049 ($msg, $msg_hash, $module) = &input_from_unknown_host($next_msg, $session_id);
1051 # msg is from a gosa-si-server
1052 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1053 ($msg, $msg_hash, $module) = &input_from_known_server($next_msg, $heap->{'remote_ip'}, $session_id);
1054 }
1055 # msg is from a gosa-si-client
1056 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1057 ($msg, $msg_hash, $module) = &input_from_known_client($next_msg, $heap->{'remote_ip'}, $session_id);
1058 }
1059 # an error occurred
1060 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1061 # if an incoming msg could not be decrypted (maybe a wrong key), send client a ping. If the client
1062 # could not understand a msg from its server the client cause a re-registering process
1063 daemon_log("$session_id WARNING cannot understand incoming msg, send 'ping'-msg to all host with ip '".$heap->{remote_ip}.
1064 "' to cause a re-registering of the client if necessary", 3);
1065 my $sql_statement = "SELECT * FROM $main::known_clients_tn WHERE (hostname LIKE '".$heap->{'remote_ip'}."%')";
1066 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1067 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1068 my $host_name = $hit->{'hostname'};
1069 my $host_key = $hit->{'hostkey'};
1070 my $ping_msg = "<xml> <header>gosa_ping</header> <source>$server_address</source> <target>$host_name</target></xml>";
1071 my $error = &send_msg_to_target($ping_msg, $host_name, $host_key, "gosa_ping", $session_id);
1072 &update_jobdb_status_for_send_msgs($ping_msg, $error);
1073 }
1074 $error++;
1075 }
1078 my $header;
1079 my $target;
1080 my $source;
1081 my $done = 0;
1082 my $sql;
1083 my $res;
1085 # check whether this message should be processed here
1086 if ($error == 0) {
1087 $header = @{$msg_hash->{'header'}}[0];
1088 $target = @{$msg_hash->{'target'}}[0];
1089 $source = @{$msg_hash->{'source'}}[0];
1090 my $not_found_in_known_clients_db = 0;
1091 my $not_found_in_known_server_db = 0;
1092 my $not_found_in_foreign_clients_db = 0;
1093 my $local_address;
1094 my $local_mac;
1095 my ($target_ip, $target_port) = split(':', $target);
1097 # Determine the local ip address if target is an ip address
1098 if ($target =~ /^\d+\.\d+\.\d+\.\d+:\d+$/) {
1099 $local_address = &get_local_ip_for_remote_ip($target_ip).":$server_port";
1100 } else {
1101 $local_address = $server_address;
1102 }
1104 # Determine the local mac address if target is a mac address
1105 if ($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) {
1106 my $loc_ip = &get_local_ip_for_remote_ip($heap->{'remote_ip'});
1107 my $network_interface= &get_interface_for_ip($loc_ip);
1108 $local_mac = &get_mac_for_interface($network_interface);
1109 } else {
1110 $local_mac = $server_mac_address;
1111 }
1113 # target and source is equal to GOSA -> process here
1114 if (not $done) {
1115 if ($target eq "GOSA" && $source eq "GOSA") {
1116 $done = 1;
1117 &daemon_log("$session_id DEBUG: target and source is 'GOSA' -> process here", 7);
1118 }
1119 }
1121 # target is own address without forward_to_gosa-tag -> process here
1122 if (not $done) {
1123 #if ((($target eq $local_address) || ($target eq $local_mac) ) && (not exists $msg_hash->{'forward_to_gosa'})) {
1124 if (($target eq $local_address) && (not exists $msg_hash->{'forward_to_gosa'})) {
1125 $done = 1;
1126 if ($source eq "GOSA") {
1127 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1128 }
1129 &daemon_log("$session_id DEBUG: target is own address without forward_to_gosa-tag -> process here", 7);
1130 }
1131 }
1133 # target is a client address in known_clients -> process here
1134 if (not $done) {
1135 $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1136 $res = $known_clients_db->select_dbentry($sql);
1137 if (keys(%$res) > 0) {
1138 $done = 1;
1139 my $hostname = $res->{1}->{'hostname'};
1140 $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1141 my $local_address = &get_local_ip_for_remote_ip($target_ip).":$server_port";
1142 if ($source eq "GOSA") {
1143 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1144 }
1145 &daemon_log("$session_id DEBUG: target is a client address in known_clients -> process here", 7);
1147 } else {
1148 $not_found_in_known_clients_db = 1;
1149 }
1150 }
1152 # target ist own address with forward_to_gosa-tag not pointing to myself -> process here
1153 if (not $done) {
1154 my $forward_to_gosa = @{$msg_hash->{'forward_to_gosa'}}[0];
1155 my $gosa_at;
1156 my $gosa_session_id;
1157 if (($target eq $local_address) && (defined $forward_to_gosa)){
1158 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1159 if ($gosa_at ne $local_address) {
1160 $done = 1;
1161 &daemon_log("$session_id DEBUG: target is own address with forward_to_gosa-tag not pointing to myself -> process here", 7);
1162 }
1163 }
1164 }
1166 # if message should be processed here -> add message to incoming_db
1167 if ($done) {
1168 # if a job or a gosa message comes from a foreign server, fake module to GosaPackages
1169 # so gosa-si-server knows how to process this kind of messages
1170 if ($header =~ /^gosa_/ || $header =~ /^job_/) {
1171 $module = "GosaPackages";
1172 }
1174 my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1175 primkey=>[],
1176 headertag=>$header,
1177 targettag=>$target,
1178 xmlmessage=>&encode_base64($msg),
1179 timestamp=>&get_time,
1180 module=>$module,
1181 sessionid=>$session_id,
1182 } );
1184 }
1186 # target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa
1187 if (not $done) {
1188 my $forward_to_gosa = @{$msg_hash->{'forward_to_gosa'}}[0];
1189 my $gosa_at;
1190 my $gosa_session_id;
1191 if (($target eq $local_address) && (defined $forward_to_gosa)){
1192 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1193 if ($gosa_at eq $local_address) {
1194 my $session_reference = $kernel->ID_id_to_session($gosa_session_id);
1195 if( defined $session_reference ) {
1196 $heap = $session_reference->get_heap();
1197 }
1198 if(exists $heap->{'client'}) {
1199 $msg = &encrypt_msg($msg, $GosaPackages_key);
1200 $heap->{'client'}->put($msg);
1201 &daemon_log("$session_id INFO: incoming '$header' message forwarded to GOsa", 5);
1202 }
1203 $done = 1;
1204 &daemon_log("$session_id DEBUG: target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa", 7);
1205 }
1206 }
1208 }
1210 # target is a client address in foreign_clients -> forward to registration server
1211 if (not $done) {
1212 $sql = "SELECT * FROM $foreign_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1213 $res = $foreign_clients_db->select_dbentry($sql);
1214 if (keys(%$res) > 0) {
1215 my $hostname = $res->{1}->{'hostname'};
1216 my ($host_ip, $host_port) = split(/:/, $hostname);
1217 my $local_address = &get_local_ip_for_remote_ip($host_ip).":$server_port";
1218 my $regserver = $res->{1}->{'regserver'};
1219 my $sql = "SELECT * FROM $known_server_tn WHERE hostname='$regserver'";
1220 my $res = $known_server_db->select_dbentry($sql);
1221 if (keys(%$res) > 0) {
1222 my $regserver_key = $res->{1}->{'hostkey'};
1223 $msg =~ s/<source>GOSA<\/source>/<source>$local_address<\/source>/;
1224 $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1225 if ($source eq "GOSA") {
1226 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1227 }
1228 &send_msg_to_target($msg, $regserver, $regserver_key, $header, $session_id);
1229 }
1230 $done = 1;
1231 &daemon_log("$session_id DEBUG: target is a client address in foreign_clients -> forward to registration server", 7);
1232 } else {
1233 $not_found_in_foreign_clients_db = 1;
1234 }
1235 }
1237 # target is a server address -> forward to server
1238 if (not $done) {
1239 $sql = "SELECT * FROM $known_server_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1240 $res = $known_server_db->select_dbentry($sql);
1241 if (keys(%$res) > 0) {
1242 my $hostkey = $res->{1}->{'hostkey'};
1244 if ($source eq "GOSA") {
1245 $msg =~ s/<source>GOSA<\/source>/<source>$local_address<\/source>/;
1246 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1248 }
1250 &send_msg_to_target($msg, $target, $hostkey, $header, $session_id);
1251 $done = 1;
1252 &daemon_log("$session_id DEBUG: target is a server address -> forward to server", 7);
1253 } else {
1254 $not_found_in_known_server_db = 1;
1255 }
1256 }
1259 # target is not in foreign_clients_db, known_server_db or known_clients_db, maybe it is a complete new one -> process here
1260 if ( $not_found_in_foreign_clients_db
1261 && $not_found_in_known_server_db
1262 && $not_found_in_known_clients_db) {
1263 &daemon_log("$session_id DEBUG: target is not in foreign_clients_db, known_server_db or known_clients_db, maybe it is a complete new one -> process here", 7);
1264 if ($header =~ /^gosa_/ || $header =~ /^job_/) {
1265 $module = "GosaPackages";
1266 }
1267 my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1268 primkey=>[],
1269 headertag=>$header,
1270 targettag=>$target,
1271 xmlmessage=>&encode_base64($msg),
1272 timestamp=>&get_time,
1273 module=>$module,
1274 sessionid=>$session_id,
1275 } );
1276 $done = 1;
1277 }
1280 if (not $done) {
1281 daemon_log("$session_id ERROR: do not know what to do with this message: $msg", 1);
1282 if ($source eq "GOSA") {
1283 my %data = ('error_msg' => &encode_base64($msg), 'error_string' => "Do not know what to do with this message!");
1284 my $error_msg = &build_msg("error", $local_address, "GOSA", \%data );
1286 my $session_reference = $kernel->ID_id_to_session($session_id);
1287 if( defined $session_reference ) {
1288 $heap = $session_reference->get_heap();
1289 }
1290 if(exists $heap->{'client'}) {
1291 $error_msg = &encrypt_msg($error_msg, $GosaPackages_key);
1292 $heap->{'client'}->put($error_msg);
1293 }
1294 }
1295 }
1297 }
1299 return;
1300 }
1303 sub next_task {
1304 my ($session, $heap, $task) = @_[SESSION, HEAP, ARG0];
1305 my $running_task = POE::Wheel::Run->new(
1306 Program => sub { process_task($session, $heap, $task) },
1307 StdioFilter => POE::Filter::Reference->new(),
1308 StdoutEvent => "task_result",
1309 StderrEvent => "task_debug",
1310 CloseEvent => "task_done",
1311 );
1312 $heap->{task}->{ $running_task->ID } = $running_task;
1313 }
1315 sub handle_task_result {
1316 my ($kernel, $heap, $result) = @_[KERNEL, HEAP, ARG0];
1317 my $client_answer = $result->{'answer'};
1318 if( $client_answer =~ s/session_id=(\d+)$// ) {
1319 my $session_id = $1;
1320 if( defined $session_id ) {
1321 my $session_reference = $kernel->ID_id_to_session($session_id);
1322 if( defined $session_reference ) {
1323 $heap = $session_reference->get_heap();
1324 }
1325 }
1327 if(exists $heap->{'client'}) {
1328 $heap->{'client'}->put($client_answer);
1329 }
1330 }
1331 $kernel->sig(CHLD => "child_reap");
1332 }
1334 sub handle_task_debug {
1335 my $result = $_[ARG0];
1336 print STDERR "$result\n";
1337 }
1339 sub handle_task_done {
1340 my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1341 delete $heap->{task}->{$task_id};
1342 }
1344 sub process_task {
1345 no strict "refs";
1346 #CHECK: Not @_[...]?
1347 my ($session, $heap, $task) = @_;
1348 my $error = 0;
1349 my $answer_l;
1350 my ($answer_header, @answer_target_l, $answer_source);
1351 my $client_answer = "";
1353 # prepare all variables needed to process message
1354 #my $msg = $task->{'xmlmessage'};
1355 my $msg = &decode_base64($task->{'xmlmessage'});
1356 my $incoming_id = $task->{'id'};
1357 my $module = $task->{'module'};
1358 my $header = $task->{'headertag'};
1359 my $session_id = $task->{'sessionid'};
1360 my $msg_hash;
1361 eval {
1362 $msg_hash = $xml->XMLin($msg, ForceArray=>1);
1363 };
1364 daemon_log("ERROR: XML failure '$@'") if ($@);
1365 my $source = @{$msg_hash->{'source'}}[0];
1367 # set timestamp of incoming client uptodate, so client will not
1368 # be deleted from known_clients because of expiration
1369 my $act_time = &get_time();
1370 my $sql = "UPDATE $known_clients_tn SET timestamp='$act_time' WHERE hostname='$source'";
1371 my $res = $known_clients_db->exec_statement($sql);
1373 ######################
1374 # process incoming msg
1375 if( $error == 0) {
1376 daemon_log("$session_id INFO: Incoming msg (session_id=$session_id) with header '".@{$msg_hash->{'header'}}[0]."'", 5);
1377 daemon_log("$session_id DEBUG: Processing module ".$module, 7);
1378 $answer_l = &{ $module."::process_incoming_msg" }($msg, $msg_hash, $session_id);
1380 if ( 0 < @{$answer_l} ) {
1381 my $answer_str = join("\n", @{$answer_l});
1382 while ($answer_str =~ /<header>(\w+)<\/header>/g) {
1383 daemon_log("$session_id INFO: got answer message with header '$1'", 5);
1384 }
1385 daemon_log("$session_id DEBUG: $module: got answer from module: \n".$answer_str,9);
1386 } else {
1387 daemon_log("$session_id DEBUG: $module: got no answer from module!" ,7);
1388 }
1390 }
1391 if( !$answer_l ) { $error++ };
1393 ########
1394 # answer
1395 if( $error == 0 ) {
1397 foreach my $answer ( @{$answer_l} ) {
1398 # check outgoing msg to xml validity
1399 my $answer_hash = &check_outgoing_xml_validity($answer, $session_id);
1400 if( not defined $answer_hash ) { next; }
1402 $answer_header = @{$answer_hash->{'header'}}[0];
1403 @answer_target_l = @{$answer_hash->{'target'}};
1404 $answer_source = @{$answer_hash->{'source'}}[0];
1406 # deliver msg to all targets
1407 foreach my $answer_target ( @answer_target_l ) {
1409 # targets of msg are all gosa-si-clients in known_clients_db
1410 if( $answer_target eq "*" ) {
1411 # answer is for all clients
1412 my $sql_statement= "SELECT * FROM known_clients";
1413 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1414 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1415 my $host_name = $hit->{hostname};
1416 my $host_key = $hit->{hostkey};
1417 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1418 &update_jobdb_status_for_send_msgs($answer, $error);
1419 }
1420 }
1422 # targets of msg are all gosa-si-server in known_server_db
1423 elsif( $answer_target eq "KNOWN_SERVER" ) {
1424 # answer is for all server in known_server
1425 my $sql_statement= "SELECT * FROM $known_server_tn";
1426 my $query_res = $known_server_db->select_dbentry( $sql_statement );
1427 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1428 my $host_name = $hit->{hostname};
1429 my $host_key = $hit->{hostkey};
1430 $answer =~ s/<target>\S+<\/target>/<target>$host_name<\/target>/g;
1431 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1432 &update_jobdb_status_for_send_msgs($answer, $error);
1433 }
1434 }
1436 # target of msg is GOsa
1437 elsif( $answer_target eq "GOSA" ) {
1438 my $session_id = ($1) if $answer =~ /<session_id>(\d+?)<\/session_id>/;
1439 my $add_on = "";
1440 if( defined $session_id ) {
1441 $add_on = ".session_id=$session_id";
1442 }
1443 # answer is for GOSA and has to returned to connected client
1444 my $gosa_answer = &encrypt_msg($answer, $GosaPackages_key);
1445 $client_answer = $gosa_answer.$add_on;
1446 }
1448 # target of msg is job queue at this host
1449 elsif( $answer_target eq "JOBDB") {
1450 $answer =~ /<header>(\S+)<\/header>/;
1451 my $header;
1452 if( defined $1 ) { $header = $1; }
1453 my $error = &send_msg_to_target($answer, $server_address, $GosaPackages_key, $header, $session_id);
1454 &update_jobdb_status_for_send_msgs($answer, $error);
1455 }
1457 # Target of msg is a mac address
1458 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 ) {
1459 daemon_log("$session_id INFO: target is mac address '$answer_target', looking for host in known_clients and foreign_clients", 5);
1460 my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$answer_target'";
1461 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1462 my $found_ip_flag = 0;
1463 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1464 my $host_name = $hit->{hostname};
1465 my $host_key = $hit->{hostkey};
1466 $answer =~ s/$answer_target/$host_name/g;
1467 daemon_log("$session_id INFO: found host '$host_name', associated to '$answer_target'", 5);
1468 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1469 &update_jobdb_status_for_send_msgs($answer, $error);
1470 $found_ip_flag++ ;
1471 }
1472 if ($found_ip_flag == 0) {
1473 my $sql = "SELECT * FROM $foreign_clients_tn WHERE macaddress LIKE '$answer_target'";
1474 my $res = $foreign_clients_db->select_dbentry($sql);
1475 while( my ($hit_num, $hit) = each %{ $res } ) {
1476 my $host_name = $hit->{hostname};
1477 my $reg_server = $hit->{regserver};
1478 daemon_log("$session_id INFO: found host '$host_name' with mac '$answer_target', registered at '$reg_server'", 5);
1480 # Fetch key for reg_server
1481 my $reg_server_key;
1482 my $sql = "SELECT * FROM $known_server_tn WHERE hostname='$reg_server'";
1483 my $res = $known_server_db->select_dbentry($sql);
1484 if (exists $res->{1}) {
1485 $reg_server_key = $res->{1}->{'hostkey'};
1486 } else {
1487 daemon_log("$session_id ERROR: cannot find hostkey for '$host_name' in '$known_server_tn'", 1);
1488 daemon_log("$session_id ERROR: unable to forward answer to correct registration server, processing is aborted!", 1);
1489 $reg_server_key = undef;
1490 }
1492 # Send answer to server where client is registered
1493 if (defined $reg_server_key) {
1494 $answer =~ s/$answer_target/$host_name/g;
1495 my $error = &send_msg_to_target($answer, $reg_server, $reg_server_key, $answer_header, $session_id);
1496 &update_jobdb_status_for_send_msgs($answer, $error);
1497 $found_ip_flag++ ;
1498 }
1499 }
1500 }
1501 if( $found_ip_flag == 0) {
1502 daemon_log("$session_id WARNING: no host found in known_clients or foreign_clients with mac address '$answer_target'", 3);
1504 # Sometimes the client is still booting or does not wake up, in this case reactivate the job (if it exists) with a delay of 30 sec
1505 my $delay_timestamp = &calc_timestamp(&get_time(), "plus", 30);
1506 my $sql = "UPDATE $job_queue_tn Set timestamp='$delay_timestamp', status='waiting' WHERE (macaddress='$answer_target' AND headertag='$answer_header')";
1507 my $res = $job_db->update_dbentry($sql);
1508 daemon_log("$session_id INFO: '$answer_header'-job will be reactivated at '$delay_timestamp' ".
1509 "cause client '$answer_target' is currently not available", 5);
1510 daemon_log("$session_id $sql", 7);
1511 }
1513 # Answer is for one specific host
1514 } else {
1515 # get encrypt_key
1516 my $encrypt_key = &get_encrypt_key($answer_target);
1517 if( not defined $encrypt_key ) {
1518 # unknown target
1519 daemon_log("$session_id WARNING: unknown target '$answer_target'", 3);
1520 next;
1521 }
1522 my $error = &send_msg_to_target($answer, $answer_target, $encrypt_key, $answer_header,$session_id);
1523 &update_jobdb_status_for_send_msgs($answer, $error);
1524 }
1525 }
1526 }
1527 }
1529 my $filter = POE::Filter::Reference->new();
1530 my %result = (
1531 status => "seems ok to me",
1532 answer => $client_answer,
1533 );
1535 my $output = $filter->put( [ \%result ] );
1536 print @$output;
1539 }
1541 sub session_start {
1542 my ($kernel) = $_[KERNEL];
1543 $global_kernel = $kernel;
1544 $kernel->yield('register_at_foreign_servers');
1545 $kernel->yield('create_fai_server_db', $fai_server_tn );
1546 $kernel->yield('create_fai_release_db', $fai_release_tn );
1547 $kernel->yield('watch_for_next_tasks');
1548 $kernel->sig(USR1 => "sig_handler");
1549 $kernel->sig(USR2 => "recreate_packages_db");
1550 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1551 $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay);
1552 $kernel->delay_set('watch_for_modified_jobs', $modified_jobs_loop_delay);
1553 $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1554 $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1555 $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1556 $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1558 # Start opsi check
1559 if ($opsi_enabled eq "true") {
1560 $kernel->delay_set('watch_for_opsi_jobs', $job_queue_opsi_delay);
1561 }
1563 }
1566 sub watch_for_done_jobs {
1567 #CHECK: $heap for what?
1568 my ($kernel,$heap) = @_[KERNEL, HEAP];
1570 my $sql_statement = "SELECT * FROM ".$job_queue_tn." WHERE ((status='done') AND (modified='0'))";
1571 my $res = $job_db->select_dbentry( $sql_statement );
1573 while( my ($id, $hit) = each %{$res} ) {
1574 my $jobdb_id = $hit->{id};
1575 my $sql_statement = "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1576 my $res = $job_db->del_dbentry($sql_statement);
1577 }
1579 $kernel->delay_set('watch_for_done_jobs',$job_queue_loop_delay);
1580 }
1583 sub watch_for_opsi_jobs {
1584 my ($kernel) = $_[KERNEL];
1586 # This is not very nice to look for opsi install jobs, but headertag has to be trigger_action_reinstall. The only way to identify a
1587 # opsi install job is to parse the xml message. There is still the correct header.
1588 my $sql_statement = "SELECT * FROM ".$job_queue_tn." WHERE ((xmlmessage LIKE '%opsi_install_client</header>%') AND (status='processing') AND (siserver='localhost'))";
1589 my $res = $job_db->select_dbentry( $sql_statement );
1591 # Ask OPSI for an update of the running jobs
1592 while (my ($id, $hit) = each %$res ) {
1593 # Determine current parameters of the job
1594 my $hostId = $hit->{'plainname'};
1595 my $macaddress = $hit->{'macaddress'};
1596 my $progress = $hit->{'progress'};
1598 my $result= {};
1600 # For hosts, only return the products that are or get installed
1601 my $callobj;
1602 $callobj = {
1603 method => 'getProductStates_hash',
1604 params => [ $hostId ],
1605 id => 1,
1606 };
1608 my $hres = $opsi_client->call($opsi_url, $callobj);
1609 #my ($hres_err, $hres_err_string) = &check_opsi_res($hres);
1610 if (not &check_opsi_res($hres)) {
1611 my $htmp= $hres->result->{$hostId};
1613 # Check state != not_installed or action == setup -> load and add
1614 my $products= 0;
1615 my $installed= 0;
1616 my $installing = 0;
1617 my $error= 0;
1618 my @installed_list;
1619 my @error_list;
1620 my $act_status = "none";
1621 foreach my $product (@{$htmp}){
1623 if ($product->{'installationStatus'} ne "not_installed" or
1624 $product->{'actionRequest'} eq "setup"){
1626 # Increase number of products for this host
1627 $products++;
1629 if ($product->{'installationStatus'} eq "failed"){
1630 $result->{$product->{'productId'}}= "error";
1631 unshift(@error_list, $product->{'productId'});
1632 $error++;
1633 }
1634 if ($product->{'installationStatus'} eq "installed" && $product->{'actionRequest'} eq "none"){
1635 $result->{$product->{'productId'}}= "installed";
1636 unshift(@installed_list, $product->{'productId'});
1637 $installed++;
1638 }
1639 if ($product->{'installationStatus'} eq "installing"){
1640 $result->{$product->{'productId'}}= "installing";
1641 $installing++;
1642 $act_status = "installing - ".$product->{'productId'};
1643 }
1644 }
1645 }
1647 # Estimate "rough" progress, avoid division by zero
1648 if ($products == 0) {
1649 $result->{'progress'}= 0;
1650 } else {
1651 $result->{'progress'}= int($installed * 100 / $products);
1652 }
1654 # Set updates in job queue
1655 if ((not $error) && (not $installing) && ($installed)) {
1656 $act_status = "installed - ".join(", ", @installed_list);
1657 }
1658 if ($error) {
1659 $act_status = "error - ".join(", ", @error_list);
1660 }
1661 if ($progress ne $result->{'progress'} ) {
1662 # Updating progress and result
1663 my $update_statement = "UPDATE $job_queue_tn SET modified='1', progress='".$result->{'progress'}."', result='$act_status' WHERE macaddress='$macaddress' AND siserver='localhost'";
1664 my $update_res = $job_db->update_dbentry($update_statement);
1665 }
1666 if ($progress eq 100) {
1667 # Updateing status
1668 my $done_statement = "UPDATE $job_queue_tn SET modified='1', ";
1669 if ($error) {
1670 $done_statement .= "status='error'";
1671 } else {
1672 $done_statement .= "status='done'";
1673 }
1674 $done_statement .= " WHERE macaddress='$macaddress' AND siserver='localhost'";
1675 my $done_res = $job_db->update_dbentry($done_statement);
1676 }
1679 }
1680 }
1682 $kernel->delay_set('watch_for_opsi_jobs', $job_queue_opsi_delay);
1683 }
1686 # If a job got an update or was modified anyway, send to all other si-server an update message of this jobs.
1687 sub watch_for_modified_jobs {
1688 my ($kernel,$heap) = @_[KERNEL, HEAP];
1690 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE (modified='1')";
1691 my $res = $job_db->select_dbentry( $sql_statement );
1693 # if db contains no jobs which should be update, do nothing
1694 if (keys %$res != 0) {
1696 if ($job_synchronization eq "true") {
1697 # make out of the db result a gosa-si message
1698 my $update_msg = &db_res2si_msg ($res, "foreign_job_updates", "KNOWN_SERVER", "MY_LOCAL_ADDRESS");
1700 # update all other SI-server
1701 &inform_all_other_si_server($update_msg);
1702 }
1704 # set jobs all jobs to modified = 0, wait until the next modification for updates of other si-server
1705 $sql_statement = "UPDATE $job_queue_tn SET modified='0' ";
1706 $res = $job_db->update_dbentry($sql_statement);
1707 }
1709 $kernel->delay_set('watch_for_modified_jobs', $modified_jobs_loop_delay);
1710 }
1713 sub watch_for_new_jobs {
1714 if($watch_for_new_jobs_in_progress == 0) {
1715 $watch_for_new_jobs_in_progress = 1;
1716 my ($kernel,$heap) = @_[KERNEL, HEAP];
1718 # check gosa job quaeue for jobs with executable timestamp
1719 my $timestamp = &get_time();
1720 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE status='waiting' AND (CAST(timestamp AS UNSIGNED)) < $timestamp ORDER BY timestamp";
1721 my $res = $job_db->exec_statement( $sql_statement );
1723 # Merge all new jobs that would do the same actions
1724 my @drops;
1725 my $hits;
1726 foreach my $hit (reverse @{$res} ) {
1727 my $macaddress= lc @{$hit}[8];
1728 my $headertag= @{$hit}[5];
1729 if(
1730 defined($hits->{$macaddress}) &&
1731 defined($hits->{$macaddress}->{$headertag}) &&
1732 defined($hits->{$macaddress}->{$headertag}[0])
1733 ) {
1734 push @drops, "DELETE FROM $job_queue_tn WHERE id = $hits->{$macaddress}->{$headertag}[0]";
1735 }
1736 $hits->{$macaddress}->{$headertag}= $hit;
1737 }
1739 # Delete new jobs with a matching job in state 'processing'
1740 foreach my $macaddress (keys %{$hits}) {
1741 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1742 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1743 if(defined($jobdb_id)) {
1744 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND headertag='$jobdb_headertag' AND status='processing'";
1745 my $res = $job_db->exec_statement( $sql_statement );
1746 foreach my $hit (@{$res}) {
1747 push @drops, "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1748 }
1749 } else {
1750 daemon_log("J ERROR: Job without id exists for macaddress $macaddress!", 1);
1751 }
1752 }
1753 }
1755 # Commit deletion
1756 $job_db->exec_statementlist(\@drops);
1758 # Look for new jobs that could be executed
1759 foreach my $macaddress (keys %{$hits}) {
1761 # Look if there is an executing job
1762 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='processing'";
1763 my $res = $job_db->exec_statement( $sql_statement );
1765 # Skip new jobs for host if there is a processing job
1766 if(defined($res) and defined @{$res}[0]) {
1767 # Prevent race condition if there is a trigger_activate job waiting and a goto-activation job processing
1768 my $row = @{$res}[0] if (ref $res eq 'ARRAY');
1769 if(@{$row}[5] eq 'trigger_action_reinstall') {
1770 my $sql_statement_2 = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='waiting' AND headertag = 'trigger_activate_new'";
1771 my $res_2 = $job_db->exec_statement( $sql_statement_2 );
1772 if(defined($res_2) and defined @{$res_2}[0]) {
1773 # Set status from goto-activation to 'waiting' and update timestamp
1774 $job_db->exec_statement("UPDATE $job_queue_tn SET status='waiting' WHERE macaddress LIKE '$macaddress' AND headertag = 'trigger_action_reinstall'");
1775 $job_db->exec_statement("UPDATE $job_queue_tn SET timestamp='".&get_time(30)."' WHERE macaddress LIKE '$macaddress' AND headertag = 'trigger_action_reinstall'");
1776 }
1777 }
1778 next;
1779 }
1781 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1782 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1783 if(defined($jobdb_id)) {
1784 my $job_msg = @{$hits->{$macaddress}->{$jobdb_headertag}}[7];
1786 daemon_log("J DEBUG: its time to execute $job_msg", 7);
1787 my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$macaddress'";
1788 my $res_hash = $known_clients_db->select_dbentry( $sql_statement );
1790 # expect macaddress is unique!!!!!!
1791 my $target = $res_hash->{1}->{hostname};
1793 # change header
1794 $job_msg =~ s/<header>job_/<header>gosa_/;
1796 # add sqlite_id
1797 $job_msg =~ s/<\/xml>$/<jobdb_id>$jobdb_id<\/jobdb_id><\/xml>/;
1799 $job_msg =~ /<header>(\S+)<\/header>/;
1800 my $header = $1 ;
1801 my $func_error = &send_msg_to_target($job_msg, $server_address, $GosaPackages_key, $header, "J");
1803 # update status in job queue to ...
1804 # ... 'processing', for jobs: 'reinstall', 'update'
1805 if (($header =~ /gosa_trigger_action_reinstall/) || ($header =~ /gosa_trigger_action_update/)) {
1806 my $sql_statement = "UPDATE $job_queue_tn SET status='processing' WHERE id=$jobdb_id";
1807 my $dbres = $job_db->update_dbentry($sql_statement);
1808 }
1810 # ... 'done', for all other jobs, they are no longer needed in the jobqueue
1811 else {
1812 my $sql_statement = "UPDATE $job_queue_tn SET status='done' WHERE id=$jobdb_id";
1813 my $dbres = $job_db->update_dbentry($sql_statement);
1814 }
1817 # We don't want parallel processing
1818 last;
1819 }
1820 }
1821 }
1823 $watch_for_new_jobs_in_progress = 0;
1824 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1825 }
1826 }
1829 sub watch_for_new_messages {
1830 my ($kernel,$heap) = @_[KERNEL, HEAP];
1831 my @coll_user_msg; # collection list of outgoing messages
1833 # check messaging_db for new incoming messages with executable timestamp
1834 my $timestamp = &get_time();
1835 my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( (CAST(timestamp AS UNSIGNED))<$timestamp AND flag='n' AND direction='in' )";
1836 my $res = $messaging_db->exec_statement( $sql_statement );
1837 foreach my $hit (@{$res}) {
1839 # create outgoing messages
1840 my $message_to = @{$hit}[3];
1841 # translate message_to to plain login name
1842 my @message_to_l = split(/,/, $message_to);
1843 my %receiver_h;
1844 foreach my $receiver (@message_to_l) {
1845 if ($receiver =~ /^u_([\s\S]*)$/) {
1846 $receiver_h{$receiver} = 0;
1847 } elsif ($receiver =~ /^g_([\s\S]*)$/) {
1848 my $group_name = $1;
1849 # fetch all group members from ldap and add them to receiver hash
1850 my $ldap_handle = &get_ldap_handle();
1851 if (defined $ldap_handle) {
1852 my $mesg = $ldap_handle->search(
1853 base => $ldap_base,
1854 scope => 'sub',
1855 attrs => ['memberUid'],
1856 filter => "cn=$group_name",
1857 );
1858 if ($mesg->count) {
1859 my @entries = $mesg->entries;
1860 foreach my $entry (@entries) {
1861 my @receivers= $entry->get_value("memberUid");
1862 foreach my $receiver (@receivers) {
1863 $receiver_h{$receiver} = 0;
1864 }
1865 }
1866 }
1867 # translating errors ?
1868 if ($mesg->code) {
1869 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: $mesg->error", 1);
1870 }
1871 # ldap handle error ?
1872 } else {
1873 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: no ldap handle available", 1);
1874 }
1875 } else {
1876 my $sbjct = &encode_base64(@{$hit}[1]);
1877 my $msg = &encode_base64(@{$hit}[7]);
1878 &daemon_log("M WARNING: unknown receiver '$receiver' for a user-message '$sbjct - $msg'", 3);
1879 }
1880 }
1881 my @receiver_l = keys(%receiver_h);
1883 my $message_id = @{$hit}[0];
1885 #add each outgoing msg to messaging_db
1886 my $receiver;
1887 foreach $receiver (@receiver_l) {
1888 my $sql_statement = "INSERT INTO $messaging_tn (id, subject, message_from, message_to, flag, direction, delivery_time, message, timestamp) ".
1889 "VALUES ('".
1890 $message_id."', '". # id
1891 @{$hit}[1]."', '". # subject
1892 @{$hit}[2]."', '". # message_from
1893 $receiver."', '". # message_to
1894 "none"."', '". # flag
1895 "out"."', '". # direction
1896 @{$hit}[6]."', '". # delivery_time
1897 @{$hit}[7]."', '". # message
1898 $timestamp."'". # timestamp
1899 ")";
1900 &daemon_log("M DEBUG: $sql_statement", 1);
1901 my $res = $messaging_db->exec_statement($sql_statement);
1902 &daemon_log("M INFO: message '".@{$hit}[0]."' is prepared for delivery to receiver '$receiver'", 5);
1903 }
1905 # set incoming message to flag d=deliverd
1906 $sql_statement = "UPDATE $messaging_tn SET flag='p' WHERE id='$message_id'";
1907 &daemon_log("M DEBUG: $sql_statement", 7);
1908 $res = $messaging_db->update_dbentry($sql_statement);
1909 &daemon_log("M INFO: message '$message_id' is set to flag 'p' (processed)", 5);
1910 }
1912 $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1913 return;
1914 }
1916 sub watch_for_delivery_messages {
1917 my ($kernel, $heap) = @_[KERNEL, HEAP];
1919 # select outgoing messages
1920 my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( flag='p' AND direction='out' )";
1921 #&daemon_log("0 DEBUG: $sql", 7);
1922 my $res = $messaging_db->exec_statement( $sql_statement );
1924 # build out msg for each usr
1925 foreach my $hit (@{$res}) {
1926 my $receiver = @{$hit}[3];
1927 my $msg_id = @{$hit}[0];
1928 my $subject = @{$hit}[1];
1929 my $message = @{$hit}[7];
1931 # resolve usr -> host where usr is logged in
1932 my $sql = "SELECT * FROM $login_users_tn WHERE (user='$receiver')";
1933 #&daemon_log("0 DEBUG: $sql", 7);
1934 my $res = $login_users_db->exec_statement($sql);
1936 # receiver is logged in nowhere
1937 if (not ref(@$res[0]) eq "ARRAY") { next; }
1939 # receiver ist logged in at a client registered at local server
1940 my $send_succeed = 0;
1941 foreach my $hit (@$res) {
1942 my $receiver_host = @$hit[0];
1943 my $delivered2host = 0;
1944 &daemon_log("M DEBUG: user '$receiver' is logged in at host '$receiver_host'", 7);
1946 # Looking for host in know_clients_db
1947 my $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$receiver_host')";
1948 my $res = $known_clients_db->exec_statement($sql);
1950 # Host is known in known_clients_db
1951 if (ref(@$res[0]) eq "ARRAY") {
1952 my $receiver_key = @{@{$res}[0]}[2];
1953 my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
1954 my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data );
1955 my $error = &send_msg_to_target($out_msg, $receiver_host, $receiver_key, "usr_msg", 0);
1956 if ($error == 0 ) {
1957 $send_succeed++ ;
1958 $delivered2host++ ;
1959 &daemon_log("M DEBUG: send message for user '$receiver' to host '$receiver_host'", 7);
1960 } else {
1961 &daemon_log("M DEBUG: cannot send message for user '$receiver' to host '$receiver_host'", 7);
1962 }
1963 }
1965 # Message already send, do not need to do anything more, otherwise ...
1966 if ($delivered2host) { next;}
1968 # ...looking for host in foreign_clients_db
1969 $sql = "SELECT * FROM $foreign_clients_tn WHERE (hostname='$receiver_host')";
1970 $res = $foreign_clients_db->exec_statement($sql);
1972 # Host is known in foreign_clients_db
1973 if (ref(@$res[0]) eq "ARRAY") {
1974 my $registration_server = @{@{$res}[0]}[2];
1976 # Fetch encryption key for registration server
1977 my $sql = "SELECT * FROM $known_server_tn WHERE (hostname='$registration_server')";
1978 my $res = $known_server_db->exec_statement($sql);
1979 if (ref(@$res[0]) eq "ARRAY") {
1980 my $registration_server_key = @{@{$res}[0]}[3];
1981 my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
1982 my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data );
1983 my $error = &send_msg_to_target($out_msg, $registration_server, $registration_server_key, "usr_msg", 0);
1984 if ($error == 0 ) {
1985 $send_succeed++ ;
1986 $delivered2host++ ;
1987 &daemon_log("M DEBUG: send message for user '$receiver' to server '$registration_server'", 7);
1988 } else {
1989 &daemon_log("M ERROR: cannot send message for user '$receiver' to server '$registration_server'", 1);
1990 }
1992 } else {
1993 &daemon_log("M ERROR: host '$receiver_host' is reported to be ".
1994 "registrated at server '$registration_server', ".
1995 "but no data available in known_server_db ", 1);
1996 }
1997 }
1999 if (not $delivered2host) {
2000 &daemon_log("M ERROR: unable to send user message to host '$receiver_host'", 1);
2001 }
2002 }
2004 if ($send_succeed) {
2005 # set outgoing msg at db to deliverd
2006 my $sql = "UPDATE $messaging_tn SET flag='d' WHERE (id='$msg_id' AND direction='out' AND message_to='$receiver')";
2007 my $res = $messaging_db->exec_statement($sql);
2008 &daemon_log("M INFO: send message for user '$receiver' to logged in hosts", 5);
2009 } else {
2010 &daemon_log("M WARNING: failed to deliver message for user '$receiver'", 3);
2011 }
2012 }
2014 $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
2015 return;
2016 }
2019 sub watch_for_done_messages {
2020 my ($kernel,$heap) = @_[KERNEL, HEAP];
2022 my $sql = "SELECT * FROM $messaging_tn WHERE (flag='p' AND direction='in')";
2023 #&daemon_log("0 DEBUG: $sql", 7);
2024 my $res = $messaging_db->exec_statement($sql);
2026 foreach my $hit (@{$res}) {
2027 my $msg_id = @{$hit}[0];
2029 my $sql = "SELECT * FROM $messaging_tn WHERE (id='$msg_id' AND direction='out' AND (NOT flag='s'))";
2030 #&daemon_log("0 DEBUG: $sql", 7);
2031 my $res = $messaging_db->exec_statement($sql);
2033 # not all usr msgs have been seen till now
2034 if ( ref(@$res[0]) eq "ARRAY") { next; }
2036 $sql = "DELETE FROM $messaging_tn WHERE (id='$msg_id')";
2037 #&daemon_log("0 DEBUG: $sql", 7);
2038 $res = $messaging_db->exec_statement($sql);
2040 }
2042 $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
2043 return;
2044 }
2047 sub watch_for_old_known_clients {
2048 my ($kernel,$heap) = @_[KERNEL, HEAP];
2050 my $sql_statement = "SELECT * FROM $known_clients_tn";
2051 my $res = $known_clients_db->select_dbentry( $sql_statement );
2053 my $act_time = int(&get_time());
2055 while ( my ($hit_num, $hit) = each %$res) {
2056 my $expired_timestamp = int($hit->{'timestamp'});
2057 $expired_timestamp =~ /(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)/;
2058 my $dt = DateTime->new( year => $1,
2059 month => $2,
2060 day => $3,
2061 hour => $4,
2062 minute => $5,
2063 second => $6,
2064 );
2066 $dt->add( seconds => 2 * int($hit->{'keylifetime'}) );
2067 $expired_timestamp = $dt->ymd('').$dt->hms('');
2068 if ($act_time > $expired_timestamp) {
2069 my $hostname = $hit->{'hostname'};
2070 my $del_sql = "DELETE FROM $known_clients_tn WHERE hostname='$hostname'";
2071 my $del_res = $known_clients_db->exec_statement($del_sql);
2073 &main::daemon_log("0 INFO: timestamp '".$hit->{'timestamp'}."' of client '$hostname' is expired('$expired_timestamp'), client will be deleted from known_clients_db", 5);
2074 }
2076 }
2078 $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
2079 }
2082 sub watch_for_next_tasks {
2083 my ($kernel,$heap) = @_[KERNEL, HEAP];
2085 my $sql = "SELECT * FROM $incoming_tn";
2086 my $res = $incoming_db->select_dbentry($sql);
2088 while ( my ($hit_num, $hit) = each %$res) {
2089 my $headertag = $hit->{'headertag'};
2090 if ($headertag =~ /^answer_(\d+)/) {
2091 # do not start processing, this message is for a still running POE::Wheel
2092 next;
2093 }
2094 my $message_id = $hit->{'id'};
2095 my $session_id = $hit->{'sessionid'};
2096 &daemon_log("$session_id DEBUG: start processing for message with incoming id: '$message_id'", 7);
2097 $kernel->yield('next_task', $hit);
2099 my $sql = "DELETE FROM $incoming_tn WHERE id=$message_id";
2100 my $res = $incoming_db->exec_statement($sql);
2101 }
2103 $kernel->delay_set('watch_for_next_tasks', 1);
2104 }
2107 sub get_ldap_handle {
2108 my ($session_id) = @_;
2109 my $heap;
2110 my $ldap_handle;
2112 if (not defined $session_id ) { $session_id = 0 };
2113 if ($session_id =~ /[^0-9]*/) { $session_id = 0 };
2115 if ($session_id == 0) {
2116 daemon_log("$session_id DEBUG: get_ldap_handle invoked without a session_id, create a new ldap_handle", 7);
2117 $ldap_handle = Net::LDAP->new( $ldap_uri );
2118 if (defined $ldap_handle) {
2119 $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!");
2120 } else {
2121 daemon_log("$session_id ERROR: creation of a new LDAP handle failed (ldap_uri '$ldap_uri')");
2122 }
2124 } else {
2125 my $session_reference = $global_kernel->ID_id_to_session($session_id);
2126 if( defined $session_reference ) {
2127 $heap = $session_reference->get_heap();
2128 }
2130 if (not defined $heap) {
2131 daemon_log("$session_id DEBUG: cannot get heap for session_id '$session_id'", 7);
2132 return;
2133 }
2135 # TODO: This "if" is nonsense, because it doesn't prove that the
2136 # used handle is still valid - or if we've to reconnect...
2137 #if (not exists $heap->{ldap_handle}) {
2138 $ldap_handle = Net::LDAP->new( $ldap_uri );
2139 $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!");
2140 $heap->{ldap_handle} = $ldap_handle;
2141 #}
2142 }
2143 return $ldap_handle;
2144 }
2147 sub change_fai_state {
2148 my ($st, $targets, $session_id) = @_;
2149 $session_id = 0 if not defined $session_id;
2150 # Set FAI state to localboot
2151 my %mapActions= (
2152 reboot => '',
2153 update => 'softupdate',
2154 localboot => 'localboot',
2155 reinstall => 'install',
2156 rescan => '',
2157 wake => '',
2158 memcheck => 'memcheck',
2159 sysinfo => 'sysinfo',
2160 install => 'install',
2161 );
2163 # Return if this is unknown
2164 if (!exists $mapActions{ $st }){
2165 daemon_log("$session_id ERROR: unknown action '$st', can not translate ot FAIstate", 1);
2166 return;
2167 }
2169 my $state= $mapActions{ $st };
2171 my $ldap_handle = &get_ldap_handle($session_id);
2172 if( defined($ldap_handle) ) {
2174 # Build search filter for hosts
2175 my $search= "(&(objectClass=GOhard)";
2176 foreach (@{$targets}){
2177 $search.= "(macAddress=$_)";
2178 }
2179 $search.= ")";
2181 # If there's any host inside of the search string, procress them
2182 if (!($search =~ /macAddress/)){
2183 daemon_log("$session_id ERROR: no macAddress found in filter statement for LDAP search: '$search'", 1);
2184 return;
2185 }
2187 # Perform search for Unit Tag
2188 my $mesg = $ldap_handle->search(
2189 base => $ldap_base,
2190 scope => 'sub',
2191 attrs => ['dn', 'FAIstate', 'objectClass'],
2192 filter => "$search"
2193 );
2195 if ($mesg->count) {
2196 my @entries = $mesg->entries;
2197 if (0 == @entries) {
2198 daemon_log("$session_id ERROR: ldap search failed: ldap_base=$ldap_base, filter=$search", 1);
2199 }
2201 foreach my $entry (@entries) {
2202 # Only modify entry if it is not set to '$state'
2203 if ($entry->get_value("FAIstate") ne "$state"){
2204 daemon_log("$session_id INFO: Setting FAIstate to '$state' for ".$entry->dn, 5);
2205 my $result;
2206 my %tmp = map { $_ => 1 } $entry->get_value("objectClass");
2207 if (exists $tmp{'FAIobject'}){
2208 if ($state eq ''){
2209 $result= $ldap_handle->modify($entry->dn, changes => [ delete => [ FAIstate => [] ] ]);
2210 } else {
2211 $result= $ldap_handle->modify($entry->dn, changes => [ replace => [ FAIstate => $state ] ]);
2212 }
2213 } elsif ($state ne ''){
2214 $result= $ldap_handle->modify($entry->dn, changes => [ add => [ objectClass => 'FAIobject' ], add => [ FAIstate => $state ] ]);
2215 }
2217 # Errors?
2218 if ($result->code){
2219 daemon_log("$session_id Error: Setting FAIstate to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
2220 }
2221 } else {
2222 daemon_log("$session_id DEBUG FAIstate at host '".$entry->dn."' already at state '$st'", 7);
2223 }
2224 }
2225 } else {
2226 daemon_log("$session_id ERROR: LDAP search failed: ldap_base=$ldap_base, filter=$search", 1);
2227 }
2229 # if no ldap handle defined
2230 } else {
2231 daemon_log("$session_id ERROR: no LDAP handle defined for update FAIstate", 1);
2232 }
2234 return;
2235 }
2238 sub change_goto_state {
2239 my ($st, $targets, $session_id) = @_;
2240 $session_id = 0 if not defined $session_id;
2242 # Switch on or off?
2243 my $state= $st eq 'active' ? 'active': 'locked';
2245 my $ldap_handle = &get_ldap_handle($session_id);
2246 if( defined($ldap_handle) ) {
2248 # Build search filter for hosts
2249 my $search= "(&(objectClass=GOhard)";
2250 foreach (@{$targets}){
2251 $search.= "(macAddress=$_)";
2252 }
2253 $search.= ")";
2255 # If there's any host inside of the search string, procress them
2256 if (!($search =~ /macAddress/)){
2257 return;
2258 }
2260 # Perform search for Unit Tag
2261 my $mesg = $ldap_handle->search(
2262 base => $ldap_base,
2263 scope => 'sub',
2264 attrs => ['dn', 'gotoMode'],
2265 filter => "$search"
2266 );
2268 if ($mesg->count) {
2269 my @entries = $mesg->entries;
2270 foreach my $entry (@entries) {
2272 # Only modify entry if it is not set to '$state'
2273 if ($entry->get_value("gotoMode") ne $state){
2275 daemon_log("$session_id INFO: Setting gotoMode to '$state' for ".$entry->dn, 5);
2276 my $result;
2277 $result= $ldap_handle->modify($entry->dn, changes => [replace => [ gotoMode => $state ] ]);
2279 # Errors?
2280 if ($result->code){
2281 &daemon_log("$session_id Error: Setting gotoMode to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
2282 }
2284 }
2285 }
2286 } else {
2287 daemon_log("$session_id ERROR: LDAP search failed in function change_goto_state: ldap_base=$ldap_base, filter=$search", 1);
2288 }
2290 }
2291 }
2294 sub run_recreate_packages_db {
2295 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2296 my $session_id = $session->ID;
2297 &main::daemon_log("$session_id INFO: Recreating FAI Packages DB ('$fai_release_tn', '$fai_server_tn', '$packages_list_tn')", 5);
2298 $kernel->yield('create_fai_release_db', $fai_release_tn);
2299 $kernel->yield('create_fai_server_db', $fai_server_tn);
2300 return;
2301 }
2304 sub run_create_fai_server_db {
2305 my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
2306 my $session_id = $session->ID;
2307 my $task = POE::Wheel::Run->new(
2308 Program => sub { &create_fai_server_db($table_name,$kernel, undef, $session_id) },
2309 StdoutEvent => "session_run_result",
2310 StderrEvent => "session_run_debug",
2311 CloseEvent => "session_run_done",
2312 );
2314 $heap->{task}->{ $task->ID } = $task;
2315 return;
2316 }
2319 sub create_fai_server_db {
2320 my ($table_name, $kernel, $dont_create_packages_list, $session_id) = @_;
2321 my $result;
2323 if (not defined $session_id) { $session_id = 0; }
2324 my $ldap_handle = &get_ldap_handle();
2325 if(defined($ldap_handle)) {
2326 daemon_log("$session_id INFO: create_fai_server_db: start", 5);
2327 my $mesg= $ldap_handle->search(
2328 base => $ldap_base,
2329 scope => 'sub',
2330 attrs => ['FAIrepository', 'gosaUnitTag'],
2331 filter => "(&(FAIrepository=*)(objectClass=FAIrepositoryServer))",
2332 );
2333 if($mesg->{'resultCode'} == 0 &&
2334 $mesg->count != 0) {
2335 foreach my $entry (@{$mesg->{entries}}) {
2336 if($entry->exists('FAIrepository')) {
2337 # Add an entry for each Repository configured for server
2338 foreach my $repo(@{$entry->get_value('FAIrepository', asref => 1)}) {
2339 my($tmp_url,$tmp_server,$tmp_release,$tmp_sections) = split(/\|/, $repo);
2340 my $tmp_tag= $entry->get_value('gosaUnitTag') || "";
2341 $result= $fai_server_db->add_dbentry( {
2342 table => $table_name,
2343 primkey => ['server', 'fai_release', 'tag'],
2344 server => $tmp_url,
2345 fai_release => $tmp_release,
2346 sections => $tmp_sections,
2347 tag => (length($tmp_tag)>0)?$tmp_tag:"",
2348 } );
2349 }
2350 }
2351 }
2352 }
2353 daemon_log("$session_id INFO: create_fai_server_db: finished", 5);
2355 # TODO: Find a way to post the 'create_packages_list_db' event
2356 if(not defined($dont_create_packages_list)) {
2357 &create_packages_list_db(undef, undef, $session_id);
2358 }
2359 }
2361 $ldap_handle->disconnect;
2362 return $result;
2363 }
2366 sub run_create_fai_release_db {
2367 my ($session, $heap, $table_name) = @_[SESSION, HEAP, ARG0];
2368 my $session_id = $session->ID;
2369 my $task = POE::Wheel::Run->new(
2370 Program => sub { &create_fai_release_db($table_name, $session_id) },
2371 StdoutEvent => "session_run_result",
2372 StderrEvent => "session_run_debug",
2373 CloseEvent => "session_run_done",
2374 );
2376 $heap->{task}->{ $task->ID } = $task;
2377 return;
2378 }
2381 sub create_fai_release_db {
2382 my ($table_name, $session_id) = @_;
2383 my $result;
2385 # used for logging
2386 if (not defined $session_id) { $session_id = 0; }
2388 my $ldap_handle = &get_ldap_handle();
2389 if(defined($ldap_handle)) {
2390 daemon_log("$session_id INFO: create_fai_release_db: start",5);
2391 my $mesg= $ldap_handle->search(
2392 base => $ldap_base,
2393 scope => 'sub',
2394 attrs => [],
2395 filter => "(&(objectClass=organizationalUnit)(ou=fai))",
2396 );
2397 if($mesg->{'resultCode'} == 0 &&
2398 $mesg->count != 0) {
2399 # Walk through all possible FAI container ou's
2400 my @sql_list;
2401 my $timestamp= &get_time();
2402 foreach my $ou (@{$mesg->{entries}}) {
2403 my $tmp_classes= resolve_fai_classes($ou->dn, $ldap_handle, $session_id);
2404 if(defined($tmp_classes) && ref($tmp_classes) eq 'HASH') {
2405 my @tmp_array=get_fai_release_entries($tmp_classes);
2406 if(@tmp_array) {
2407 foreach my $entry (@tmp_array) {
2408 if(defined($entry) && ref($entry) eq 'HASH') {
2409 my $sql=
2410 "INSERT INTO $table_name "
2411 ."(timestamp, fai_release, class, type, state) VALUES ("
2412 .$timestamp.","
2413 ."'".$entry->{'release'}."',"
2414 ."'".$entry->{'class'}."',"
2415 ."'".$entry->{'type'}."',"
2416 ."'".$entry->{'state'}."')";
2417 push @sql_list, $sql;
2418 }
2419 }
2420 }
2421 }
2422 }
2424 daemon_log("$session_id DEBUG: Inserting ".scalar @sql_list." entries to DB",8);
2425 if(@sql_list) {
2426 unshift @sql_list, "DELETE FROM $table_name";
2427 $fai_release_db->exec_statementlist(\@sql_list);
2428 }
2429 daemon_log("$session_id DEBUG: Done with inserting",7);
2430 }
2431 daemon_log("$session_id INFO: create_fai_release_db: finished",5);
2432 }
2433 $ldap_handle->disconnect;
2434 return $result;
2435 }
2437 sub get_fai_types {
2438 my $tmp_classes = shift || return undef;
2439 my @result;
2441 foreach my $type(keys %{$tmp_classes}) {
2442 if(defined($tmp_classes->{$type}[0]) && (!($tmp_classes->{$type}[0] =~ /^.*?removed.*?$/))) {
2443 my $entry = {
2444 type => $type,
2445 state => $tmp_classes->{$type}[0],
2446 };
2447 push @result, $entry;
2448 }
2449 }
2451 return @result;
2452 }
2454 sub get_fai_state {
2455 my $result = "";
2456 my $tmp_classes = shift || return $result;
2458 foreach my $type(keys %{$tmp_classes}) {
2459 if(defined($tmp_classes->{$type}[0])) {
2460 $result = $tmp_classes->{$type}[0];
2462 # State is equal for all types in class
2463 last;
2464 }
2465 }
2467 return $result;
2468 }
2470 sub resolve_fai_classes {
2471 my ($fai_base, $ldap_handle, $session_id) = @_;
2472 if (not defined $session_id) { $session_id = 0; }
2473 my $result;
2474 my @possible_fai_classes= ("FAIscript", "FAIhook", "FAIpartitionTable", "FAItemplate", "FAIvariable", "FAIprofile", "FAIpackageList");
2475 my $fai_filter= "(|(&(objectClass=FAIclass)(|(objectClass=".join(")(objectClass=", @possible_fai_classes).")))(objectClass=FAIbranch))";
2476 my $fai_classes;
2478 daemon_log("$session_id DEBUG: Searching for FAI entries in base $fai_base",7);
2479 my $mesg= $ldap_handle->search(
2480 base => $fai_base,
2481 scope => 'sub',
2482 attrs => ['cn','objectClass','FAIstate'],
2483 filter => $fai_filter,
2484 );
2485 daemon_log("$session_id DEBUG: Found ".$mesg->count()." FAI entries",7);
2487 if($mesg->{'resultCode'} == 0 &&
2488 $mesg->count != 0) {
2489 foreach my $entry (@{$mesg->{entries}}) {
2490 if($entry->exists('cn')) {
2491 my $tmp_dn= $entry->dn();
2493 # Skip classname and ou dn parts for class
2494 my $tmp_release = ($1) if $tmp_dn =~ /^[^,]+,[^,]+,(.*?),$fai_base$/;
2496 # Skip classes without releases
2497 if((!defined($tmp_release)) || length($tmp_release)==0) {
2498 next;
2499 }
2501 my $tmp_cn= $entry->get_value('cn');
2502 my $tmp_state= $entry->get_value('FAIstate');
2504 my $tmp_type;
2505 # Get FAI type
2506 for my $oclass(@{$entry->get_value('objectClass', asref => 1)}) {
2507 if(grep $_ eq $oclass, @possible_fai_classes) {
2508 $tmp_type= $oclass;
2509 last;
2510 }
2511 }
2513 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2514 # A Subrelease
2515 my @sub_releases = split(/,/, $tmp_release);
2517 # Walk through subreleases and build hash tree
2518 my $hash;
2519 while(my $tmp_sub_release = pop @sub_releases) {
2520 $hash .= "\{'$tmp_sub_release'\}->";
2521 }
2522 eval('push @{$fai_classes->'.$hash.'{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";');
2523 } else {
2524 # A branch, no subrelease
2525 push @{$fai_classes->{$tmp_release}->{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";
2526 }
2527 } elsif (!$entry->exists('cn')) {
2528 my $tmp_dn= $entry->dn();
2529 my $tmp_release = ($1) if $tmp_dn =~ /^(.*?),$fai_base$/;
2531 # Skip classes without releases
2532 if((!defined($tmp_release)) || length($tmp_release)==0) {
2533 next;
2534 }
2536 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2537 # A Subrelease
2538 my @sub_releases= split(/,/, $tmp_release);
2540 # Walk through subreleases and build hash tree
2541 my $hash;
2542 while(my $tmp_sub_release = pop @sub_releases) {
2543 $hash .= "\{'$tmp_sub_release'\}->";
2544 }
2545 # Remove the last two characters
2546 chop($hash);
2547 chop($hash);
2549 eval('$fai_classes->'.$hash.'= {}');
2550 } else {
2551 # A branch, no subrelease
2552 if(!exists($fai_classes->{$tmp_release})) {
2553 $fai_classes->{$tmp_release} = {};
2554 }
2555 }
2556 }
2557 }
2559 # The hash is complete, now we can honor the copy-on-write based missing entries
2560 foreach my $release (keys %$fai_classes) {
2561 $result->{$release}= deep_copy(apply_fai_inheritance($fai_classes->{$release}));
2562 }
2563 }
2564 return $result;
2565 }
2567 sub apply_fai_inheritance {
2568 my $fai_classes = shift || return {};
2569 my $tmp_classes;
2571 # Get the classes from the branch
2572 foreach my $class (keys %{$fai_classes}) {
2573 # Skip subreleases
2574 if($class =~ /^ou=.*$/) {
2575 next;
2576 } else {
2577 $tmp_classes->{$class}= deep_copy($fai_classes->{$class});
2578 }
2579 }
2581 # Apply to each subrelease
2582 foreach my $subrelease (keys %{$fai_classes}) {
2583 if($subrelease =~ /ou=/) {
2584 foreach my $tmp_class (keys %{$tmp_classes}) {
2585 if(!exists($fai_classes->{$subrelease}->{$tmp_class})) {
2586 $fai_classes->{$subrelease}->{$tmp_class} =
2587 deep_copy($tmp_classes->{$tmp_class});
2588 } else {
2589 foreach my $type (keys %{$tmp_classes->{$tmp_class}}) {
2590 if(!exists($fai_classes->{$subrelease}->{$tmp_class}->{$type})) {
2591 $fai_classes->{$subrelease}->{$tmp_class}->{$type}=
2592 deep_copy($tmp_classes->{$tmp_class}->{$type});
2593 }
2594 }
2595 }
2596 }
2597 }
2598 }
2600 # Find subreleases in deeper levels
2601 foreach my $subrelease (keys %{$fai_classes}) {
2602 if($subrelease =~ /ou=/) {
2603 foreach my $subsubrelease (keys %{$fai_classes->{$subrelease}}) {
2604 if($subsubrelease =~ /ou=/) {
2605 apply_fai_inheritance($fai_classes->{$subrelease});
2606 }
2607 }
2608 }
2609 }
2611 return $fai_classes;
2612 }
2614 sub get_fai_release_entries {
2615 my $tmp_classes = shift || return;
2616 my $parent = shift || "";
2617 my @result = shift || ();
2619 foreach my $entry (keys %{$tmp_classes}) {
2620 if(defined($entry)) {
2621 if($entry =~ /^ou=.*$/) {
2622 my $release_name = $entry;
2623 $release_name =~ s/ou=//g;
2624 if(length($parent)>0) {
2625 $release_name = $parent."/".$release_name;
2626 }
2627 my @bufentries = get_fai_release_entries($tmp_classes->{$entry}, $release_name, @result);
2628 foreach my $bufentry(@bufentries) {
2629 push @result, $bufentry;
2630 }
2631 } else {
2632 my @types = get_fai_types($tmp_classes->{$entry});
2633 foreach my $type (@types) {
2634 push @result,
2635 {
2636 'class' => $entry,
2637 'type' => $type->{'type'},
2638 'release' => $parent,
2639 'state' => $type->{'state'},
2640 };
2641 }
2642 }
2643 }
2644 }
2646 return @result;
2647 }
2649 sub deep_copy {
2650 my $this = shift;
2651 if (not ref $this) {
2652 $this;
2653 } elsif (ref $this eq "ARRAY") {
2654 [map deep_copy($_), @$this];
2655 } elsif (ref $this eq "HASH") {
2656 +{map { $_ => deep_copy($this->{$_}) } keys %$this};
2657 } else { die "what type is $_?" }
2658 }
2661 sub session_run_result {
2662 my ($kernel, $heap, $client_answer) = @_[KERNEL, HEAP, ARG0];
2663 $kernel->sig(CHLD => "child_reap");
2664 }
2666 sub session_run_debug {
2667 my $result = $_[ARG0];
2668 print STDERR "$result\n";
2669 }
2671 sub session_run_done {
2672 my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
2673 delete $heap->{task}->{$task_id};
2674 }
2677 sub create_sources_list {
2678 my $session_id = shift;
2679 my $ldap_handle = &main::get_ldap_handle;
2680 my $result="/tmp/gosa_si_tmp_sources_list";
2682 # Remove old file
2683 if(stat($result)) {
2684 unlink($result);
2685 &main::daemon_log("$session_id DEBUG: remove an old version of '$result'", 7);
2686 }
2688 my $fh;
2689 open($fh, ">$result");
2690 if (not defined $fh) {
2691 &main::daemon_log("$session_id DEBUG: cannot open '$result' for writing", 7);
2692 return undef;
2693 }
2694 if(defined($main::ldap_server_dn) and length($main::ldap_server_dn) > 0) {
2695 my $mesg=$ldap_handle->search(
2696 base => $main::ldap_server_dn,
2697 scope => 'base',
2698 attrs => 'FAIrepository',
2699 filter => 'objectClass=FAIrepositoryServer'
2700 );
2701 if($mesg->count) {
2702 foreach my $entry(@{$mesg->{'entries'}}) {
2703 foreach my $value(@{$entry->get_value('FAIrepository', asref => 1)}) {
2704 my ($server, $tag, $release, $sections)= split /\|/, $value;
2705 my $line = "deb $server $release";
2706 $sections =~ s/,/ /g;
2707 $line.= " $sections";
2708 print $fh $line."\n";
2709 }
2710 }
2711 }
2712 } else {
2713 if (defined $main::ldap_server_dn){
2714 &main::daemon_log("$session_id ERROR: something wrong with ldap_server_dn '$main::ldap_server_dn', abort create_sources_list", 1);
2715 } else {
2716 &main::daemon_log("$session_id ERROR: no ldap_server_dn found, abort create_sources_list", 1);
2717 }
2718 }
2719 close($fh);
2721 return $result;
2722 }
2725 sub run_create_packages_list_db {
2726 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2727 my $session_id = $session->ID;
2729 my $task = POE::Wheel::Run->new(
2730 Priority => +20,
2731 Program => sub {&create_packages_list_db(undef, undef, $session_id)},
2732 StdoutEvent => "session_run_result",
2733 StderrEvent => "session_run_debug",
2734 CloseEvent => "session_run_done",
2735 );
2736 $heap->{task}->{ $task->ID } = $task;
2737 }
2740 sub create_packages_list_db {
2741 my ($ldap_handle, $sources_file, $session_id) = @_;
2743 # it should not be possible to trigger a recreation of packages_list_db
2744 # while packages_list_db is under construction, so set flag packages_list_under_construction
2745 # which is tested befor recreation can be started
2746 if (-r $packages_list_under_construction) {
2747 daemon_log("$session_id WARNING: packages_list_db is right now under construction, please wait until this process is finished", 3);
2748 return;
2749 } else {
2750 daemon_log("$session_id INFO: create_packages_list_db: start", 5);
2751 # set packages_list_under_construction to true
2752 system("touch $packages_list_under_construction");
2753 @packages_list_statements=();
2754 }
2756 if (not defined $session_id) { $session_id = 0; }
2757 if (not defined $ldap_handle) {
2758 $ldap_handle= &get_ldap_handle();
2760 if (not defined $ldap_handle) {
2761 daemon_log("$session_id ERROR: no ldap_handle available to create_packages_list_db", 1);
2762 unlink($packages_list_under_construction);
2763 return;
2764 }
2765 }
2766 if (not defined $sources_file) {
2767 &main::daemon_log("$session_id INFO: no sources_file given for creating packages list so trigger creation of it", 5);
2768 $sources_file = &create_sources_list($session_id);
2769 }
2771 if (not defined $sources_file) {
2772 &main::daemon_log("$session_id ERROR: no sources_file given under '$sources_file', skip create_packages_list_db", 1);
2773 unlink($packages_list_under_construction);
2774 return;
2775 }
2777 my $line;
2779 open(CONFIG, "<$sources_file") or do {
2780 daemon_log( "$session_id ERROR: create_packages_list_db: Failed to open '$sources_file'", 1);
2781 unlink($packages_list_under_construction);
2782 return;
2783 };
2785 # Read lines
2786 while ($line = <CONFIG>){
2787 # Unify
2788 chop($line);
2789 $line =~ s/^\s+//;
2790 $line =~ s/^\s+/ /;
2792 # Strip comments
2793 $line =~ s/#.*$//g;
2795 # Skip empty lines
2796 if ($line =~ /^\s*$/){
2797 next;
2798 }
2800 # Interpret deb line
2801 if ($line =~ /^deb [^\s]+\s[^\s]+\s[^\s]+/){
2802 my( $baseurl, $dist, $sections ) = ($line =~ /^deb\s([^\s]+)\s+([^\s]+)\s+(.*)$/);
2803 my $section;
2804 foreach $section (split(' ', $sections)){
2805 &parse_package_info( $baseurl, $dist, $section, $session_id );
2806 }
2807 }
2808 }
2810 close (CONFIG);
2812 if(keys(%repo_dirs)) {
2813 find(\&cleanup_and_extract, keys( %repo_dirs ));
2814 &main::strip_packages_list_statements();
2815 $packages_list_db->exec_statementlist(\@packages_list_statements);
2816 }
2817 unlink($packages_list_under_construction);
2818 daemon_log("$session_id INFO: create_packages_list_db: finished", 5);
2819 return;
2820 }
2822 # This function should do some intensive task to minimize the db-traffic
2823 sub strip_packages_list_statements {
2824 my @existing_entries= @{$packages_list_db->exec_statement("SELECT * FROM $main::packages_list_tn")};
2825 my @new_statement_list=();
2826 my $hash;
2827 my $insert_hash;
2828 my $update_hash;
2829 my $delete_hash;
2830 my $known_packages_hash;
2831 my $local_timestamp=get_time();
2833 foreach my $existing_entry (@existing_entries) {
2834 $hash->{@{$existing_entry}[0]}->{@{$existing_entry}[1]}->{@{$existing_entry}[2]}= $existing_entry;
2835 }
2837 foreach my $statement (@packages_list_statements) {
2838 if($statement =~ /^INSERT/i) {
2839 # Assign the values from the insert statement
2840 my ($distribution,$package,$version,$section,$description,$template,$timestamp) = ($1,$2,$3,$4,$5,$6,$7) if $statement =~
2841 /^INSERT\s+?INTO\s+?$main::packages_list_tn\s+?VALUES\s*?\('(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)'\s*?\)$/si;
2842 if(exists($hash->{$distribution}->{$package}->{$version})) {
2843 # If section or description has changed, update the DB
2844 if(
2845 (! (@{$hash->{$distribution}->{$package}->{$version}}[3] eq $section)) or
2846 (! (@{$hash->{$distribution}->{$package}->{$version}}[4] eq $description))
2847 ) {
2848 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,undef);
2849 } else {
2850 # package is already present in database. cache this knowledge for later use
2851 @{$known_packages_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2852 }
2853 } else {
2854 # Insert a non-existing entry to db
2855 @{$insert_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2856 }
2857 } elsif ($statement =~ /^UPDATE/i) {
2858 my ($template,$package,$version) = ($1,$2,$3) if $statement =~
2859 /^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;
2860 foreach my $distribution (keys %{$hash}) {
2861 if(exists($insert_hash->{$distribution}->{$package}->{$version})) {
2862 # update the insertion hash to execute only one query per package (insert instead insert+update)
2863 @{$insert_hash->{$distribution}->{$package}->{$version}}[5]= $template;
2864 } elsif(exists($hash->{$distribution}->{$package}->{$version})) {
2865 if( ! (@{$hash->{$distribution}->{$package}->{$version}}[5] eq $template)) {
2866 my $section;
2867 my $description;
2868 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) and
2869 length(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) > 0 ) {
2870 $section= @{$update_hash->{$distribution}->{$package}->{$version}}[3];
2871 }
2872 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2873 $description= @{$update_hash->{$distribution}->{$package}->{$version}}[4];
2874 }
2875 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2876 }
2877 }
2878 }
2879 }
2880 }
2882 # Check for orphaned entries
2883 foreach my $existing_entry (@existing_entries) {
2884 my $distribution= @{$existing_entry}[0];
2885 my $package= @{$existing_entry}[1];
2886 my $version= @{$existing_entry}[2];
2887 my $section= @{$existing_entry}[3];
2889 if(
2890 exists($insert_hash->{$distribution}->{$package}->{$version}) ||
2891 exists($update_hash->{$distribution}->{$package}->{$version}) ||
2892 exists($known_packages_hash->{$distribution}->{$package}->{$version})
2893 ) {
2894 next;
2895 } else {
2896 # Insert entry to delete hash
2897 @{$delete_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section);
2898 }
2899 }
2901 # unroll the insert hash
2902 foreach my $distribution (keys %{$insert_hash}) {
2903 foreach my $package (keys %{$insert_hash->{$distribution}}) {
2904 foreach my $version (keys %{$insert_hash->{$distribution}->{$package}}) {
2905 push @new_statement_list, "INSERT INTO $main::packages_list_tn VALUES ('$distribution','$package','$version',"
2906 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[3]',"
2907 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[4]',"
2908 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[5]',"
2909 ."'$local_timestamp')";
2910 }
2911 }
2912 }
2914 # unroll the update hash
2915 foreach my $distribution (keys %{$update_hash}) {
2916 foreach my $package (keys %{$update_hash->{$distribution}}) {
2917 foreach my $version (keys %{$update_hash->{$distribution}->{$package}}) {
2918 my $set = "";
2919 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3])) {
2920 $set .= "section = '@{$update_hash->{$distribution}->{$package}->{$version}}[3]', ";
2921 }
2922 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2923 $set .= "description = '@{$update_hash->{$distribution}->{$package}->{$version}}[4]', ";
2924 }
2925 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[5])) {
2926 $set .= "template = '@{$update_hash->{$distribution}->{$package}->{$version}}[5]', ";
2927 }
2928 if(defined($set) and length($set) > 0) {
2929 $set .= "timestamp = '$local_timestamp'";
2930 } else {
2931 next;
2932 }
2933 push @new_statement_list,
2934 "UPDATE $main::packages_list_tn SET $set WHERE"
2935 ." distribution = '$distribution'"
2936 ." AND package = '$package'"
2937 ." AND version = '$version'";
2938 }
2939 }
2940 }
2942 # unroll the delete hash
2943 foreach my $distribution (keys %{$delete_hash}) {
2944 foreach my $package (keys %{$delete_hash->{$distribution}}) {
2945 foreach my $version (keys %{$delete_hash->{$distribution}->{$package}}) {
2946 my $section = @{$delete_hash->{$distribution}->{$package}->{$version}}[3];
2947 push @new_statement_list, "DELETE FROM $main::packages_list_tn WHERE distribution='$distribution' AND package='$package' AND version='$version' AND section='$section'";
2948 }
2949 }
2950 }
2952 @packages_list_statements = @new_statement_list;
2953 }
2956 sub parse_package_info {
2957 my ($baseurl, $dist, $section, $session_id)= @_;
2958 my ($package);
2959 if (not defined $session_id) { $session_id = 0; }
2960 my ($path) = ($baseurl =~ m%://[^/]*(.*)$%);
2961 $repo_dirs{ "${repo_path}/pool" } = 1;
2963 foreach $package ("Packages.gz"){
2964 daemon_log("$session_id DEBUG: create_packages_list: fetch $baseurl, $dist, $section", 7);
2965 get_package( "$baseurl/dists/$dist/$section/binary-$arch/$package", "$outdir/$dist/$section", $session_id );
2966 parse_package( "$outdir/$dist/$section", $dist, $path, $session_id );
2967 }
2969 }
2972 sub get_package {
2973 my ($url, $dest, $session_id)= @_;
2974 if (not defined $session_id) { $session_id = 0; }
2976 my $tpath = dirname($dest);
2977 -d "$tpath" || mkpath "$tpath";
2979 # This is ugly, but I've no time to take a look at "how it works in perl"
2980 if(0 == system("wget '$url' -O '$dest' 2>/dev/null") ) {
2981 system("gunzip -cd '$dest' > '$dest.in'");
2982 daemon_log("$session_id DEBUG: run command: gunzip -cd '$dest' > '$dest.in'", 5);
2983 unlink($dest);
2984 daemon_log("$session_id DEBUG: delete file '$dest'", 5);
2985 } else {
2986 daemon_log("$session_id ERROR: create_packages_list_db: get_packages: fetching '$url' failed!", 1);
2987 }
2988 return 0;
2989 }
2992 sub parse_package {
2993 my ($path, $dist, $srv_path, $session_id)= @_;
2994 if (not defined $session_id) { $session_id = 0;}
2995 my ($package, $version, $section, $description);
2996 my $PACKAGES;
2997 my $timestamp = &get_time();
2999 if(not stat("$path.in")) {
3000 daemon_log("$session_id ERROR: create_packages_list: parse_package: file '$path.in' is not readable",1);
3001 return;
3002 }
3004 open($PACKAGES, "<$path.in");
3005 if(not defined($PACKAGES)) {
3006 daemon_log("$session_id ERROR: create_packages_list_db: parse_package: cannot open '$path.in'",1);
3007 return;
3008 }
3010 # Read lines
3011 while (<$PACKAGES>){
3012 my $line = $_;
3013 # Unify
3014 chop($line);
3016 # Use empty lines as a trigger
3017 if ($line =~ /^\s*$/){
3018 my $sql = "INSERT INTO packages_list VALUES ('$dist', '$package', '$version', '$section', '$description', '', '$timestamp')";
3019 push(@packages_list_statements, $sql);
3020 $package = "none";
3021 $version = "none";
3022 $section = "none";
3023 $description = "none";
3024 next;
3025 }
3027 # Trigger for package name
3028 if ($line =~ /^Package:\s/){
3029 ($package)= ($line =~ /^Package: (.*)$/);
3030 next;
3031 }
3033 # Trigger for version
3034 if ($line =~ /^Version:\s/){
3035 ($version)= ($line =~ /^Version: (.*)$/);
3036 next;
3037 }
3039 # Trigger for description
3040 if ($line =~ /^Description:\s/){
3041 ($description)= &encode_base64(($line =~ /^Description: (.*)$/));
3042 next;
3043 }
3045 # Trigger for section
3046 if ($line =~ /^Section:\s/){
3047 ($section)= ($line =~ /^Section: (.*)$/);
3048 next;
3049 }
3051 # Trigger for filename
3052 if ($line =~ /^Filename:\s/){
3053 my ($filename) = ($line =~ /^Filename: (.*)$/);
3054 store_fileinfo( $package, $filename, $dist, $srv_path, $version, $repo_path );
3055 next;
3056 }
3057 }
3059 close( $PACKAGES );
3060 unlink( "$path.in" );
3061 }
3064 sub store_fileinfo {
3065 my( $package, $file, $dist, $path, $vers, $srvdir) = @_;
3067 my %fileinfo = (
3068 'package' => $package,
3069 'dist' => $dist,
3070 'version' => $vers,
3071 );
3073 $repo_files{ "${srvdir}/$file" } = \%fileinfo;
3074 }
3077 sub cleanup_and_extract {
3078 my $fileinfo = $repo_files{ $File::Find::name };
3080 if( defined $fileinfo ) {
3081 my $dir = "$outdir/$fileinfo->{ 'dist' }/debconf.d";
3082 my $sql;
3083 my $package = $fileinfo->{ 'package' };
3084 my $newver = $fileinfo->{ 'version' };
3086 mkpath($dir);
3087 system( "dpkg -e '$File::Find::name' '$dir/DEBIAN'" );
3089 if( -f "$dir/DEBIAN/templates" ) {
3091 daemon_log("0 DEBUG: Found debconf templates in '$package' - $newver", 7);
3093 my $tmpl= ""; {
3094 local $/=undef;
3095 open FILE, "$dir/DEBIAN/templates";
3096 $tmpl = &encode_base64(<FILE>);
3097 close FILE;
3098 }
3099 rmtree("$dir/DEBIAN/templates");
3101 $sql= "update $main::packages_list_tn set template = '$tmpl' where package = '$package' and version = '$newver';";
3102 push @packages_list_statements, $sql;
3103 }
3104 }
3106 return;
3107 }
3110 sub register_at_foreign_servers {
3111 my ($kernel) = $_[KERNEL];
3113 # hole alle bekannten server aus known_server_db
3114 my $server_sql = "SELECT * FROM $known_server_tn";
3115 my $server_res = $known_server_db->exec_statement($server_sql);
3117 # no entries in known_server_db
3118 if (not ref(@$server_res[0]) eq "ARRAY") {
3119 # TODO
3120 }
3122 # detect already connected clients
3123 my $client_sql = "SELECT * FROM $known_clients_tn";
3124 my $client_res = $known_clients_db->exec_statement($client_sql);
3126 # send my server details to all other gosa-si-server within the network
3127 foreach my $hit (@$server_res) {
3128 my $hostname = @$hit[0];
3129 my $hostkey = &create_passwd;
3131 # add already connected clients to registration message
3132 my $myhash = &create_xml_hash('new_server', $server_address, $hostname);
3133 &add_content2xml_hash($myhash, 'key', $hostkey);
3134 map(&add_content2xml_hash($myhash, 'client', @{$_}[0].",".@{$_}[4]), @$client_res);
3136 # add locally loaded gosa-si modules to registration message
3137 my $loaded_modules = {};
3138 while (my ($package, $pck_info) = each %$known_modules) {
3139 next if ((!defined(@$pck_info[2])) || (!(ref (@$pck_info[2]) eq 'HASH')));
3140 foreach my $act_module (keys(%{@$pck_info[2]})) {
3141 $loaded_modules->{$act_module} = "";
3142 }
3143 }
3145 map(&add_content2xml_hash($myhash, "loaded_modules", $_), keys(%$loaded_modules));
3147 # add macaddress to registration message
3148 my ($host_ip, $host_port) = split(/:/, $hostname);
3149 my $local_ip = &get_local_ip_for_remote_ip($host_ip);
3150 my $network_interface= &get_interface_for_ip($local_ip);
3151 my $host_mac = &get_mac_for_interface($network_interface);
3152 &add_content2xml_hash($myhash, 'macaddress', $host_mac);
3154 # build registration message and send it
3155 my $foreign_server_msg = &create_xml_string($myhash);
3156 my $error = &send_msg_to_target($foreign_server_msg, $hostname, $ServerPackages_key, "new_server", 0);
3157 }
3159 $kernel->delay_set("register_at_foreign_servers", $foreign_servers_register_delay);
3160 return;
3161 }
3164 #==== MAIN = main ==============================================================
3165 # parse commandline options
3166 Getopt::Long::Configure( "bundling" );
3167 GetOptions("h|help" => \&usage,
3168 "c|config=s" => \$cfg_file,
3169 "f|foreground" => \$foreground,
3170 "v|verbose+" => \$verbose,
3171 "no-arp+" => \$no_arp,
3172 );
3174 # read and set config parameters
3175 &check_cmdline_param ;
3176 &read_configfile($cfg_file, %cfg_defaults);
3177 &check_pid;
3179 $SIG{CHLD} = 'IGNORE';
3181 # forward error messages to logfile
3182 if( ! $foreground ) {
3183 open( STDIN, '+>/dev/null' );
3184 open( STDOUT, '+>&STDIN' );
3185 open( STDERR, '+>&STDIN' );
3186 }
3188 # Just fork, if we are not in foreground mode
3189 if( ! $foreground ) {
3190 chdir '/' or die "Can't chdir to /: $!";
3191 $pid = fork;
3192 setsid or die "Can't start a new session: $!";
3193 umask 0;
3194 } else {
3195 $pid = $$;
3196 }
3198 # Do something useful - put our PID into the pid_file
3199 if( 0 != $pid ) {
3200 open( LOCK_FILE, ">$pid_file" );
3201 print LOCK_FILE "$pid\n";
3202 close( LOCK_FILE );
3203 if( !$foreground ) {
3204 exit( 0 )
3205 };
3206 }
3208 # parse head url and revision from svn
3209 my $server_status_hash = { 'developmental'=>'revision', 'stable'=>'release'};
3210 $server_version =~ /^\$HeadURL: (\S+) \$:\$Rev: (\d+) \$$/;
3211 $server_headURL = defined $1 ? $1 : 'unknown' ;
3212 $server_revision = defined $2 ? $2 : 'unknown' ;
3213 if ($server_headURL =~ /\/tag\// ||
3214 $server_headURL =~ /\/branches\// ) {
3215 $server_status = "stable";
3216 } else {
3217 $server_status = "developmental" ;
3218 }
3220 # Prepare log file
3221 $root_uid = getpwnam('root');
3222 $adm_gid = getgrnam('adm');
3223 chmod(0640, $log_file);
3224 chown($root_uid, $adm_gid, $log_file);
3225 chown($root_uid, $adm_gid, "/var/lib/gosa-si");
3227 daemon_log(" ", 1);
3228 daemon_log("$0 started!", 1);
3229 daemon_log("status: $server_status", 1);
3230 daemon_log($server_status_hash->{$server_status}.": $server_revision", 1);
3232 {
3233 no strict "refs";
3235 if ($db_module eq "DBmysql") {
3236 # connect to incoming_db
3237 $incoming_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3239 # connect to gosa-si job queue
3240 $job_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3242 # connect to known_clients_db
3243 $known_clients_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3245 # connect to foreign_clients_db
3246 $foreign_clients_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3248 # connect to known_server_db
3249 $known_server_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3251 # connect to login_usr_db
3252 $login_users_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3254 # connect to fai_server_db
3255 $fai_server_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3257 # connect to fai_release_db
3258 $fai_release_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3260 # connect to packages_list_db
3261 $packages_list_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3263 # connect to messaging_db
3264 $messaging_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3266 } elsif ($db_module eq "DBsqlite") {
3267 # connect to incoming_db
3268 unlink($incoming_file_name);
3269 $incoming_db = GOSA::DBsqlite->new($incoming_file_name);
3271 # connect to gosa-si job queue
3272 unlink($job_queue_file_name); ## just for debugging
3273 $job_db = GOSA::DBsqlite->new($job_queue_file_name);
3274 chmod(0660, $job_queue_file_name);
3275 chown($root_uid, $adm_gid, $job_queue_file_name);
3277 # connect to known_clients_db
3278 unlink($known_clients_file_name); ## just for debugging
3279 $known_clients_db = GOSA::DBsqlite->new($known_clients_file_name);
3280 chmod(0660, $known_clients_file_name);
3281 chown($root_uid, $adm_gid, $known_clients_file_name);
3283 # connect to foreign_clients_db
3284 unlink($foreign_clients_file_name);
3285 $foreign_clients_db = GOSA::DBsqlite->new($foreign_clients_file_name);
3286 chmod(0660, $foreign_clients_file_name);
3287 chown($root_uid, $adm_gid, $foreign_clients_file_name);
3289 # connect to known_server_db
3290 unlink($known_server_file_name);
3291 $known_server_db = GOSA::DBsqlite->new($known_server_file_name);
3292 chmod(0660, $known_server_file_name);
3293 chown($root_uid, $adm_gid, $known_server_file_name);
3295 # connect to login_usr_db
3296 unlink($login_users_file_name);
3297 $login_users_db = GOSA::DBsqlite->new($login_users_file_name);
3298 chmod(0660, $login_users_file_name);
3299 chown($root_uid, $adm_gid, $login_users_file_name);
3301 # connect to fai_server_db
3302 unlink($fai_server_file_name);
3303 $fai_server_db = GOSA::DBsqlite->new($fai_server_file_name);
3304 chmod(0660, $fai_server_file_name);
3305 chown($root_uid, $adm_gid, $fai_server_file_name);
3307 # connect to fai_release_db
3308 unlink($fai_release_file_name);
3309 $fai_release_db = GOSA::DBsqlite->new($fai_release_file_name);
3310 chmod(0660, $fai_release_file_name);
3311 chown($root_uid, $adm_gid, $fai_release_file_name);
3313 # connect to packages_list_db
3314 #unlink($packages_list_file_name);
3315 unlink($packages_list_under_construction);
3316 $packages_list_db = GOSA::DBsqlite->new($packages_list_file_name);
3317 chmod(0660, $packages_list_file_name);
3318 chown($root_uid, $adm_gid, $packages_list_file_name);
3320 # connect to messaging_db
3321 unlink($messaging_file_name);
3322 $messaging_db = GOSA::DBsqlite->new($messaging_file_name);
3323 chmod(0660, $messaging_file_name);
3324 chown($root_uid, $adm_gid, $messaging_file_name);
3325 }
3326 }
3328 # Creating tables
3329 $messaging_db->create_table($messaging_tn, \@messaging_col_names);
3330 $packages_list_db->create_table($packages_list_tn, \@packages_list_col_names);
3331 $fai_release_db->create_table($fai_release_tn, \@fai_release_col_names);
3332 $fai_server_db->create_table($fai_server_tn, \@fai_server_col_names);
3333 $login_users_db->create_table($login_users_tn, \@login_users_col_names);
3334 $known_server_db->create_table($known_server_tn, \@known_server_col_names);
3335 $foreign_clients_db->create_table($foreign_clients_tn, \@foreign_clients_col_names);
3336 $known_clients_db->create_table($known_clients_tn, \@known_clients_col_names);
3337 $incoming_db->create_table($incoming_tn, \@incoming_col_names);
3338 $job_db->create_table($job_queue_tn, \@job_queue_col_names);
3341 # create xml object used for en/decrypting
3342 $xml = new XML::Simple();
3345 # foreign servers
3346 my @foreign_server_list;
3348 # add foreign server from cfg file
3349 if ($foreign_server_string ne "") {
3350 my @cfg_foreign_server_list = split(",", $foreign_server_string);
3351 foreach my $foreign_server (@cfg_foreign_server_list) {
3352 push(@foreign_server_list, $foreign_server);
3353 }
3355 daemon_log("0 INFO: found foreign server in config file: ".join(", ", @foreign_server_list), 5);
3356 }
3358 # Perform a DNS lookup for server registration if flag is true
3359 if ($dns_lookup eq "true") {
3360 # Add foreign server from dns
3361 my @tmp_servers;
3362 if (not $server_domain) {
3363 # Try our DNS Searchlist
3364 for my $domain(get_dns_domains()) {
3365 chomp($domain);
3366 my ($tmp_domains, $error_string) = &get_server_addresses($domain);
3367 if(@$tmp_domains) {
3368 for my $tmp_server(@$tmp_domains) {
3369 push @tmp_servers, $tmp_server;
3370 }
3371 }
3372 }
3373 if(@tmp_servers && length(@tmp_servers)==0) {
3374 daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
3375 }
3376 } else {
3377 @tmp_servers = &get_server_addresses($server_domain);
3378 if( 0 == @tmp_servers ) {
3379 daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
3380 }
3381 }
3383 daemon_log("0 INFO: found foreign server via DNS ".join(", ", @tmp_servers), 5);
3385 foreach my $server (@tmp_servers) {
3386 unshift(@foreign_server_list, $server);
3387 }
3388 } else {
3389 daemon_log("0 INFO: DNS lookup for server registration is disabled", 5);
3390 }
3393 # eliminate duplicate entries
3394 @foreign_server_list = &del_doubles(@foreign_server_list);
3395 my $all_foreign_server = join(", ", @foreign_server_list);
3396 daemon_log("0 INFO: found foreign server in config file and DNS: '$all_foreign_server'", 5);
3398 # add all found foreign servers to known_server
3399 my $act_timestamp = &get_time();
3400 foreach my $foreign_server (@foreign_server_list) {
3402 # do not add myself to known_server_db
3403 if (&is_local($foreign_server)) { next; }
3404 ######################################
3406 my $res = $known_server_db->add_dbentry( {table=>$known_server_tn,
3407 primkey=>['hostname'],
3408 hostname=>$foreign_server,
3409 macaddress=>"",
3410 status=>'not_jet_registered',
3411 hostkey=>"none",
3412 loaded_modules => "none",
3413 timestamp=>$act_timestamp,
3414 } );
3415 }
3418 # Import all modules
3419 &import_modules;
3421 # Check wether all modules are gosa-si valid passwd check
3422 &password_check;
3424 # Prepare for using Opsi
3425 if ($opsi_enabled eq "true") {
3426 use JSON::RPC::Client;
3427 use XML::Quote qw(:all);
3428 $opsi_url= "https://".$opsi_admin.":".$opsi_password."@".$opsi_server.":4447/rpc";
3429 $opsi_client = new JSON::RPC::Client;
3430 }
3433 POE::Component::Server::TCP->new(
3434 Alias => "TCP_SERVER",
3435 Port => $server_port,
3436 ClientInput => sub {
3437 my ($kernel, $input, $heap, $session) = @_[KERNEL, ARG0, HEAP, SESSION];
3438 my $session_id = $session->ID;
3439 my $remote_ip = $heap->{'remote_ip'};
3440 push(@msgs_to_decrypt, $input);
3441 &daemon_log("$session_id DEBUG: incoming message from '$remote_ip'", 7);
3442 $kernel->yield("msg_to_decrypt");
3443 },
3444 InlineStates => {
3445 msg_to_decrypt => \&msg_to_decrypt,
3446 next_task => \&next_task,
3447 task_result => \&handle_task_result,
3448 task_done => \&handle_task_done,
3449 task_debug => \&handle_task_debug,
3450 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!" },
3451 }
3452 );
3454 daemon_log("0 INFO: start socket for incoming xml messages at port '$server_port' ", 1);
3456 # create session for repeatedly checking the job queue for jobs
3457 POE::Session->create(
3458 inline_states => {
3459 _start => \&session_start,
3460 register_at_foreign_servers => \®ister_at_foreign_servers,
3461 sig_handler => \&sig_handler,
3462 next_task => \&next_task,
3463 task_result => \&handle_task_result,
3464 task_done => \&handle_task_done,
3465 task_debug => \&handle_task_debug,
3466 watch_for_next_tasks => \&watch_for_next_tasks,
3467 watch_for_new_messages => \&watch_for_new_messages,
3468 watch_for_delivery_messages => \&watch_for_delivery_messages,
3469 watch_for_done_messages => \&watch_for_done_messages,
3470 watch_for_new_jobs => \&watch_for_new_jobs,
3471 watch_for_modified_jobs => \&watch_for_modified_jobs,
3472 watch_for_done_jobs => \&watch_for_done_jobs,
3473 watch_for_opsi_jobs => \&watch_for_opsi_jobs,
3474 watch_for_old_known_clients => \&watch_for_old_known_clients,
3475 create_packages_list_db => \&run_create_packages_list_db,
3476 create_fai_server_db => \&run_create_fai_server_db,
3477 create_fai_release_db => \&run_create_fai_release_db,
3478 recreate_packages_db => \&run_recreate_packages_db,
3479 session_run_result => \&session_run_result,
3480 session_run_debug => \&session_run_debug,
3481 session_run_done => \&session_run_done,
3482 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!" },
3483 }
3484 );
3487 POE::Kernel->run();
3488 exit;