fbb16fee507b9389f978d0bf958c9f2bf2cba107
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 #===============================================================================
23 my $server_version = '$HeadURL: https://oss.gonicus.de/repositories/gosa/trunk/gosa-si/gosa-si-server $:$Rev$';
25 use strict;
26 use warnings;
27 use Getopt::Long;
28 use Config::IniFiles;
29 use POSIX;
31 use Fcntl qw/:flock/;
32 use IO::Socket::INET;
33 use IO::Handle;
34 use IO::Select;
35 use Symbol qw(qualify_to_ref);
36 use Crypt::Rijndael;
37 use MIME::Base64;
38 use Digest::MD5 qw(md5 md5_hex md5_base64);
39 use XML::Simple;
40 use Data::Dumper;
41 use Sys::Syslog qw( :DEFAULT setlogsock);
42 use Time::HiRes qw( usleep);
43 use Cwd;
44 use File::Spec;
45 use File::Basename;
46 use File::Find;
47 use File::Copy;
48 use File::Path;
49 use GOSA::GosaSupportDaemon;
50 use POE qw(Component::Server::TCP Wheel::Run Filter::Reference);
51 use Net::LDAP;
52 use Net::LDAP::Util qw(:escape);
53 use ResourcePool;
54 use ResourcePool::Factory::Net::LDAP;
56 # revision number of server and program name
57 my $server_headURL;
58 my $server_revision;
59 my $server_status;
60 our $prg= basename($0);
62 my $db_module = "DBsqlite";
63 {
64 no strict "refs";
65 require ("GOSA/".$db_module.".pm");
66 ("GOSA/".$db_module)->import;
67 daemon_log("0 INFO: importing database module '$db_module'", 1);
68 }
70 my $modules_path = "/usr/lib/gosa-si/modules";
71 use lib "/usr/lib/gosa-si/modules";
73 our $global_kernel;
74 my ($foreground, $ping_timeout);
75 my ($server);
76 my ($gosa_server, $job_queue_timeout, $job_queue_loop_delay);
77 my ($messaging_db_loop_delay);
78 my ($procid, $pid);
79 my ($arp_fifo, $ldap_pool, $ldap_factory);
80 my ($xml);
81 my $sources_list;
82 my $max_clients;
83 my %repo_files=();
84 my $repo_path;
85 my %repo_dirs=();
87 # Variables declared in config file are always set to 'our'
88 our (%cfg_defaults, $log_file, $pid_file,
89 $server_ip, $server_port, $ClientPackages_key, $dns_lookup,
90 $arp_activ, $gosa_unit_tag,
91 $GosaPackages_key, $gosa_timeout,
92 $foreign_server_string, $server_domain, $ServerPackages_key, $foreign_servers_register_delay,
93 $wake_on_lan_passwd, $job_synchronization, $modified_jobs_loop_delay,
94 $arp_enabled, $arp_interface,
95 $opsi_enabled, $opsi_server, $opsi_admin, $opsi_password,
96 $new_systems_ou,
97 );
99 # additional variable which should be globaly accessable
100 our $server_address;
101 our $server_mac_address;
102 our $gosa_address;
103 our $no_arp;
104 our $verbose;
105 our $forground;
106 our $cfg_file;
107 our ($ldap_uri, $ldap_base, $ldap_admin_dn, $ldap_admin_password, $ldap_server_dn, $ldap_version, $max_ldap_handle, $precreate_ldap_handle);
108 our ($mysql_username, $mysql_password, $mysql_database, $mysql_host);
109 our $known_modules;
110 our $root_uid;
111 our $adm_gid;
114 # specifies the verbosity of the daemon_log
115 $verbose = 0 ;
117 # if foreground is not null, script will be not forked to background
118 $foreground = 0 ;
120 # specifies the timeout seconds while checking the online status of a registrating client
121 $ping_timeout = 5;
123 $no_arp = 0;
124 my $packages_list_under_construction = "/tmp/packages_list_creation_in_progress";
125 my @packages_list_statements;
126 my $watch_for_new_jobs_in_progress = 0;
128 # holds all incoming decrypted messages
129 our $incoming_db;
130 our $incoming_tn = 'incoming';
131 my $incoming_file_name;
132 my @incoming_col_names = ("id INTEGER PRIMARY KEY",
133 "timestamp VARCHAR(14) DEFAULT 'none'",
134 "headertag VARCHAR(255) DEFAULT 'none'",
135 "targettag VARCHAR(255) DEFAULT 'none'",
136 "xmlmessage TEXT",
137 "module VARCHAR(255) DEFAULT 'none'",
138 "sessionid VARCHAR(255) DEFAULT '0'",
139 );
141 # holds all gosa jobs
142 our $job_db;
143 our $job_queue_tn = 'jobs';
144 my $job_queue_file_name;
145 my @job_queue_col_names = ("id INTEGER PRIMARY KEY",
146 "timestamp VARCHAR(14) DEFAULT 'none'",
147 "status VARCHAR(255) DEFAULT 'none'",
148 "result TEXT",
149 "progress VARCHAR(255) DEFAULT 'none'",
150 "headertag VARCHAR(255) DEFAULT 'none'",
151 "targettag VARCHAR(255) DEFAULT 'none'",
152 "xmlmessage TEXT",
153 "macaddress VARCHAR(17) DEFAULT 'none'",
154 "plainname VARCHAR(255) DEFAULT 'none'",
155 "siserver VARCHAR(255) DEFAULT 'none'",
156 "modified INTEGER DEFAULT '0'",
157 );
159 # holds all other gosa-si-server
160 our $known_server_db;
161 our $known_server_tn = "known_server";
162 my $known_server_file_name;
163 my @known_server_col_names = ("hostname VARCHAR(255)", "macaddress VARCHAR(17)", "status VARCHAR(255)", "hostkey VARCHAR(255)", "loaded_modules TEXT", "timestamp VARCHAR(14)");
165 # holds all registrated clients
166 our $known_clients_db;
167 our $known_clients_tn = "known_clients";
168 my $known_clients_file_name;
169 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)");
171 # holds all registered clients at a foreign server
172 our $foreign_clients_db;
173 our $foreign_clients_tn = "foreign_clients";
174 my $foreign_clients_file_name;
175 my @foreign_clients_col_names = ("hostname VARCHAR(255)", "macaddress VARCHAR(17)", "regserver VARCHAR(255)", "timestamp VARCHAR(14)");
177 # holds all logged in user at each client
178 our $login_users_db;
179 our $login_users_tn = "login_users";
180 my $login_users_file_name;
181 my @login_users_col_names = ("client VARCHAR(255)", "user VARCHAR(255)", "timestamp VARCHAR(14)", "regserver VARCHAR(255) DEFAULT 'localhost'");
183 # holds all fai server, the debian release and tag
184 our $fai_server_db;
185 our $fai_server_tn = "fai_server";
186 my $fai_server_file_name;
187 our @fai_server_col_names = ("timestamp VARCHAR(14)", "server VARCHAR(255)", "fai_release VARCHAR(255)", "sections VARCHAR(255)", "tag VARCHAR(255)");
189 our $fai_release_db;
190 our $fai_release_tn = "fai_release";
191 my $fai_release_file_name;
192 our @fai_release_col_names = ("timestamp VARCHAR(14)", "fai_release VARCHAR(255)", "class VARCHAR(255)", "type VARCHAR(255)", "state VARCHAR(255)");
194 # holds all packages available from different repositories
195 our $packages_list_db;
196 our $packages_list_tn = "packages_list";
197 my $packages_list_file_name;
198 our @packages_list_col_names = ("distribution VARCHAR(255)", "package VARCHAR(255)", "version VARCHAR(255)", "section VARCHAR(255)", "description TEXT", "template LONGBLOB", "timestamp VARCHAR(14)");
199 my $outdir = "/tmp/packages_list_db";
200 my $arch = "i386";
202 # holds all messages which should be delivered to a user
203 our $messaging_db;
204 our $messaging_tn = "messaging";
205 our @messaging_col_names = ("id INTEGER", "subject TEXT", "message_from VARCHAR(255)", "message_to VARCHAR(255)",
206 "flag VARCHAR(255)", "direction VARCHAR(255)", "delivery_time VARCHAR(255)", "message TEXT", "timestamp VARCHAR(14)" );
207 my $messaging_file_name;
209 # path to directory to store client install log files
210 our $client_fai_log_dir = "/var/log/fai";
212 # queue which stores taskes until one of the $max_children children are ready to process the task
213 #my @tasks = qw();
214 my @msgs_to_decrypt = qw();
215 my $max_children = 2;
218 # loop delay for job queue to look for opsi jobs
219 my $job_queue_opsi_delay = 10;
220 our $opsi_client;
221 our $opsi_url;
223 # Lifetime of logged in user information. If no update information comes after n seconds,
224 # the user is expeceted to be no longer logged in or the host is no longer running. Because
225 # of this, the user is deleted from login_users_db
226 our $logged_in_user_date_of_expiry = 600;
229 %cfg_defaults = (
230 "general" => {
231 "log-file" => [\$log_file, "/var/run/".$prg.".log"],
232 "pid-file" => [\$pid_file, "/var/run/".$prg.".pid"],
233 },
234 "server" => {
235 "ip" => [\$server_ip, "0.0.0.0"],
236 "port" => [\$server_port, "20081"],
237 "known-clients" => [\$known_clients_file_name, '/var/lib/gosa-si/clients.db' ],
238 "known-servers" => [\$known_server_file_name, '/var/lib/gosa-si/servers.db'],
239 "incoming" => [\$incoming_file_name, '/var/lib/gosa-si/incoming.db'],
240 "login-users" => [\$login_users_file_name, '/var/lib/gosa-si/users.db'],
241 "fai-server" => [\$fai_server_file_name, '/var/lib/gosa-si/fai_server.db'],
242 "fai-release" => [\$fai_release_file_name, '/var/lib/gosa-si/fai_release.db'],
243 "packages-list" => [\$packages_list_file_name, '/var/lib/gosa-si/packages.db'],
244 "messaging" => [\$messaging_file_name, '/var/lib/gosa-si/messaging.db'],
245 "foreign-clients" => [\$foreign_clients_file_name, '/var/lib/gosa-si/foreign_clients.db'],
246 "source-list" => [\$sources_list, '/etc/apt/sources.list'],
247 "repo-path" => [\$repo_path, '/srv/www/repository'],
248 "ldap-uri" => [\$ldap_uri, ""],
249 "ldap-base" => [\$ldap_base, ""],
250 "ldap-admin-dn" => [\$ldap_admin_dn, ""],
251 "ldap-admin-password" => [\$ldap_admin_password, ""],
252 "ldap-version" => [\$ldap_version, 3],
253 "max-ldap-handle" => [\$max_ldap_handle, 10],
254 "precreate-ldap-handle" => [\$precreate_ldap_handle, 5],
255 "gosa-unit-tag" => [\$gosa_unit_tag, ""],
256 "max-clients" => [\$max_clients, 10],
257 "wol-password" => [\$wake_on_lan_passwd, ""],
258 "mysql-username" => [\$mysql_username, "gosa_si"],
259 "mysql-password" => [\$mysql_password, ""],
260 "mysql-database" => [\$mysql_database, "gosa_si"],
261 "mysql-host" => [\$mysql_host, "127.0.0.1"],
262 },
263 "GOsaPackages" => {
264 "job-queue" => [\$job_queue_file_name, '/var/lib/gosa-si/jobs.db'],
265 "job-queue-loop-delay" => [\$job_queue_loop_delay, 3],
266 "messaging-db-loop-delay" => [\$messaging_db_loop_delay, 3],
267 "key" => [\$GosaPackages_key, "none"],
268 "new-systems-ou" => [\$new_systems_ou, 'ou=workstations,ou=systems'],
269 },
270 "ClientPackages" => {
271 "key" => [\$ClientPackages_key, "none"],
272 "user-date-of-expiry" => [\$logged_in_user_date_of_expiry, 600],
273 },
274 "ServerPackages"=> {
275 "address" => [\$foreign_server_string, ""],
276 "dns-lookup" => [\$dns_lookup, "true"],
277 "domain" => [\$server_domain, ""],
278 "key" => [\$ServerPackages_key, "none"],
279 "key-lifetime" => [\$foreign_servers_register_delay, 120],
280 "job-synchronization-enabled" => [\$job_synchronization, "true"],
281 "synchronization-loop" => [\$modified_jobs_loop_delay, 5],
282 },
283 "ArpHandler" => {
284 "enabled" => [\$arp_enabled, "true"],
285 "interface" => [\$arp_interface, "all"],
286 },
287 "Opsi" => {
288 "enabled" => [\$opsi_enabled, "false"],
289 "server" => [\$opsi_server, "localhost"],
290 "admin" => [\$opsi_admin, "opsi-admin"],
291 "password" => [\$opsi_password, "secret"],
292 },
294 );
297 #=== FUNCTION ================================================================
298 # NAME: usage
299 # PARAMETERS: nothing
300 # RETURNS: nothing
301 # DESCRIPTION: print out usage text to STDERR
302 #===============================================================================
303 sub usage {
304 print STDERR << "EOF" ;
305 usage: $prg [-hvf] [-c config]
307 -h : this (help) message
308 -c <file> : config file
309 -f : foreground, process will not be forked to background
310 -v : be verbose (multiple to increase verbosity)
311 -no-arp : starts $prg without connection to arp module
313 EOF
314 print "\n" ;
315 }
318 #=== FUNCTION ================================================================
319 # NAME: logging
320 # PARAMETERS: level - string - default 'info'
321 # msg - string -
322 # facility - string - default 'LOG_DAEMON'
323 # RETURNS: nothing
324 # DESCRIPTION: function for logging
325 #===============================================================================
326 sub daemon_log {
327 # log into log_file
328 my( $msg, $level ) = @_;
329 if(not defined $msg) { return }
330 if(not defined $level) { $level = 1 }
331 if(defined $log_file){
332 my $open_log_fh = sysopen(LOG_HANDLE, $log_file, O_WRONLY | O_CREAT | O_APPEND , 0440);
333 if(not $open_log_fh) {
334 print STDERR "cannot open $log_file: $!";
335 return;
336 }
337 # check owner and group of log_file and update settings if necessary
338 my ($dev, $ino, $mode, $nlink, $uid, $gid, $rdev, $size, $atime, $mtime, $ctime, $blksize, $blocks) = stat($log_file);
339 if((not $uid eq $root_uid) || (not $gid eq $adm_gid)) {
340 chown($root_uid, $adm_gid, $log_file);
341 }
343 chomp($msg);
344 #$msg =~s/\n//g; # no newlines are allowed in log messages, this is important for later log parsing
345 if($level <= $verbose){
346 my ($seconds, $minutes, $hours, $monthday, $month,
347 $year, $weekday, $yearday, $sommertime) = localtime(time);
348 $hours = $hours < 10 ? $hours = "0".$hours : $hours;
349 $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes;
350 $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds;
351 my @monthnames = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
352 $month = $monthnames[$month];
353 $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday;
354 $year+=1900;
355 my $name = $prg;
357 my $log_msg = "$month $monthday $hours:$minutes:$seconds $name $msg\n";
358 flock(LOG_HANDLE, LOCK_EX);
359 seek(LOG_HANDLE, 0, 2);
360 print LOG_HANDLE $log_msg;
361 flock(LOG_HANDLE, LOCK_UN);
362 if( $foreground ) {
363 print STDERR $log_msg;
364 }
365 }
366 close( LOG_HANDLE );
367 }
368 }
371 #=== FUNCTION ================================================================
372 # NAME: check_cmdline_param
373 # PARAMETERS: nothing
374 # RETURNS: nothing
375 # DESCRIPTION: validates commandline parameter
376 #===============================================================================
377 sub check_cmdline_param () {
378 my $err_config;
379 my $err_counter = 0;
380 if(not defined($cfg_file)) {
381 $cfg_file = "/etc/gosa-si/server.conf";
382 if(! -r $cfg_file) {
383 $err_config = "please specify a config file";
384 $err_counter += 1;
385 }
386 }
387 if( $err_counter > 0 ) {
388 &usage( "", 1 );
389 if( defined( $err_config)) { print STDERR "$err_config\n"}
390 print STDERR "\n";
391 exit( -1 );
392 }
393 }
396 #=== FUNCTION ================================================================
397 # NAME: check_pid
398 # PARAMETERS: nothing
399 # RETURNS: nothing
400 # DESCRIPTION: handels pid processing
401 #===============================================================================
402 sub check_pid {
403 $pid = -1;
404 # Check, if we are already running
405 if( open(LOCK_FILE, "<$pid_file") ) {
406 $pid = <LOCK_FILE>;
407 if( defined $pid ) {
408 chomp( $pid );
409 if( -f "/proc/$pid/stat" ) {
410 my($stat) = `cat /proc/$pid/stat` =~ m/$pid \((.+)\).*/;
411 if( $stat ) {
412 print STDERR "\nERROR: Already running!\n";
413 close( LOCK_FILE );
414 exit -1;
415 }
416 }
417 }
418 close( LOCK_FILE );
419 unlink( $pid_file );
420 }
422 # create a syslog msg if it is not to possible to open PID file
423 if (not sysopen(LOCK_FILE, $pid_file, O_WRONLY|O_CREAT|O_EXCL, 0644)) {
424 my($msg) = "Couldn't obtain lockfile '$pid_file' ";
425 if (open(LOCK_FILE, '<', $pid_file)
426 && ($pid = <LOCK_FILE>))
427 {
428 chomp($pid);
429 $msg .= "(PID $pid)\n";
430 } else {
431 $msg .= "(unable to read PID)\n";
432 }
433 if( ! ($foreground) ) {
434 openlog( $0, "cons,pid", "daemon" );
435 syslog( "warning", $msg );
436 closelog();
437 }
438 else {
439 print( STDERR " $msg " );
440 }
441 exit( -1 );
442 }
443 }
445 #=== FUNCTION ================================================================
446 # NAME: import_modules
447 # PARAMETERS: module_path - string - abs. path to the directory the modules
448 # are stored
449 # RETURNS: nothing
450 # DESCRIPTION: each file in module_path which ends with '.pm' and activation
451 # state is on is imported by "require 'file';"
452 #===============================================================================
453 sub import_modules {
454 daemon_log(" ", 1);
456 if (not -e $modules_path) {
457 daemon_log("0 ERROR: cannot find directory or directory is not readable: $modules_path", 1);
458 }
460 opendir (DIR, $modules_path) or die "ERROR while loading modules from directory $modules_path : $!\n";
462 my $ldap_handle = &get_ldap_handle;
463 while (defined (my $file = readdir (DIR))) {
464 if (not $file =~ /(\S*?).pm$/) {
465 next;
466 }
467 my $mod_name = $1;
469 # ArpHandler switch
470 if( $file =~ /ArpHandler.pm/ ) {
471 if( $arp_enabled eq "false" ) { next; }
472 }
474 eval { require $file; };
475 if ($@) {
476 daemon_log("0 ERROR: gosa-si-server could not load module $file", 1);
477 daemon_log("$@", 1);
478 exit;
479 } else {
480 my $info = eval($mod_name.'::get_module_info($ldap_handle)');
481 # Only load module if get_module_info() returns a non-null object
482 if( $info ) {
483 my ($input_address, $input_key, $event_hash) = @{$info};
484 $known_modules->{$mod_name} = $info;
485 daemon_log("0 INFO: module $mod_name loaded", 5);
486 }
487 }
488 }
489 &release_ldap_handle($ldap_handle);
490 close (DIR);
491 }
493 #=== FUNCTION ================================================================
494 # NAME: password_check
495 # PARAMETERS: nothing
496 # RETURNS: nothing
497 # DESCRIPTION: escalates an critical error if two modules exist which are avaialable by
498 # the same password
499 #===============================================================================
500 sub password_check {
501 my $passwd_hash = {};
502 while (my ($mod_name, $mod_info) = each %$known_modules) {
503 my $mod_passwd = @$mod_info[1];
504 if (not defined $mod_passwd) { next; }
505 if (not exists $passwd_hash->{$mod_passwd}) {
506 $passwd_hash->{$mod_passwd} = $mod_name;
508 # escalates critical error
509 } else {
510 &daemon_log("0 ERROR: two loaded modules do have the same password. Please modify the 'key'-parameter in config file");
511 &daemon_log("0 ERROR: module='$mod_name' and module='".$passwd_hash->{$mod_passwd}."'");
512 exit( -1 );
513 }
514 }
516 }
519 #=== FUNCTION ================================================================
520 # NAME: sig_int_handler
521 # PARAMETERS: signal - string - signal arose from system
522 # RETURNS: nothing
523 # DESCRIPTION: handels tasks to be done befor signal becomes active
524 #===============================================================================
525 sub sig_int_handler {
526 my ($signal) = @_;
528 # if (defined($ldap_handle)) {
529 # $ldap_handle->disconnect;
530 # }
531 # TODO alle verbliebenden ldap verbindungen aus allen heaps beenden
534 daemon_log("shutting down gosa-si-server", 1);
535 system("kill `ps -C gosa-si-server -o pid=`");
536 }
537 $SIG{INT} = \&sig_int_handler;
540 sub check_key_and_xml_validity {
541 my ($crypted_msg, $module_key, $session_id) = @_;
542 my $msg;
543 my $msg_hash;
544 my $error_string;
545 eval{
546 $msg = &decrypt_msg($crypted_msg, $module_key);
548 if ($msg =~ /<xml>/i){
549 $msg =~ s/\s+/ /g; # just for better daemon_log
550 daemon_log("$session_id DEBUG: decrypted_msg: \n$msg", 9);
551 $msg_hash = $xml->XMLin($msg, ForceArray=>1);
553 ##############
554 # check header
555 if( not exists $msg_hash->{'header'} ) { die "no header specified"; }
556 my $header_l = $msg_hash->{'header'};
557 if( (1 > @{$header_l}) || ( ( 'HASH' eq ref @{$header_l}[0]) && (1 > keys %{@{$header_l}[0]}) ) ) { die 'empty header tag'; }
558 if( 1 < @{$header_l} ) { die 'more than one header specified'; }
559 my $header = @{$header_l}[0];
560 if( 0 == length $header) { die 'empty string in header tag'; }
562 ##############
563 # check source
564 if( not exists $msg_hash->{'source'} ) { die "no source specified"; }
565 my $source_l = $msg_hash->{'source'};
566 if( (1 > @{$source_l}) || ( ( 'HASH' eq ref @{$source_l}[0]) && (1 > keys %{@{$source_l}[0]}) ) ) { die 'empty source tag'; }
567 if( 1 < @{$source_l} ) { die 'more than one source specified'; }
568 my $source = @{$source_l}[0];
569 if( 0 == length $source) { die 'source error'; }
571 ##############
572 # check target
573 if( not exists $msg_hash->{'target'} ) { die "no target specified"; }
574 my $target_l = $msg_hash->{'target'};
575 if( (1 > @{$target_l}) || ( ('HASH' eq ref @{$target_l}[0]) && (1 > keys %{@{$target_l}[0]}) ) ) { die 'empty target tag'; }
576 }
577 };
578 if($@) {
579 daemon_log("$session_id ERROR: do not understand the message: $@", 1);
580 $msg = undef;
581 $msg_hash = undef;
582 }
584 return ($msg, $msg_hash);
585 }
588 sub check_outgoing_xml_validity {
589 my ($msg, $session_id) = @_;
591 my $msg_hash;
592 eval{
593 $msg_hash = $xml->XMLin($msg, ForceArray=>1);
595 ##############
596 # check header
597 my $header_l = $msg_hash->{'header'};
598 if( 1 != @{$header_l} ) {
599 die 'no or more than one headers specified';
600 }
601 my $header = @{$header_l}[0];
602 if( 0 == length $header) {
603 die 'header has length 0';
604 }
606 ##############
607 # check source
608 my $source_l = $msg_hash->{'source'};
609 if( 1 != @{$source_l} ) {
610 die 'no or more than 1 sources specified';
611 }
612 my $source = @{$source_l}[0];
613 if( 0 == length $source) {
614 die 'source has length 0';
615 }
617 # Check if source contains hostname instead of ip address
618 if(not $source =~ /^[a-z0-9\.]+:\d+$/i) {
619 my ($hostname,$port) = split(/:/, $source);
620 my $ip_address = inet_ntoa(scalar gethostbyname($hostname));
621 if(defined($ip_address) && $ip_address =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/ && $port =~ /^\d+$/) {
622 # Write ip address to $source variable
623 $source = "$ip_address:$port";
624 }
625 }
626 unless( $source =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
627 $source =~ /^GOSA$/i) {
628 die "source '$source' is neither a complete ip-address with port nor 'GOSA'";
629 }
631 ##############
632 # check target
633 my $target_l = $msg_hash->{'target'};
634 if( 0 == @{$target_l} ) {
635 die "no targets specified";
636 }
637 foreach my $target (@$target_l) {
638 if( 0 == length $target) {
639 die "target has length 0";
640 }
641 unless( $target =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
642 $target =~ /^GOSA$/i ||
643 $target =~ /^\*$/ ||
644 $target =~ /KNOWN_SERVER/i ||
645 $target =~ /JOBDB/i ||
646 $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 ){
647 die "target '$target' is not a complete ip-address with port or a valid target name or a mac-address";
648 }
649 }
650 };
651 if($@) {
652 daemon_log("$session_id ERROR: outgoing msg is not gosa-si envelope conform: $@", 1);
653 daemon_log("$@ ".(defined($msg) && length($msg)>0)?$msg:"Empty Message", 1);
654 $msg_hash = undef;
655 }
657 return ($msg_hash);
658 }
661 sub input_from_known_server {
662 my ($input, $remote_ip, $session_id) = @_ ;
663 my ($msg, $msg_hash, $module);
665 my $sql_statement= "SELECT * FROM known_server";
666 my $query_res = $known_server_db->select_dbentry( $sql_statement );
668 while( my ($hit_num, $hit) = each %{ $query_res } ) {
669 my $host_name = $hit->{hostname};
670 if( not $host_name =~ "^$remote_ip") {
671 next;
672 }
673 my $host_key = $hit->{hostkey};
674 daemon_log("$session_id DEBUG: input_from_known_server: host_name: $host_name", 7);
675 daemon_log("$session_id DEBUG: input_from_known_server: host_key: $host_key", 7);
677 # check if module can open msg envelope with module key
678 my ($tmp_msg, $tmp_msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
679 if( (!$tmp_msg) || (!$tmp_msg_hash) ) {
680 daemon_log("$session_id DEBUG: input_from_known_server: deciphering raise error", 7);
681 daemon_log("$@", 8);
682 next;
683 }
684 else {
685 $msg = $tmp_msg;
686 $msg_hash = $tmp_msg_hash;
687 $module = "ServerPackages";
688 daemon_log("$session_id DEBUG: check_key_and_xml_validity... ok", 7);
689 last;
690 }
691 }
693 if( (!$msg) || (!$msg_hash) || (!$module) ) {
694 daemon_log("$session_id DEBUG: Incoming message is not from a known server", 7);
695 }
697 return ($msg, $msg_hash, $module);
698 }
701 sub input_from_known_client {
702 my ($input, $remote_ip, $session_id) = @_ ;
703 my ($msg, $msg_hash, $module);
705 my $sql_statement= "SELECT * FROM known_clients";
706 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
707 while( my ($hit_num, $hit) = each %{ $query_res } ) {
708 my $host_name = $hit->{hostname};
709 if( not $host_name =~ /^$remote_ip:\d*$/) {
710 next;
711 }
712 my $host_key = $hit->{hostkey};
713 &daemon_log("$session_id DEBUG: input_from_known_client: host_name: $host_name", 7);
714 &daemon_log("$session_id DEBUG: input_from_known_client: host_key: $host_key", 7);
716 # check if module can open msg envelope with module key
717 ($msg, $msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
719 if( (!$msg) || (!$msg_hash) ) {
720 &daemon_log("$session_id DEGUG: input_from_known_client: deciphering raise error", 7);
721 &daemon_log("$@", 8);
722 next;
723 }
724 else {
725 $module = "ClientPackages";
726 daemon_log("$session_id DEBUG: check_key_and_xml_validity... ok", 7);
727 last;
728 }
729 }
731 if( (!$msg) || (!$msg_hash) || (!$module) ) {
732 &daemon_log("$session_id DEBUG: Incoming message is not from a known client", 7);
733 }
735 return ($msg, $msg_hash, $module);
736 }
739 sub input_from_unknown_host {
740 no strict "refs";
741 my ($input, $session_id) = @_ ;
742 my ($msg, $msg_hash, $module);
743 my $error_string;
745 my %act_modules = %$known_modules;
747 while( my ($mod, $info) = each(%act_modules)) {
749 # check a key exists for this module
750 my $module_key = ${$mod."_key"};
751 if( not defined $module_key ) {
752 if( $mod eq 'ArpHandler' ) {
753 next;
754 }
755 daemon_log("$session_id ERROR: no key specified in config file for $mod", 1);
756 next;
757 }
758 daemon_log("$session_id DEBUG: $mod: $module_key", 7);
760 # check if module can open msg envelope with module key
761 ($msg, $msg_hash) = &check_key_and_xml_validity($input, $module_key, $session_id);
762 if( (not defined $msg) || (not defined $msg_hash) ) {
763 next;
764 } else {
765 $module = $mod;
766 daemon_log("$session_id DEBUG: check_key_and_xml_validity... ok", 7);
767 last;
768 }
769 }
771 if( (!$msg) || (!$msg_hash) || (!$module)) {
772 daemon_log("$session_id DEBUG: Incoming message is not from an unknown host", 7);
773 }
775 return ($msg, $msg_hash, $module);
776 }
779 sub create_ciphering {
780 my ($passwd) = @_;
781 if((!defined($passwd)) || length($passwd)==0) {
782 $passwd = "";
783 }
784 $passwd = substr(md5_hex("$passwd") x 32, 0, 32);
785 my $iv = substr(md5_hex('GONICUS GmbH'),0, 16);
786 my $my_cipher = Crypt::Rijndael->new($passwd , Crypt::Rijndael::MODE_CBC());
787 $my_cipher->set_iv($iv);
788 return $my_cipher;
789 }
792 sub encrypt_msg {
793 my ($msg, $key) = @_;
794 my $my_cipher = &create_ciphering($key);
795 my $len;
796 {
797 use bytes;
798 $len= 16-length($msg)%16;
799 }
800 $msg = "\0"x($len).$msg;
801 $msg = $my_cipher->encrypt($msg);
802 chomp($msg = &encode_base64($msg));
803 # there are no newlines allowed inside msg
804 $msg=~ s/\n//g;
805 return $msg;
806 }
809 sub decrypt_msg {
811 my ($msg, $key) = @_ ;
812 $msg = &decode_base64($msg);
813 my $my_cipher = &create_ciphering($key);
814 $msg = $my_cipher->decrypt($msg);
815 $msg =~ s/\0*//g;
816 return $msg;
817 }
820 sub get_encrypt_key {
821 my ($target) = @_ ;
822 my $encrypt_key;
823 my $error = 0;
825 # target can be in known_server
826 if( not defined $encrypt_key ) {
827 my $sql_statement= "SELECT * FROM known_server WHERE hostname='$target'";
828 my $query_res = $known_server_db->select_dbentry( $sql_statement );
829 while( my ($hit_num, $hit) = each %{ $query_res } ) {
830 my $host_name = $hit->{hostname};
831 if( $host_name ne $target ) {
832 next;
833 }
834 $encrypt_key = $hit->{hostkey};
835 last;
836 }
837 }
839 # target can be in known_client
840 if( not defined $encrypt_key ) {
841 my $sql_statement= "SELECT * FROM known_clients WHERE hostname='$target'";
842 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
843 while( my ($hit_num, $hit) = each %{ $query_res } ) {
844 my $host_name = $hit->{hostname};
845 if( $host_name ne $target ) {
846 next;
847 }
848 $encrypt_key = $hit->{hostkey};
849 last;
850 }
851 }
853 return $encrypt_key;
854 }
857 #=== FUNCTION ================================================================
858 # NAME: open_socket
859 # PARAMETERS: PeerAddr string something like 192.168.1.1 or 192.168.1.1:10000
860 # [PeerPort] string necessary if port not appended by PeerAddr
861 # RETURNS: socket IO::Socket::INET
862 # DESCRIPTION: open a socket to PeerAddr
863 #===============================================================================
864 sub open_socket {
865 my ($PeerAddr, $PeerPort) = @_ ;
866 if(defined($PeerPort)){
867 $PeerAddr = $PeerAddr.":".$PeerPort;
868 }
869 my $socket;
870 $socket = new IO::Socket::INET(PeerAddr => $PeerAddr,
871 Porto => "tcp",
872 Type => SOCK_STREAM,
873 Timeout => 5,
874 );
875 if(not defined $socket) {
876 return;
877 }
878 # &daemon_log("DEBUG: open_socket: $PeerAddr", 7);
879 return $socket;
880 }
883 sub send_msg_to_target {
884 my ($msg, $address, $encrypt_key, $msg_header, $session_id) = @_ ;
885 my $error = 0;
886 my $header;
887 my $timestamp = &get_time();
888 my $new_status;
889 my $act_status;
890 my ($sql_statement, $res);
892 if( $msg_header ) {
893 $header = "'$msg_header'-";
894 } else {
895 $header = "";
896 }
898 # Patch the source ip
899 if($msg =~ /<source>0\.0\.0\.0:\d*?<\/source>/) {
900 my $remote_ip = &get_local_ip_for_remote_ip(sprintf("%s", $address =~ /^([0-9\.]*?):.*$/));
901 $msg =~ s/<source>(0\.0\.0\.0):(\d*?)<\/source>/<source>$remote_ip:$2<\/source>/s;
902 }
904 # encrypt xml msg
905 my $crypted_msg = &encrypt_msg($msg, $encrypt_key);
907 # opensocket
908 my $socket = &open_socket($address);
909 if( !$socket ) {
910 daemon_log("$session_id WARNING: cannot send ".$header."msg to $address , host not reachable", 3);
911 $error++;
912 }
914 if( $error == 0 ) {
915 # send xml msg
916 print $socket $crypted_msg."\n";
918 daemon_log("$session_id INFO: send ".$header."msg to $address", 5);
919 daemon_log("$session_id DEBUG: message:\n$msg", 9);
921 }
923 # close socket in any case
924 if( $socket ) {
925 close $socket;
926 }
928 if( $error > 0 ) { $new_status = "down"; }
929 else { $new_status = $msg_header; }
932 # known_clients
933 $sql_statement = "SELECT * FROM $known_clients_tn WHERE hostname='$address'";
934 $res = $known_clients_db->select_dbentry($sql_statement);
935 if( keys(%$res) == 1) {
936 $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
937 if ($act_status eq "down" && $new_status eq "down") {
938 $sql_statement = "DELETE FROM known_clients WHERE hostname='$address'";
939 $res = $known_clients_db->del_dbentry($sql_statement);
940 daemon_log("$session_id WARNING: failed 2x to send msg to host '$address', delete host from known_clients", 3);
941 } else {
942 $sql_statement = "UPDATE known_clients SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
943 $res = $known_clients_db->update_dbentry($sql_statement);
944 if($new_status eq "down"){
945 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
946 } else {
947 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
948 }
949 }
950 }
952 # known_server
953 $sql_statement = "SELECT * FROM $known_server_tn WHERE hostname='$address'";
954 $res = $known_server_db->select_dbentry($sql_statement);
955 if( keys(%$res) == 1) {
956 $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
957 if ($act_status eq "down" && $new_status eq "down") {
958 $sql_statement = "DELETE FROM known_server WHERE hostname='$address'";
959 $res = $known_server_db->del_dbentry($sql_statement);
960 daemon_log("$session_id WARNING: failed 2x to send a message to host '$address', delete host from known_server", 3);
961 }
962 else {
963 $sql_statement = "UPDATE known_server SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
964 $res = $known_server_db->update_dbentry($sql_statement);
965 if($new_status eq "down"){
966 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
967 } else {
968 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
969 }
970 }
971 }
972 return $error;
973 }
976 sub update_jobdb_status_for_send_msgs {
977 my ($session_id, $answer, $error) = @_;
978 &daemon_log("$session_id DEBUG: try to update job status", 7);
979 if( $answer =~ /<jobdb_id>(\d+)<\/jobdb_id>/ ) {
980 my $jobdb_id = $1;
982 $answer =~ /<header>(.*)<\/header>/;
983 my $job_header = $1;
985 $answer =~ /<target>(.*)<\/target>/;
986 my $job_target = $1;
988 # Sending msg failed
989 if( $error ) {
991 # Set jobs to done, jobs do not need to deliver their message in any case
992 if (($job_header eq "trigger_action_localboot")
993 ||($job_header eq "trigger_action_lock")
994 ||($job_header eq "trigger_action_halt")
995 ) {
996 my $sql_statement = "UPDATE $job_queue_tn SET status='done' WHERE id=$jobdb_id";
997 &daemon_log("$session_id DEBUG: $sql_statement", 7);
998 my $res = $job_db->update_dbentry($sql_statement);
1000 # Reactivate jobs, jobs need to deliver their message
1001 } elsif (($job_header eq "trigger_action_activate")
1002 ||($job_header eq "trigger_action_update")
1003 ||($job_header eq "trigger_action_reinstall")
1004 ||($job_header eq "trigger_activate_new")
1005 ) {
1006 &reactivate_job_with_delay($session_id, $job_target, $job_header, 30 );
1008 # For all other messages
1009 } else {
1010 my $sql_statement = "UPDATE $job_queue_tn ".
1011 "SET status='error', result='can not deliver msg, please consult log file' ".
1012 "WHERE id=$jobdb_id";
1013 &daemon_log("$session_id DEBUG: $sql_statement", 7);
1014 my $res = $job_db->update_dbentry($sql_statement);
1015 }
1017 # Sending msg was successful
1018 } else {
1019 # Set jobs localboot, lock, activate, halt, reboot and wake to done
1020 # jobs reinstall, update, inst_update do themself setting to done
1021 if (($job_header eq "trigger_action_localboot")
1022 ||($job_header eq "trigger_action_lock")
1023 ||($job_header eq "trigger_action_activate")
1024 ||($job_header eq "trigger_action_halt")
1025 ||($job_header eq "trigger_action_reboot")
1026 ||($job_header eq "trigger_action_wake")
1027 ||($job_header eq "trigger_wake")
1028 ) {
1030 my $sql_statement = "UPDATE $job_queue_tn ".
1031 "SET status='done' ".
1032 "WHERE id=$jobdb_id AND status='processed'";
1033 &daemon_log("$session_id DEBUG: $sql_statement", 7);
1034 my $res = $job_db->update_dbentry($sql_statement);
1035 } else {
1036 &daemon_log("$session_id DEBUG: sending message succeed but cannot update job status.", 7);
1037 }
1038 }
1039 } else {
1040 &daemon_log("$session_id DEBUG: cannot update job status, msg has no jobdb_id-tag: $answer", 7);
1041 }
1042 }
1044 sub reactivate_job_with_delay {
1045 my ($session_id, $target, $header, $delay) = @_ ;
1046 # Sometimes the client is still booting or does not wake up, in this case reactivate the job (if it exists) with a delay of n sec
1048 if (not defined $delay) { $delay = 30 } ;
1049 my $delay_timestamp = &calc_timestamp(&get_time(), "plus", $delay);
1051 my $sql = "UPDATE $job_queue_tn Set timestamp='$delay_timestamp', status='waiting' WHERE (macaddress LIKE 'target' AND headertag='$header')";
1052 my $res = $job_db->update_dbentry($sql);
1053 daemon_log("$session_id INFO: '$header'-job will be reactivated at '$delay_timestamp' ".
1054 "cause client '$target' is currently not available", 5);
1055 daemon_log("$session_id $sql", 7);
1056 return;
1057 }
1060 sub sig_handler {
1061 my ($kernel, $signal) = @_[KERNEL, ARG0] ;
1062 daemon_log("0 INFO got signal '$signal'", 1);
1063 $kernel->sig_handled();
1064 return;
1065 }
1068 sub msg_to_decrypt {
1069 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
1070 my $session_id = $session->ID;
1071 my ($msg, $msg_hash, $module);
1072 my $error = 0;
1074 # fetch new msg out of @msgs_to_decrypt
1075 my $tmp_next_msg = shift @msgs_to_decrypt;
1076 my ($next_msg, $msg_source) = split(/;/, $tmp_next_msg);
1078 # msg is from a new client or gosa
1079 ($msg, $msg_hash, $module) = &input_from_unknown_host($next_msg, $session_id);
1081 # msg is from a gosa-si-server
1082 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1083 ($msg, $msg_hash, $module) = &input_from_known_server($next_msg, $heap->{'remote_ip'}, $session_id);
1084 }
1085 # msg is from a gosa-si-client
1086 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1087 ($msg, $msg_hash, $module) = &input_from_known_client($next_msg, $heap->{'remote_ip'}, $session_id);
1088 }
1089 # an error occurred
1090 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1091 # if an incoming msg could not be decrypted (maybe a wrong key), send client a ping. If the client
1092 # could not understand a msg from its server the client cause a re-registering process
1093 my $remote_ip = $heap->{'remote_ip'};
1094 my $remote_port = $heap->{'remote_port'};
1095 my $ping_msg = "<xml> <header>gosa_ping</header> <source>$server_address</source><target>$msg_source</target></xml>";
1096 my ($test_error, $test_error_string) = &send_msg_to_target($ping_msg, "$msg_source", "dummy-key", "gosa_ping", $session_id);
1098 daemon_log("$session_id WARNING cannot understand incoming msg, send 'ping'-msg to all host with ip '".$heap->{remote_ip}.
1099 "' to cause a re-registering of the client if necessary", 3);
1100 $error++;
1101 }
1104 my $header;
1105 my $target;
1106 my $source;
1107 my $done = 0;
1108 my $sql;
1109 my $res;
1111 # check whether this message should be processed here
1112 if ($error == 0) {
1113 $header = @{$msg_hash->{'header'}}[0];
1114 $target = @{$msg_hash->{'target'}}[0];
1115 $source = @{$msg_hash->{'source'}}[0];
1116 my $not_found_in_known_clients_db = 0;
1117 my $not_found_in_known_server_db = 0;
1118 my $not_found_in_foreign_clients_db = 0;
1119 my $local_address;
1120 my $local_mac;
1121 my ($target_ip, $target_port) = split(':', $target);
1123 # Determine the local ip address if target is an ip address
1124 if ($target =~ /^\d+\.\d+\.\d+\.\d+:\d+$/) {
1125 $local_address = &get_local_ip_for_remote_ip($target_ip).":$server_port";
1126 } else {
1127 $local_address = $server_address;
1128 }
1130 # Determine the local mac address if target is a mac address
1131 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) {
1132 my $loc_ip = &get_local_ip_for_remote_ip($heap->{'remote_ip'});
1133 my $network_interface= &get_interface_for_ip($loc_ip);
1134 $local_mac = &get_mac_for_interface($network_interface);
1135 } else {
1136 $local_mac = $server_mac_address;
1137 }
1139 # target and source is equal to GOSA -> process here
1140 if (not $done) {
1141 if ($target eq "GOSA" && $source eq "GOSA") {
1142 $done = 1;
1143 &daemon_log("$session_id DEBUG: target and source is 'GOSA' -> process here", 7);
1144 }
1145 }
1147 # target is own address without forward_to_gosa-tag -> process here
1148 if (not $done) {
1149 #if ((($target eq $local_address) || ($target eq $local_mac) ) && (not exists $msg_hash->{'forward_to_gosa'})) {
1150 if (($target eq $local_address) && (not exists $msg_hash->{'forward_to_gosa'})) {
1151 $done = 1;
1152 if ($source eq "GOSA") {
1153 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1154 }
1155 &daemon_log("$session_id DEBUG: target is own address without forward_to_gosa-tag -> process here", 7);
1156 }
1157 }
1159 # target is a client address in known_clients -> process here
1160 if (not $done) {
1161 $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1162 $res = $known_clients_db->select_dbentry($sql);
1163 if (keys(%$res) > 0) {
1164 $done = 1;
1165 my $hostname = $res->{1}->{'hostname'};
1166 $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1167 my $local_address = &get_local_ip_for_remote_ip($target_ip).":$server_port";
1168 if ($source eq "GOSA") {
1169 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1170 }
1171 &daemon_log("$session_id DEBUG: target is a client address in known_clients -> process here", 7);
1173 } else {
1174 $not_found_in_known_clients_db = 1;
1175 }
1176 }
1178 # target ist own address with forward_to_gosa-tag not pointing to myself -> process here
1179 if (not $done) {
1180 my $forward_to_gosa = @{$msg_hash->{'forward_to_gosa'}}[0];
1181 my $gosa_at;
1182 my $gosa_session_id;
1183 if (($target eq $local_address) && (defined $forward_to_gosa)){
1184 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1185 if ($gosa_at ne $local_address) {
1186 $done = 1;
1187 &daemon_log("$session_id DEBUG: target is own address with forward_to_gosa-tag not pointing to myself -> process here", 7);
1188 }
1189 }
1190 }
1192 # if message should be processed here -> add message to incoming_db
1193 if ($done) {
1194 # if a job or a gosa message comes from a foreign server, fake module to GosaPackages
1195 # so gosa-si-server knows how to process this kind of messages
1196 if ($header =~ /^gosa_/ || $header =~ /^job_/) {
1197 $module = "GosaPackages";
1198 }
1200 my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1201 primkey=>[],
1202 headertag=>$header,
1203 targettag=>$target,
1204 xmlmessage=>&encode_base64($msg),
1205 timestamp=>&get_time,
1206 module=>$module,
1207 sessionid=>$session_id,
1208 } );
1210 }
1212 # target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa
1213 if (not $done) {
1214 my $forward_to_gosa = @{$msg_hash->{'forward_to_gosa'}}[0];
1215 my $gosa_at;
1216 my $gosa_session_id;
1217 if (($target eq $local_address) && (defined $forward_to_gosa)){
1218 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1219 if ($gosa_at eq $local_address) {
1220 my $session_reference = $kernel->ID_id_to_session($gosa_session_id);
1221 if( defined $session_reference ) {
1222 $heap = $session_reference->get_heap();
1223 }
1224 if(exists $heap->{'client'}) {
1225 $msg = &encrypt_msg($msg, $GosaPackages_key);
1226 $heap->{'client'}->put($msg);
1227 &daemon_log("$session_id INFO: incoming '$header' message forwarded to GOsa", 5);
1228 }
1229 $done = 1;
1230 &daemon_log("$session_id DEBUG: target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa", 7);
1231 }
1232 }
1234 }
1236 # target is a client address in foreign_clients -> forward to registration server
1237 if (not $done) {
1238 $sql = "SELECT * FROM $foreign_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1239 $res = $foreign_clients_db->select_dbentry($sql);
1240 if (keys(%$res) > 0) {
1241 my $hostname = $res->{1}->{'hostname'};
1242 my ($host_ip, $host_port) = split(/:/, $hostname);
1243 my $local_address = &get_local_ip_for_remote_ip($host_ip).":$server_port";
1244 my $regserver = $res->{1}->{'regserver'};
1245 my $sql = "SELECT * FROM $known_server_tn WHERE hostname='$regserver'";
1246 my $res = $known_server_db->select_dbentry($sql);
1247 if (keys(%$res) > 0) {
1248 my $regserver_key = $res->{1}->{'hostkey'};
1249 $msg =~ s/<source>GOSA<\/source>/<source>$local_address<\/source>/;
1250 $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1251 if ($source eq "GOSA") {
1252 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1253 }
1254 my $error= &send_msg_to_target($msg, $regserver, $regserver_key, $header, $session_id);
1255 if ($error) {
1256 &daemon_log("$session_id ERROR: some problems (error=$error) occurred while trying to send msg to registration server: $msg", 1);
1257 }
1258 }
1259 $done = 1;
1260 &daemon_log("$session_id DEBUG: target is a client address in foreign_clients -> forward to registration server", 7);
1261 } else {
1262 $not_found_in_foreign_clients_db = 1;
1263 }
1264 }
1266 # target is a server address -> forward to server
1267 if (not $done) {
1268 $sql = "SELECT * FROM $known_server_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1269 $res = $known_server_db->select_dbentry($sql);
1270 if (keys(%$res) > 0) {
1271 my $hostkey = $res->{1}->{'hostkey'};
1273 if ($source eq "GOSA") {
1274 $msg =~ s/<source>GOSA<\/source>/<source>$local_address<\/source>/;
1275 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1277 }
1279 &send_msg_to_target($msg, $target, $hostkey, $header, $session_id);
1280 $done = 1;
1281 &daemon_log("$session_id DEBUG: target is a server address -> forward to server", 7);
1282 } else {
1283 $not_found_in_known_server_db = 1;
1284 }
1285 }
1288 # target is not in foreign_clients_db, known_server_db or known_clients_db, maybe it is a complete new one -> process here
1289 if ( $not_found_in_foreign_clients_db
1290 && $not_found_in_known_server_db
1291 && $not_found_in_known_clients_db) {
1292 &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);
1293 if ($header =~ /^gosa_/ || $header =~ /^job_/) {
1294 $module = "GosaPackages";
1295 }
1296 my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1297 primkey=>[],
1298 headertag=>$header,
1299 targettag=>$target,
1300 xmlmessage=>&encode_base64($msg),
1301 timestamp=>&get_time,
1302 module=>$module,
1303 sessionid=>$session_id,
1304 } );
1305 $done = 1;
1306 }
1309 if (not $done) {
1310 daemon_log("$session_id ERROR: do not know what to do with this message: $msg", 1);
1311 if ($source eq "GOSA") {
1312 my %data = ('error_msg' => &encode_base64($msg), 'error_string' => "Do not know what to do with this message!");
1313 my $error_msg = &build_msg("error", $local_address, "GOSA", \%data );
1315 my $session_reference = $kernel->ID_id_to_session($session_id);
1316 if( defined $session_reference ) {
1317 $heap = $session_reference->get_heap();
1318 }
1319 if(exists $heap->{'client'}) {
1320 $error_msg = &encrypt_msg($error_msg, $GosaPackages_key);
1321 $heap->{'client'}->put($error_msg);
1322 }
1323 }
1324 }
1326 }
1328 return;
1329 }
1332 sub next_task {
1333 my ($session, $heap, $task, $ldap_handle) = @_[SESSION, HEAP, ARG0, ARG1];
1334 my $running_task = POE::Wheel::Run->new(
1335 Program => sub { process_task($session, $heap, $task, $ldap_handle) },
1336 StdioFilter => POE::Filter::Reference->new(),
1337 StdoutEvent => "task_result",
1338 StderrEvent => "task_debug",
1339 CloseEvent => "task_done",
1340 );
1341 $heap->{task}->{ $running_task->ID } = $running_task;
1342 $heap->{ldap_handle}->{$running_task->ID} = $ldap_handle;
1343 }
1345 sub handle_task_result {
1346 my ($kernel, $heap, $result) = @_[KERNEL, HEAP, ARG0];
1347 my $client_answer = $result->{'answer'};
1348 if( $client_answer =~ s/session_id=(\d+)$// ) {
1349 my $session_id = $1;
1350 if( defined $session_id ) {
1351 my $session_reference = $kernel->ID_id_to_session($session_id);
1352 if( defined $session_reference ) {
1353 $heap = $session_reference->get_heap();
1354 }
1355 }
1357 if(exists $heap->{'client'}) {
1358 $heap->{'client'}->put($client_answer);
1359 }
1360 }
1361 $kernel->sig(CHLD => "child_reap");
1362 }
1364 sub handle_task_debug {
1365 my $result = $_[ARG0];
1366 print STDERR "$result\n";
1367 }
1369 sub handle_task_done {
1370 my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1371 delete $heap->{task}->{$task_id};
1372 if (exists $heap->{ldap_handle}->{$task_id}) {
1373 &release_ldap_handle($heap->{ldap_handle}->{$task_id});
1374 }
1375 }
1377 sub process_task {
1378 no strict "refs";
1379 #CHECK: Not @_[...]?
1380 my ($session, $heap, $task, $ldap_handle) = @_;
1381 my $error = 0;
1382 my $answer_l;
1383 my ($answer_header, @answer_target_l, $answer_source);
1384 my $client_answer = "";
1386 # prepare all variables needed to process message
1387 #my $msg = $task->{'xmlmessage'};
1388 my $msg = &decode_base64($task->{'xmlmessage'});
1389 my $incoming_id = $task->{'id'};
1390 my $module = $task->{'module'};
1391 my $header = $task->{'headertag'};
1392 my $session_id = $task->{'sessionid'};
1393 my $msg_hash;
1394 eval {
1395 $msg_hash = $xml->XMLin($msg, ForceArray=>1);
1396 };
1397 daemon_log("ERROR: XML failure '$@'") if ($@);
1398 my $source = @{$msg_hash->{'source'}}[0];
1400 # set timestamp of incoming client uptodate, so client will not
1401 # be deleted from known_clients because of expiration
1402 my $cur_time = &get_time();
1403 my $sql = "UPDATE $known_clients_tn SET timestamp='$cur_time' WHERE hostname='$source'";
1404 my $res = $known_clients_db->exec_statement($sql);
1406 ######################
1407 # process incoming msg
1408 if( $error == 0) {
1409 daemon_log("$session_id INFO: Incoming msg (session_id=$session_id) with header '".@{$msg_hash->{'header'}}[0]."'", 5);
1410 daemon_log("$session_id DEBUG: Processing module ".$module, 7);
1411 $answer_l = &{ $module."::process_incoming_msg" }($msg, $msg_hash, $session_id, $ldap_handle);
1413 if ( 0 < @{$answer_l} ) {
1414 my $answer_str = join("\n", @{$answer_l});
1415 while ($answer_str =~ /<header>(\w+)<\/header>/g) {
1416 daemon_log("$session_id INFO: got answer message with header '$1'", 5);
1417 }
1418 daemon_log("$session_id DEBUG: $module: got answer from module: \n".$answer_str,9);
1419 } else {
1420 daemon_log("$session_id DEBUG: $module: got no answer from module!" ,7);
1421 }
1423 }
1424 if( !$answer_l ) { $error++ };
1426 ########
1427 # answer
1428 if( $error == 0 ) {
1430 foreach my $answer ( @{$answer_l} ) {
1431 # check outgoing msg to xml validity
1432 my $answer_hash = &check_outgoing_xml_validity($answer, $session_id);
1433 if( not defined $answer_hash ) { next; }
1435 $answer_header = @{$answer_hash->{'header'}}[0];
1436 @answer_target_l = @{$answer_hash->{'target'}};
1437 $answer_source = @{$answer_hash->{'source'}}[0];
1439 # deliver msg to all targets
1440 foreach my $answer_target ( @answer_target_l ) {
1442 # targets of msg are all gosa-si-clients in known_clients_db
1443 if( $answer_target eq "*" ) {
1444 # answer is for all clients
1445 my $sql_statement= "SELECT * FROM known_clients";
1446 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1447 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1448 my $host_name = $hit->{hostname};
1449 my $host_key = $hit->{hostkey};
1450 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1451 &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1452 }
1453 }
1455 # targets of msg are all gosa-si-server in known_server_db
1456 elsif( $answer_target eq "KNOWN_SERVER" ) {
1457 # answer is for all server in known_server
1458 my $sql_statement= "SELECT * FROM $known_server_tn";
1459 my $query_res = $known_server_db->select_dbentry( $sql_statement );
1460 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1461 my $host_name = $hit->{hostname};
1462 my $host_key = $hit->{hostkey};
1463 $answer =~ s/<target>\S+<\/target>/<target>$host_name<\/target>/g;
1464 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1465 &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1466 }
1467 }
1469 # target of msg is GOsa
1470 elsif( $answer_target eq "GOSA" ) {
1471 my $session_id = ($1) if $answer =~ /<session_id>(\d+?)<\/session_id>/;
1472 my $add_on = "";
1473 if( defined $session_id ) {
1474 $add_on = ".session_id=$session_id";
1475 }
1476 # answer is for GOSA and has to returned to connected client
1477 my $gosa_answer = &encrypt_msg($answer, $GosaPackages_key);
1478 $client_answer = $gosa_answer.$add_on;
1479 }
1481 # target of msg is job queue at this host
1482 elsif( $answer_target eq "JOBDB") {
1483 $answer =~ /<header>(\S+)<\/header>/;
1484 my $header;
1485 if( defined $1 ) { $header = $1; }
1486 my $error = &send_msg_to_target($answer, $server_address, $GosaPackages_key, $header, $session_id);
1487 &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1488 }
1490 # Target of msg is a mac address
1491 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 ) {
1492 daemon_log("$session_id INFO: target is mac address '$answer_target', looking for host in known_clients and foreign_clients", 5);
1494 # Looking for macaddress in known_clients
1495 my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$answer_target'";
1496 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1497 my $found_ip_flag = 0;
1498 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1499 my $host_name = $hit->{hostname};
1500 my $host_key = $hit->{hostkey};
1501 $answer =~ s/$answer_target/$host_name/g;
1502 daemon_log("$session_id INFO: found host '$host_name', associated to '$answer_target'", 5);
1503 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1504 &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1505 $found_ip_flag++ ;
1506 }
1508 # Looking for macaddress in foreign_clients
1509 if ($found_ip_flag == 0) {
1510 my $sql = "SELECT * FROM $foreign_clients_tn WHERE macaddress LIKE '$answer_target'";
1511 my $res = $foreign_clients_db->select_dbentry($sql);
1512 while( my ($hit_num, $hit) = each %{ $res } ) {
1513 my $host_name = $hit->{hostname};
1514 my $reg_server = $hit->{regserver};
1515 daemon_log("$session_id INFO: found host '$host_name' with mac '$answer_target', registered at '$reg_server'", 5);
1517 # Fetch key for reg_server
1518 my $reg_server_key;
1519 my $sql = "SELECT * FROM $known_server_tn WHERE hostname='$reg_server'";
1520 my $res = $known_server_db->select_dbentry($sql);
1521 if (exists $res->{1}) {
1522 $reg_server_key = $res->{1}->{'hostkey'};
1523 } else {
1524 daemon_log("$session_id ERROR: cannot find hostkey for '$host_name' in '$known_server_tn'", 1);
1525 daemon_log("$session_id ERROR: unable to forward answer to correct registration server, processing is aborted!", 1);
1526 $reg_server_key = undef;
1527 }
1529 # Send answer to server where client is registered
1530 if (defined $reg_server_key) {
1531 $answer =~ s/$answer_target/$host_name/g;
1532 my $error = &send_msg_to_target($answer, $reg_server, $reg_server_key, $answer_header, $session_id);
1533 &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1534 $found_ip_flag++ ;
1535 }
1536 }
1537 }
1539 # No mac to ip matching found
1540 if( $found_ip_flag == 0) {
1541 daemon_log("$session_id WARNING: no host found in known_clients or foreign_clients with mac address '$answer_target'", 3);
1542 &reactivate_job_with_delay($session_id, $answer_target, $answer_header, 30);
1543 }
1545 # Answer is for one specific host
1546 } else {
1547 # get encrypt_key
1548 my $encrypt_key = &get_encrypt_key($answer_target);
1549 if( not defined $encrypt_key ) {
1550 # unknown target
1551 daemon_log("$session_id WARNING: unknown target '$answer_target'", 3);
1552 next;
1553 }
1554 my $error = &send_msg_to_target($answer, $answer_target, $encrypt_key, $answer_header,$session_id);
1555 &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1556 }
1557 }
1558 }
1559 }
1561 my $filter = POE::Filter::Reference->new();
1562 my %result = (
1563 status => "seems ok to me",
1564 answer => $client_answer,
1565 );
1567 my $output = $filter->put( [ \%result ] );
1568 print @$output;
1571 }
1573 sub session_start {
1574 my ($kernel) = $_[KERNEL];
1575 $global_kernel = $kernel;
1576 $kernel->yield('register_at_foreign_servers');
1577 $kernel->yield('create_fai_server_db', $fai_server_tn );
1578 $kernel->yield('create_fai_release_db', $fai_release_tn );
1579 $kernel->yield('watch_for_next_tasks');
1580 $kernel->sig(USR1 => "sig_handler");
1581 $kernel->sig(USR2 => "recreate_packages_db");
1582 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1583 $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay);
1584 $kernel->delay_set('watch_for_modified_jobs', $modified_jobs_loop_delay);
1585 $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1586 $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1587 $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1588 $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1590 # Start opsi check
1591 if ($opsi_enabled eq "true") {
1592 $kernel->delay_set('watch_for_opsi_jobs', $job_queue_opsi_delay);
1593 }
1595 }
1598 sub watch_for_done_jobs {
1599 #CHECK: $heap for what?
1600 my ($kernel,$heap) = @_[KERNEL, HEAP];
1602 my $sql_statement = "SELECT * FROM ".$job_queue_tn." WHERE ((status='done') AND (modified='0'))";
1603 my $res = $job_db->select_dbentry( $sql_statement );
1605 while( my ($id, $hit) = each %{$res} ) {
1606 my $jobdb_id = $hit->{id};
1607 my $sql_statement = "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1608 my $res = $job_db->del_dbentry($sql_statement);
1609 }
1611 $kernel->delay_set('watch_for_done_jobs',$job_queue_loop_delay);
1612 }
1615 sub watch_for_opsi_jobs {
1616 my ($kernel) = $_[KERNEL];
1618 # 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
1619 # opsi install job is to parse the xml message. There is still the correct header.
1620 my $sql_statement = "SELECT * FROM ".$job_queue_tn." WHERE ((xmlmessage LIKE '%opsi_install_client</header>%') AND (status='processing') AND (siserver='localhost'))";
1621 my $res = $job_db->select_dbentry( $sql_statement );
1623 # Ask OPSI for an update of the running jobs
1624 while (my ($id, $hit) = each %$res ) {
1625 # Determine current parameters of the job
1626 my $hostId = $hit->{'plainname'};
1627 my $macaddress = $hit->{'macaddress'};
1628 my $progress = $hit->{'progress'};
1630 my $result= {};
1632 # For hosts, only return the products that are or get installed
1633 my $callobj;
1634 $callobj = {
1635 method => 'getProductStates_hash',
1636 params => [ $hostId ],
1637 id => 1,
1638 };
1640 my $hres = $opsi_client->call($opsi_url, $callobj);
1641 #my ($hres_err, $hres_err_string) = &check_opsi_res($hres);
1642 if (not &check_opsi_res($hres)) {
1643 my $htmp= $hres->result->{$hostId};
1645 # Check state != not_installed or action == setup -> load and add
1646 my $products= 0;
1647 my $installed= 0;
1648 my $installing = 0;
1649 my $error= 0;
1650 my @installed_list;
1651 my @error_list;
1652 my $act_status = "none";
1653 foreach my $product (@{$htmp}){
1655 if ($product->{'installationStatus'} ne "not_installed" or
1656 $product->{'actionRequest'} eq "setup"){
1658 # Increase number of products for this host
1659 $products++;
1661 if ($product->{'installationStatus'} eq "failed"){
1662 $result->{$product->{'productId'}}= "error";
1663 unshift(@error_list, $product->{'productId'});
1664 $error++;
1665 }
1666 if ($product->{'installationStatus'} eq "installed" && $product->{'actionRequest'} eq "none"){
1667 $result->{$product->{'productId'}}= "installed";
1668 unshift(@installed_list, $product->{'productId'});
1669 $installed++;
1670 }
1671 if ($product->{'installationStatus'} eq "installing"){
1672 $result->{$product->{'productId'}}= "installing";
1673 $installing++;
1674 $act_status = "installing - ".$product->{'productId'};
1675 }
1676 }
1677 }
1679 # Estimate "rough" progress, avoid division by zero
1680 if ($products == 0) {
1681 $result->{'progress'}= 0;
1682 } else {
1683 $result->{'progress'}= int($installed * 100 / $products);
1684 }
1686 # Set updates in job queue
1687 if ((not $error) && (not $installing) && ($installed)) {
1688 $act_status = "installed - ".join(", ", @installed_list);
1689 }
1690 if ($error) {
1691 $act_status = "error - ".join(", ", @error_list);
1692 }
1693 if ($progress ne $result->{'progress'} ) {
1694 # Updating progress and result
1695 my $update_statement = "UPDATE $job_queue_tn SET modified='1', progress='".$result->{'progress'}."', result='$act_status' WHERE macaddress LIKE '$macaddress' AND siserver='localhost'";
1696 my $update_res = $job_db->update_dbentry($update_statement);
1697 }
1698 if ($progress eq 100) {
1699 # Updateing status
1700 my $done_statement = "UPDATE $job_queue_tn SET modified='1', ";
1701 if ($error) {
1702 $done_statement .= "status='error'";
1703 } else {
1704 $done_statement .= "status='done'";
1705 }
1706 $done_statement .= " WHERE macaddress LIKE '$macaddress' AND siserver='localhost'";
1707 my $done_res = $job_db->update_dbentry($done_statement);
1708 }
1711 }
1712 }
1714 $kernel->delay_set('watch_for_opsi_jobs', $job_queue_opsi_delay);
1715 }
1718 # If a job got an update or was modified anyway, send to all other si-server an update message of this jobs.
1719 sub watch_for_modified_jobs {
1720 my ($kernel,$heap) = @_[KERNEL, HEAP];
1722 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE (modified='1')";
1723 my $res = $job_db->select_dbentry( $sql_statement );
1725 # if db contains no jobs which should be update, do nothing
1726 if (keys %$res != 0) {
1728 if ($job_synchronization eq "true") {
1729 # make out of the db result a gosa-si message
1730 my $update_msg = &db_res2si_msg ($res, "foreign_job_updates", "KNOWN_SERVER", "MY_LOCAL_ADDRESS");
1732 # update all other SI-server
1733 &inform_all_other_si_server($update_msg);
1734 }
1736 # set jobs all jobs to modified = 0, wait until the next modification for updates of other si-server
1737 $sql_statement = "UPDATE $job_queue_tn SET modified='0' ";
1738 $res = $job_db->update_dbentry($sql_statement);
1739 }
1741 $kernel->delay_set('watch_for_modified_jobs', $modified_jobs_loop_delay);
1742 }
1745 sub watch_for_new_jobs {
1746 if($watch_for_new_jobs_in_progress == 0) {
1747 $watch_for_new_jobs_in_progress = 1;
1748 my ($kernel,$heap) = @_[KERNEL, HEAP];
1750 # check gosa job queue for jobs with executable timestamp
1751 my $timestamp = &get_time();
1752 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE siserver='localhost' AND status='waiting' AND (CAST(timestamp AS UNSIGNED)) < $timestamp ORDER BY timestamp";
1753 my $res = $job_db->exec_statement( $sql_statement );
1755 # Merge all new jobs that would do the same actions
1756 my @drops;
1757 my $hits;
1758 foreach my $hit (reverse @{$res} ) {
1759 my $macaddress= lc @{$hit}[8];
1760 my $headertag= @{$hit}[5];
1761 if(
1762 defined($hits->{$macaddress}) &&
1763 defined($hits->{$macaddress}->{$headertag}) &&
1764 defined($hits->{$macaddress}->{$headertag}[0])
1765 ) {
1766 push @drops, "DELETE FROM $job_queue_tn WHERE id = $hits->{$macaddress}->{$headertag}[0]";
1767 }
1768 $hits->{$macaddress}->{$headertag}= $hit;
1769 }
1771 # Delete new jobs with a matching job in state 'processing'
1772 foreach my $macaddress (keys %{$hits}) {
1773 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1774 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1775 if(defined($jobdb_id)) {
1776 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND headertag='$jobdb_headertag' AND status='processing'";
1777 my $res = $job_db->exec_statement( $sql_statement );
1778 foreach my $hit (@{$res}) {
1779 push @drops, "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1780 }
1781 } else {
1782 daemon_log("J ERROR: Job without id exists for macaddress $macaddress!", 1);
1783 }
1784 }
1785 }
1787 # Commit deletion
1788 $job_db->exec_statementlist(\@drops);
1790 # Look for new jobs that could be executed
1791 foreach my $macaddress (keys %{$hits}) {
1793 # Look if there is an executing job
1794 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='processing'";
1795 my $res = $job_db->exec_statement( $sql_statement );
1797 # Skip new jobs for host if there is a processing job
1798 if(defined($res) and defined @{$res}[0]) {
1799 # Prevent race condition if there is a trigger_activate job waiting and a goto-activation job processing
1800 my $row = @{$res}[0] if (ref $res eq 'ARRAY');
1801 if(@{$row}[5] eq 'trigger_action_reinstall') {
1802 my $sql_statement_2 = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='waiting' AND headertag = 'trigger_activate_new'";
1803 my $res_2 = $job_db->exec_statement( $sql_statement_2 );
1804 if(defined($res_2) and defined @{$res_2}[0]) {
1805 # Set status from goto-activation to 'waiting' and update timestamp
1806 $job_db->exec_statement("UPDATE $job_queue_tn SET status='waiting' WHERE macaddress LIKE '$macaddress' AND headertag = 'trigger_action_reinstall'");
1807 $job_db->exec_statement("UPDATE $job_queue_tn SET timestamp='".&calc_timestamp(&get_time(), 'plus', 30)."' WHERE macaddress LIKE '$macaddress' AND headertag = 'trigger_action_reinstall'");
1808 }
1809 }
1810 next;
1811 }
1813 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1814 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1815 if(defined($jobdb_id)) {
1816 my $job_msg = @{$hits->{$macaddress}->{$jobdb_headertag}}[7];
1818 daemon_log("J DEBUG: its time to execute $job_msg", 7);
1819 my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$macaddress'";
1820 my $res_hash = $known_clients_db->select_dbentry( $sql_statement );
1822 # expect macaddress is unique!!!!!!
1823 my $target = $res_hash->{1}->{hostname};
1825 # change header
1826 $job_msg =~ s/<header>job_/<header>gosa_/;
1828 # add sqlite_id
1829 $job_msg =~ s/<\/xml>$/<jobdb_id>$jobdb_id<\/jobdb_id><\/xml>/;
1831 $job_msg =~ /<header>(\S+)<\/header>/;
1832 my $header = $1 ;
1833 my $func_error = &send_msg_to_target($job_msg, $server_address, $GosaPackages_key, $header, "J");
1835 # update status in job queue to ...
1836 # ... 'processing', for jobs: 'reinstall', 'update'
1837 if (($header =~ /gosa_trigger_action_reinstall/)
1838 || ($header =~ /gosa_trigger_activate_new/)
1839 || ($header =~ /gosa_trigger_action_update/)) {
1840 my $sql_statement = "UPDATE $job_queue_tn SET status='processing' WHERE id=$jobdb_id";
1841 my $dbres = $job_db->update_dbentry($sql_statement);
1842 }
1844 # ... 'done', for all other jobs, they are no longer needed in the jobqueue
1845 else {
1846 my $sql_statement = "UPDATE $job_queue_tn SET status='done' WHERE id=$jobdb_id";
1847 my $dbres = $job_db->update_dbentry($sql_statement);
1848 }
1851 # We don't want parallel processing
1852 last;
1853 }
1854 }
1855 }
1857 $watch_for_new_jobs_in_progress = 0;
1858 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1859 }
1860 }
1863 sub watch_for_new_messages {
1864 my ($kernel,$heap) = @_[KERNEL, HEAP];
1865 my @coll_user_msg; # collection list of outgoing messages
1867 # check messaging_db for new incoming messages with executable timestamp
1868 my $timestamp = &get_time();
1869 my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( (CAST(timestamp AS UNSIGNED))<$timestamp AND flag='n' AND direction='in' )";
1870 my $res = $messaging_db->exec_statement( $sql_statement );
1871 foreach my $hit (@{$res}) {
1873 # create outgoing messages
1874 my $message_to = @{$hit}[3];
1875 # translate message_to to plain login name
1876 my @message_to_l = split(/,/, $message_to);
1877 my %receiver_h;
1878 foreach my $receiver (@message_to_l) {
1879 if ($receiver =~ /^u_([\s\S]*)$/) {
1880 $receiver_h{$1} = 0;
1881 } elsif ($receiver =~ /^g_([\s\S]*)$/) {
1882 my $group_name = $1;
1883 # fetch all group members from ldap and add them to receiver hash
1884 my $ldap_handle = &get_ldap_handle();
1885 if (defined $ldap_handle) {
1886 my $mesg = $ldap_handle->search(
1887 base => $ldap_base,
1888 scope => 'sub',
1889 attrs => ['memberUid'],
1890 filter => "cn=$group_name",
1891 );
1892 &release_ldap_handle($ldap_handle);
1893 if ($mesg->count) {
1894 my @entries = $mesg->entries;
1895 foreach my $entry (@entries) {
1896 my @receivers= $entry->get_value("memberUid");
1897 foreach my $receiver (@receivers) {
1898 $receiver_h{$receiver} = 0;
1899 }
1900 }
1901 }
1902 # translating errors ?
1903 if ($mesg->code) {
1904 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: $mesg->error", 1);
1905 }
1906 # ldap handle error ?
1907 } else {
1908 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: no ldap handle available", 1);
1909 }
1910 } else {
1911 my $sbjct = &encode_base64(@{$hit}[1]);
1912 my $msg = &encode_base64(@{$hit}[7]);
1913 &daemon_log("M WARNING: unknown receiver '$receiver' for a user-message '$sbjct - $msg'", 3);
1914 }
1915 }
1916 my @receiver_l = keys(%receiver_h);
1918 my $message_id = @{$hit}[0];
1920 #add each outgoing msg to messaging_db
1921 my $receiver;
1922 foreach $receiver (@receiver_l) {
1923 my $sql_statement = "INSERT INTO $messaging_tn (id, subject, message_from, message_to, flag, direction, delivery_time, message, timestamp) ".
1924 "VALUES ('".
1925 $message_id."', '". # id
1926 @{$hit}[1]."', '". # subject
1927 @{$hit}[2]."', '". # message_from
1928 $receiver."', '". # message_to
1929 "none"."', '". # flag
1930 "out"."', '". # direction
1931 @{$hit}[6]."', '". # delivery_time
1932 @{$hit}[7]."', '". # message
1933 $timestamp."'". # timestamp
1934 ")";
1935 &daemon_log("M DEBUG: $sql_statement", 1);
1936 my $res = $messaging_db->exec_statement($sql_statement);
1937 &daemon_log("M INFO: message '".@{$hit}[0]."' is prepared for delivery to receiver '$receiver'", 5);
1938 }
1940 # set incoming message to flag d=deliverd
1941 $sql_statement = "UPDATE $messaging_tn SET flag='p' WHERE id='$message_id'";
1942 &daemon_log("M DEBUG: $sql_statement", 7);
1943 $res = $messaging_db->update_dbentry($sql_statement);
1944 &daemon_log("M INFO: message '$message_id' is set to flag 'p' (processed)", 5);
1945 }
1947 $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1948 return;
1949 }
1951 sub watch_for_delivery_messages {
1952 my ($kernel, $heap) = @_[KERNEL, HEAP];
1954 # select outgoing messages
1955 my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( flag='p' AND direction='out' )";
1956 #&daemon_log("0 DEBUG: $sql", 7);
1957 my $res = $messaging_db->exec_statement( $sql_statement );
1959 # build out msg for each usr
1960 foreach my $hit (@{$res}) {
1961 my $receiver = @{$hit}[3];
1962 my $msg_id = @{$hit}[0];
1963 my $subject = @{$hit}[1];
1964 my $message = @{$hit}[7];
1966 # resolve usr -> host where usr is logged in
1967 my $sql = "SELECT * FROM $login_users_tn WHERE (user='$receiver')";
1968 #&daemon_log("0 DEBUG: $sql", 7);
1969 my $res = $login_users_db->exec_statement($sql);
1971 # receiver is logged in nowhere
1972 if (not ref(@$res[0]) eq "ARRAY") { next; }
1974 # receiver ist logged in at a client registered at local server
1975 my $send_succeed = 0;
1976 foreach my $hit (@$res) {
1977 my $receiver_host = @$hit[0];
1978 my $delivered2host = 0;
1979 &daemon_log("M DEBUG: user '$receiver' is logged in at host '$receiver_host'", 7);
1981 # Looking for host in know_clients_db
1982 my $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$receiver_host')";
1983 my $res = $known_clients_db->exec_statement($sql);
1985 # Host is known in known_clients_db
1986 if (ref(@$res[0]) eq "ARRAY") {
1987 my $receiver_key = @{@{$res}[0]}[2];
1988 my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
1989 my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data );
1990 my $error = &send_msg_to_target($out_msg, $receiver_host, $receiver_key, "usr_msg", 0);
1991 if ($error == 0 ) {
1992 $send_succeed++ ;
1993 $delivered2host++ ;
1994 &daemon_log("M DEBUG: send message for user '$receiver' to host '$receiver_host'", 7);
1995 } else {
1996 &daemon_log("M DEBUG: cannot send message for user '$receiver' to host '$receiver_host'", 7);
1997 }
1998 }
2000 # Message already send, do not need to do anything more, otherwise ...
2001 if ($delivered2host) { next;}
2003 # ...looking for host in foreign_clients_db
2004 $sql = "SELECT * FROM $foreign_clients_tn WHERE (hostname='$receiver_host')";
2005 $res = $foreign_clients_db->exec_statement($sql);
2007 # Host is known in foreign_clients_db
2008 if (ref(@$res[0]) eq "ARRAY") {
2009 my $registration_server = @{@{$res}[0]}[2];
2011 # Fetch encryption key for registration server
2012 my $sql = "SELECT * FROM $known_server_tn WHERE (hostname='$registration_server')";
2013 my $res = $known_server_db->exec_statement($sql);
2014 if (ref(@$res[0]) eq "ARRAY") {
2015 my $registration_server_key = @{@{$res}[0]}[3];
2016 my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
2017 my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data );
2018 my $error = &send_msg_to_target($out_msg, $registration_server, $registration_server_key, "usr_msg", 0);
2019 if ($error == 0 ) {
2020 $send_succeed++ ;
2021 $delivered2host++ ;
2022 &daemon_log("M DEBUG: send message for user '$receiver' to server '$registration_server'", 7);
2023 } else {
2024 &daemon_log("M ERROR: cannot send message for user '$receiver' to server '$registration_server'", 1);
2025 }
2027 } else {
2028 &daemon_log("M ERROR: host '$receiver_host' is reported to be ".
2029 "registrated at server '$registration_server', ".
2030 "but no data available in known_server_db ", 1);
2031 }
2032 }
2034 if (not $delivered2host) {
2035 &daemon_log("M ERROR: unable to send user message to host '$receiver_host'", 1);
2036 }
2037 }
2039 if ($send_succeed) {
2040 # set outgoing msg at db to deliverd
2041 my $sql = "UPDATE $messaging_tn SET flag='d' WHERE (id='$msg_id' AND direction='out' AND message_to='$receiver')";
2042 my $res = $messaging_db->exec_statement($sql);
2043 &daemon_log("M INFO: send message for user '$receiver' to logged in hosts", 5);
2044 } else {
2045 &daemon_log("M WARNING: failed to deliver message for user '$receiver'", 3);
2046 }
2047 }
2049 $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
2050 return;
2051 }
2054 sub watch_for_done_messages {
2055 my ($kernel,$heap) = @_[KERNEL, HEAP];
2057 my $sql = "SELECT * FROM $messaging_tn WHERE (flag='p' AND direction='in')";
2058 #&daemon_log("0 DEBUG: $sql", 7);
2059 my $res = $messaging_db->exec_statement($sql);
2061 foreach my $hit (@{$res}) {
2062 my $msg_id = @{$hit}[0];
2064 my $sql = "SELECT * FROM $messaging_tn WHERE (id='$msg_id' AND direction='out' AND (NOT flag='s'))";
2065 #&daemon_log("0 DEBUG: $sql", 7);
2066 my $res = $messaging_db->exec_statement($sql);
2068 # not all usr msgs have been seen till now
2069 if ( ref(@$res[0]) eq "ARRAY") { next; }
2071 $sql = "DELETE FROM $messaging_tn WHERE (id='$msg_id')";
2072 #&daemon_log("0 DEBUG: $sql", 7);
2073 $res = $messaging_db->exec_statement($sql);
2075 }
2077 $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
2078 return;
2079 }
2082 sub watch_for_old_known_clients {
2083 my ($kernel,$heap) = @_[KERNEL, HEAP];
2085 my $sql_statement = "SELECT * FROM $known_clients_tn";
2086 my $res = $known_clients_db->select_dbentry( $sql_statement );
2088 my $cur_time = int(&get_time());
2090 while ( my ($hit_num, $hit) = each %$res) {
2091 my $expired_timestamp = int($hit->{'timestamp'});
2092 $expired_timestamp =~ /(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)/;
2093 my $dt = DateTime->new( year => $1,
2094 month => $2,
2095 day => $3,
2096 hour => $4,
2097 minute => $5,
2098 second => $6,
2099 );
2101 $dt->add( seconds => 2 * int($hit->{'keylifetime'}) );
2102 $expired_timestamp = $dt->ymd('').$dt->hms('');
2103 if ($cur_time > $expired_timestamp) {
2104 my $hostname = $hit->{'hostname'};
2105 my $del_sql = "DELETE FROM $known_clients_tn WHERE hostname='$hostname'";
2106 my $del_res = $known_clients_db->exec_statement($del_sql);
2108 &main::daemon_log("0 INFO: timestamp '".$hit->{'timestamp'}."' of client '$hostname' is expired('$expired_timestamp'), client will be deleted from known_clients_db", 5);
2109 }
2111 }
2113 $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
2114 }
2117 sub watch_for_next_tasks {
2118 my ($kernel,$heap) = @_[KERNEL, HEAP];
2120 my $sql = "SELECT * FROM $incoming_tn";
2121 my $res = $incoming_db->select_dbentry($sql);
2123 while ( my ($hit_num, $hit) = each %$res) {
2124 my $headertag = $hit->{'headertag'};
2125 if ($headertag =~ /^answer_(\d+)/) {
2126 # do not start processing, this message is for a still running POE::Wheel
2127 next;
2128 }
2129 my $message_id = $hit->{'id'};
2130 my $session_id = $hit->{'sessionid'};
2131 &daemon_log("$session_id DEBUG: start processing for message with incoming id: '$message_id'", 7);
2133 my $ldap_handle = &get_ldap_handle();
2134 if (not defined $ldap_handle) { next; }
2135 $kernel->yield('next_task', $hit, $ldap_handle);
2137 my $sql = "DELETE FROM $incoming_tn WHERE id=$message_id";
2138 my $res = $incoming_db->exec_statement($sql);
2139 }
2141 $kernel->delay_set('watch_for_next_tasks', 1);
2142 }
2145 sub get_ldap_handle {
2146 my ($session_id) = @_;
2147 my $heap;
2149 if (not defined $session_id ) { $session_id = 0 };
2150 if ($session_id =~ /[^0-9]*/) { $session_id = 0 };
2152 (my $package, my $file, my $row, my $subroutine, my $hasArgs, my $wantArray, my $evalText, my $isRequire) = caller(1);
2153 my $caller_text = "subroutin $subroutine";
2154 if ($subroutine eq "(eval)") {
2155 $caller_text = "eval block within file '$file' for '$evalText'";
2156 }
2157 daemon_log("$session_id INFO: new ldap handle for $caller_text required", 9);
2159 my $ldap_handle = $ldap_pool->get();
2161 if (not defined $ldap_handle) {
2162 daemon_log("$session_id ERROR: ldap handle for $caller_text not available", 1);
2163 }
2164 return $ldap_handle;
2165 }
2167 sub release_ldap_handle {
2168 my ($ldap_handle) = @_ ;
2169 $ldap_pool->free($ldap_handle);
2170 return;
2171 }
2174 sub change_fai_state {
2175 my ($st, $targets, $session_id) = @_;
2176 $session_id = 0 if not defined $session_id;
2177 # Set FAI state to localboot
2178 my %mapActions= (
2179 reboot => '',
2180 update => 'softupdate',
2181 localboot => 'localboot',
2182 reinstall => 'install',
2183 rescan => '',
2184 wake => '',
2185 memcheck => 'memcheck',
2186 sysinfo => 'sysinfo',
2187 install => 'install',
2188 );
2190 # Return if this is unknown
2191 if (!exists $mapActions{ $st }){
2192 daemon_log("$session_id ERROR: unknown action '$st', can not translate ot FAIstate", 1);
2193 return;
2194 }
2196 my $state= $mapActions{ $st };
2198 #if( defined($ldap_handle) ) {
2200 # Build search filter for hosts
2201 my $search= "(&(objectClass=GOhard)";
2202 foreach (@{$targets}){
2203 $search.= "(macAddress=$_)";
2204 }
2205 $search.= ")";
2207 # If there's any host inside of the search string, procress them
2208 if (!($search =~ /macAddress/)){
2209 daemon_log("$session_id ERROR: no macAddress found in filter statement for LDAP search: '$search'", 1);
2210 return;
2211 }
2213 my $ldap_handle = &get_ldap_handle($session_id);
2214 # Perform search for Unit Tag
2215 my $mesg = $ldap_handle->search(
2216 base => $ldap_base,
2217 scope => 'sub',
2218 attrs => ['dn', 'FAIstate', 'objectClass'],
2219 filter => "$search"
2220 );
2222 if ($mesg->count) {
2223 my @entries = $mesg->entries;
2224 if (0 == @entries) {
2225 daemon_log("$session_id ERROR: ldap search failed: ldap_base=$ldap_base, filter=$search", 1);
2226 }
2228 foreach my $entry (@entries) {
2229 # Only modify entry if it is not set to '$state'
2230 if ($entry->get_value("FAIstate") ne "$state"){
2231 daemon_log("$session_id INFO: Setting FAIstate to '$state' for ".$entry->dn, 5);
2232 my $result;
2233 my %tmp = map { $_ => 1 } $entry->get_value("objectClass");
2234 if (exists $tmp{'FAIobject'}){
2235 if ($state eq ''){
2236 $result= $ldap_handle->modify($entry->dn, changes => [ delete => [ FAIstate => [] ] ]);
2237 } else {
2238 $result= $ldap_handle->modify($entry->dn, changes => [ replace => [ FAIstate => $state ] ]);
2239 }
2240 } elsif ($state ne ''){
2241 $result= $ldap_handle->modify($entry->dn, changes => [ add => [ objectClass => 'FAIobject' ], add => [ FAIstate => $state ] ]);
2242 }
2244 # Errors?
2245 if ($result->code){
2246 daemon_log("$session_id Error: Setting FAIstate to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
2247 }
2248 } else {
2249 daemon_log("$session_id DEBUG FAIstate at host '".$entry->dn."' already at state '$st'", 7);
2250 }
2251 }
2252 } else {
2253 daemon_log("$session_id ERROR: LDAP search failed: ldap_base=$ldap_base, filter=$search", 1);
2254 }
2255 &release_ldap_handle($ldap_handle);
2257 # if no ldap handle defined
2258 #} else {
2259 # daemon_log("$session_id ERROR: no LDAP handle defined for update FAIstate", 1);
2260 #}
2262 return;
2263 }
2266 sub change_goto_state {
2267 my ($st, $targets, $session_id) = @_;
2268 $session_id = 0 if not defined $session_id;
2270 # Switch on or off?
2271 my $state= $st eq 'active' ? 'active': 'locked';
2273 my $ldap_handle = &get_ldap_handle($session_id);
2274 if( defined($ldap_handle) ) {
2276 # Build search filter for hosts
2277 my $search= "(&(objectClass=GOhard)";
2278 foreach (@{$targets}){
2279 $search.= "(macAddress=$_)";
2280 }
2281 $search.= ")";
2283 # If there's any host inside of the search string, procress them
2284 if (!($search =~ /macAddress/)){
2285 return;
2286 }
2288 # Perform search for Unit Tag
2289 my $mesg = $ldap_handle->search(
2290 base => $ldap_base,
2291 scope => 'sub',
2292 attrs => ['dn', 'gotoMode'],
2293 filter => "$search"
2294 );
2296 if ($mesg->count) {
2297 my @entries = $mesg->entries;
2298 foreach my $entry (@entries) {
2300 # Only modify entry if it is not set to '$state'
2301 if ($entry->get_value("gotoMode") ne $state){
2303 daemon_log("$session_id INFO: Setting gotoMode to '$state' for ".$entry->dn, 5);
2304 my $result;
2305 $result= $ldap_handle->modify($entry->dn, changes => [replace => [ gotoMode => $state ] ]);
2307 # Errors?
2308 if ($result->code){
2309 &daemon_log("$session_id Error: Setting gotoMode to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
2310 }
2312 }
2313 }
2314 } else {
2315 daemon_log("$session_id ERROR: LDAP search failed in function change_goto_state: ldap_base=$ldap_base, filter=$search", 1);
2316 }
2318 }
2319 &release_ldap_handle($ldap_handle);
2320 return;
2321 }
2324 sub run_recreate_packages_db {
2325 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2326 my $session_id = $session->ID;
2327 &main::daemon_log("$session_id INFO: Recreating FAI Packages DB ('$fai_release_tn', '$fai_server_tn', '$packages_list_tn')", 5);
2328 $kernel->yield('create_fai_release_db', $fai_release_tn);
2329 $kernel->yield('create_fai_server_db', $fai_server_tn);
2330 return;
2331 }
2334 sub run_create_fai_server_db {
2335 my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
2336 my $session_id = $session->ID;
2337 my $ldap_handle = &get_ldap_handle();
2338 if (not defined $ldap_handle) {
2339 $kernel->delay_set('create_fai_server_db', 1, $table_name);
2340 return;
2341 }
2342 my $task = POE::Wheel::Run->new(
2343 Program => sub { &create_fai_server_db($table_name,$kernel, undef, $session_id, $ldap_handle) },
2344 StdoutEvent => "session_run_result",
2345 StderrEvent => "session_run_debug",
2346 CloseEvent => "session_run_done",
2347 );
2349 $heap->{task}->{ $task->ID } = $task;
2350 $heap->{ldap_handle}->{$task->ID} = $ldap_handle;
2351 return;
2352 }
2355 sub create_fai_server_db {
2356 my ($table_name, $kernel, $dont_create_packages_list, $session_id, $ldap_handle) = @_;
2357 my $result;
2359 if (not defined $session_id) { $session_id = 0; }
2360 if(defined($ldap_handle)) {
2361 daemon_log("$session_id INFO: create_fai_server_db: start", 5);
2362 my $mesg= $ldap_handle->search(
2363 base => $ldap_base,
2364 scope => 'sub',
2365 attrs => ['FAIrepository', 'gosaUnitTag'],
2366 filter => "(&(FAIrepository=*)(objectClass=FAIrepositoryServer))",
2367 );
2368 if($mesg->{'resultCode'} == 0 &&
2369 $mesg->count != 0) {
2370 foreach my $entry (@{$mesg->{entries}}) {
2371 if($entry->exists('FAIrepository')) {
2372 # Add an entry for each Repository configured for server
2373 foreach my $repo(@{$entry->get_value('FAIrepository', asref => 1)}) {
2374 my($tmp_url,$tmp_server,$tmp_release,$tmp_sections) = split(/\|/, $repo);
2375 my $tmp_tag= $entry->get_value('gosaUnitTag') || "";
2376 $result= $fai_server_db->add_dbentry( {
2377 table => $table_name,
2378 primkey => ['server', 'fai_release', 'tag'],
2379 server => $tmp_url,
2380 fai_release => $tmp_release,
2381 sections => $tmp_sections,
2382 tag => (length($tmp_tag)>0)?$tmp_tag:"",
2383 } );
2384 }
2385 }
2386 }
2387 }
2388 daemon_log("$session_id INFO: create_fai_server_db: finished", 5);
2390 # TODO: Find a way to post the 'create_packages_list_db' event
2391 if(not defined($dont_create_packages_list)) {
2392 &create_packages_list_db(undef, $session_id);
2393 }
2394 }
2396 return $result;
2397 }
2400 sub run_create_fai_release_db {
2401 my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
2402 my $session_id = $session->ID;
2403 my $ldap_handle = &get_ldap_handle();
2404 if (not defined $ldap_handle) {
2405 $kernel->delay_set('create_fai_release_db', 1, $table_name);
2406 return;
2407 }
2408 my $task = POE::Wheel::Run->new(
2409 Program => sub { &create_fai_release_db($table_name, $session_id, $ldap_handle) },
2410 StdoutEvent => "session_run_result",
2411 StderrEvent => "session_run_debug",
2412 CloseEvent => "session_run_done",
2413 );
2415 $heap->{task}->{ $task->ID } = $task;
2416 $heap->{ldap_handle}->{$task->ID} = $ldap_handle;
2417 return;
2418 }
2421 sub create_fai_release_db {
2422 my ($table_name, $session_id, $ldap_handle) = @_;
2423 my $result;
2425 # used for logging
2426 if (not defined $session_id) { $session_id = 0; }
2428 #my $ldap_handle = &get_ldap_handle();
2429 if(defined($ldap_handle)) {
2430 daemon_log("$session_id INFO: create_fai_release_db: start",5);
2431 my $mesg= $ldap_handle->search(
2432 base => $ldap_base,
2433 scope => 'sub',
2434 attrs => [],
2435 filter => "(&(objectClass=organizationalUnit)(ou=fai))",
2436 );
2437 if(($mesg->code == 0) && ($mesg->count != 0))
2438 {
2439 daemon_log("$session_id DEBUG: create_fai_release_db: count " . $mesg->count,8);
2441 # Walk through all possible FAI container ou's
2442 my @sql_list;
2443 my $timestamp= &get_time();
2444 foreach my $ou (@{$mesg->{entries}}) {
2445 my $tmp_classes= resolve_fai_classes($ou->dn, $ldap_handle, $session_id);
2446 if(defined($tmp_classes) && ref($tmp_classes) eq 'HASH') {
2447 my @tmp_array=get_fai_release_entries($tmp_classes);
2448 if(@tmp_array) {
2449 foreach my $entry (@tmp_array) {
2450 if(defined($entry) && ref($entry) eq 'HASH') {
2451 my $sql=
2452 "INSERT INTO $table_name "
2453 ."(timestamp, fai_release, class, type, state) VALUES ("
2454 .$timestamp.","
2455 ."'".$entry->{'release'}."',"
2456 ."'".$entry->{'class'}."',"
2457 ."'".$entry->{'type'}."',"
2458 ."'".$entry->{'state'}."')";
2459 push @sql_list, $sql;
2460 }
2461 }
2462 }
2463 }
2464 }
2466 daemon_log("$session_id DEBUG: create_fai_release_db: Inserting ".scalar @sql_list." entries to DB",8);
2467 if(@sql_list) {
2468 unshift @sql_list, "VACUUM";
2469 unshift @sql_list, "DELETE FROM $table_name";
2470 $fai_release_db->exec_statementlist(\@sql_list);
2471 }
2472 daemon_log("$session_id DEBUG: create_fai_release_db: Done with inserting",7);
2473 } else {
2474 daemon_log("$session_id INFO: create_fai_release_db: error: " . $mesg->code, 5);
2475 }
2476 daemon_log("$session_id INFO: create_fai_release_db: finished",5);
2477 }
2478 #&release_ldap_handle($ldap_handle);
2479 return $result;
2480 }
2482 sub get_fai_types {
2483 my $tmp_classes = shift || return undef;
2484 my @result;
2486 foreach my $type(keys %{$tmp_classes}) {
2487 if(defined($tmp_classes->{$type}[0]) && (!($tmp_classes->{$type}[0] =~ /^.*?removed.*?$/))) {
2488 my $entry = {
2489 type => $type,
2490 state => $tmp_classes->{$type}[0],
2491 };
2492 push @result, $entry;
2493 }
2494 }
2496 return @result;
2497 }
2499 sub get_fai_state {
2500 my $result = "";
2501 my $tmp_classes = shift || return $result;
2503 foreach my $type(keys %{$tmp_classes}) {
2504 if(defined($tmp_classes->{$type}[0])) {
2505 $result = $tmp_classes->{$type}[0];
2507 # State is equal for all types in class
2508 last;
2509 }
2510 }
2512 return $result;
2513 }
2515 sub resolve_fai_classes {
2516 my ($fai_base, $ldap_handle, $session_id) = @_;
2517 if (not defined $session_id) { $session_id = 0; }
2518 my $result;
2519 my @possible_fai_classes= ("FAIscript", "FAIhook", "FAIpartitionTable", "FAItemplate", "FAIvariable", "FAIprofile", "FAIpackageList");
2520 my $fai_filter= "(|(&(objectClass=FAIclass)(|(objectClass=".join(")(objectClass=", @possible_fai_classes).")))(objectClass=FAIbranch))";
2521 my $fai_classes;
2523 daemon_log("$session_id DEBUG: Searching for FAI entries in base $fai_base",7);
2524 my $mesg= $ldap_handle->search(
2525 base => $fai_base,
2526 scope => 'sub',
2527 attrs => ['cn','objectClass','FAIstate'],
2528 filter => $fai_filter,
2529 );
2530 daemon_log("$session_id DEBUG: Found ".$mesg->count()." FAI entries",7);
2532 if($mesg->{'resultCode'} == 0 &&
2533 $mesg->count != 0) {
2534 foreach my $entry (@{$mesg->{entries}}) {
2535 if($entry->exists('cn')) {
2536 my $tmp_dn= $entry->dn();
2537 $tmp_dn= substr( $tmp_dn, 0, length($tmp_dn)
2538 - length($fai_base) - 1 );
2540 # Skip classname and ou dn parts for class
2541 my $tmp_release = ($1) if $tmp_dn =~ /^[^,]+,[^,]+,(.*?)$/;
2543 # Skip classes without releases
2544 if((!defined($tmp_release)) || length($tmp_release)==0) {
2545 next;
2546 }
2548 my $tmp_cn= $entry->get_value('cn');
2549 my $tmp_state= $entry->get_value('FAIstate');
2551 my $tmp_type;
2552 # Get FAI type
2553 for my $oclass(@{$entry->get_value('objectClass', asref => 1)}) {
2554 if(grep $_ eq $oclass, @possible_fai_classes) {
2555 $tmp_type= $oclass;
2556 last;
2557 }
2558 }
2560 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2561 # A Subrelease
2562 my @sub_releases = split(/,/, $tmp_release);
2564 # Walk through subreleases and build hash tree
2565 my $hash;
2566 while(my $tmp_sub_release = pop @sub_releases) {
2567 $hash .= "\{'$tmp_sub_release'\}->";
2568 }
2569 eval('push @{$fai_classes->'.$hash.'{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";');
2570 } else {
2571 # A branch, no subrelease
2572 push @{$fai_classes->{$tmp_release}->{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";
2573 }
2574 } elsif (!$entry->exists('cn')) {
2575 my $tmp_dn= $entry->dn();
2576 $tmp_dn= substr( $tmp_dn, 0, length($tmp_dn)
2577 - length($fai_base) - 1 );
2578 my $tmp_release = ($1) if $tmp_dn =~ /^(.*?)$/;
2580 # Skip classes without releases
2581 if((!defined($tmp_release)) || length($tmp_release)==0) {
2582 next;
2583 }
2585 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2586 # A Subrelease
2587 my @sub_releases= split(/,/, $tmp_release);
2589 # Walk through subreleases and build hash tree
2590 my $hash;
2591 while(my $tmp_sub_release = pop @sub_releases) {
2592 $hash .= "\{'$tmp_sub_release'\}->";
2593 }
2594 # Remove the last two characters
2595 chop($hash);
2596 chop($hash);
2598 eval('$fai_classes->'.$hash.'= {}');
2599 } else {
2600 # A branch, no subrelease
2601 if(!exists($fai_classes->{$tmp_release})) {
2602 $fai_classes->{$tmp_release} = {};
2603 }
2604 }
2605 }
2606 }
2608 # The hash is complete, now we can honor the copy-on-write based missing entries
2609 foreach my $release (keys %$fai_classes) {
2610 $result->{$release}= deep_copy(apply_fai_inheritance($fai_classes->{$release}));
2611 }
2612 }
2613 return $result;
2614 }
2616 sub apply_fai_inheritance {
2617 my $fai_classes = shift || return {};
2618 my $tmp_classes;
2620 # Get the classes from the branch
2621 foreach my $class (keys %{$fai_classes}) {
2622 # Skip subreleases
2623 if($class =~ /^ou=.*$/) {
2624 next;
2625 } else {
2626 $tmp_classes->{$class}= deep_copy($fai_classes->{$class});
2627 }
2628 }
2630 # Apply to each subrelease
2631 foreach my $subrelease (keys %{$fai_classes}) {
2632 if($subrelease =~ /ou=/) {
2633 foreach my $tmp_class (keys %{$tmp_classes}) {
2634 if(!exists($fai_classes->{$subrelease}->{$tmp_class})) {
2635 $fai_classes->{$subrelease}->{$tmp_class} =
2636 deep_copy($tmp_classes->{$tmp_class});
2637 } else {
2638 foreach my $type (keys %{$tmp_classes->{$tmp_class}}) {
2639 if(!exists($fai_classes->{$subrelease}->{$tmp_class}->{$type})) {
2640 $fai_classes->{$subrelease}->{$tmp_class}->{$type}=
2641 deep_copy($tmp_classes->{$tmp_class}->{$type});
2642 }
2643 }
2644 }
2645 }
2646 }
2647 }
2649 # Find subreleases in deeper levels
2650 foreach my $subrelease (keys %{$fai_classes}) {
2651 if($subrelease =~ /ou=/) {
2652 foreach my $subsubrelease (keys %{$fai_classes->{$subrelease}}) {
2653 if($subsubrelease =~ /ou=/) {
2654 apply_fai_inheritance($fai_classes->{$subrelease});
2655 }
2656 }
2657 }
2658 }
2660 return $fai_classes;
2661 }
2663 sub get_fai_release_entries {
2664 my $tmp_classes = shift || return;
2665 my $parent = shift || "";
2666 my @result = shift || ();
2668 foreach my $entry (keys %{$tmp_classes}) {
2669 if(defined($entry)) {
2670 if($entry =~ /^ou=.*$/) {
2671 my $release_name = $entry;
2672 $release_name =~ s/ou=//g;
2673 if(length($parent)>0) {
2674 $release_name = $parent."/".$release_name;
2675 }
2676 my @bufentries = get_fai_release_entries($tmp_classes->{$entry}, $release_name, @result);
2677 foreach my $bufentry(@bufentries) {
2678 push @result, $bufentry;
2679 }
2680 } else {
2681 my @types = get_fai_types($tmp_classes->{$entry});
2682 foreach my $type (@types) {
2683 push @result,
2684 {
2685 'class' => $entry,
2686 'type' => $type->{'type'},
2687 'release' => $parent,
2688 'state' => $type->{'state'},
2689 };
2690 }
2691 }
2692 }
2693 }
2695 return @result;
2696 }
2698 sub deep_copy {
2699 my $this = shift;
2700 if (not ref $this) {
2701 $this;
2702 } elsif (ref $this eq "ARRAY") {
2703 [map deep_copy($_), @$this];
2704 } elsif (ref $this eq "HASH") {
2705 +{map { $_ => deep_copy($this->{$_}) } keys %$this};
2706 } else { die "what type is $_?" }
2707 }
2710 sub session_run_result {
2711 my ($kernel, $heap, $client_answer) = @_[KERNEL, HEAP, ARG0];
2712 $kernel->sig(CHLD => "child_reap");
2713 }
2715 sub session_run_debug {
2716 my $result = $_[ARG0];
2717 print STDERR "$result\n";
2718 }
2720 sub session_run_done {
2721 my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
2722 delete $heap->{task}->{$task_id};
2723 if (exists $heap->{ldap_handle}->{$task_id}) {
2724 &release_ldap_handle($heap->{ldap_handle}->{$task_id});
2725 }
2726 delete $heap->{ldap_handle}->{$task_id};
2727 }
2730 sub create_sources_list {
2731 my $session_id = shift;
2732 my $result="/tmp/gosa_si_tmp_sources_list";
2734 # Remove old file
2735 if(stat($result)) {
2736 unlink($result);
2737 &main::daemon_log("$session_id DEBUG: remove an old version of '$result'", 7);
2738 }
2740 my $fh;
2741 open($fh, ">$result");
2742 if (not defined $fh) {
2743 &main::daemon_log("$session_id DEBUG: cannot open '$result' for writing", 7);
2744 return undef;
2745 }
2746 if(defined($main::ldap_server_dn) and length($main::ldap_server_dn) > 0) {
2747 my $ldap_handle = &get_ldap_handle();
2748 my $mesg=$ldap_handle->search(
2749 base => $main::ldap_server_dn,
2750 scope => 'base',
2751 attrs => 'FAIrepository',
2752 filter => 'objectClass=FAIrepositoryServer'
2753 );
2754 &release_ldap_handle($ldap_handle);
2755 if($mesg->count) {
2756 foreach my $entry(@{$mesg->{'entries'}}) {
2757 foreach my $value(@{$entry->get_value('FAIrepository', asref => 1)}) {
2758 my ($server, $tag, $release, $sections)= split /\|/, $value;
2759 my $line = "deb $server $release";
2760 $sections =~ s/,/ /g;
2761 $line.= " $sections";
2762 print $fh $line."\n";
2763 }
2764 }
2765 }
2766 } else {
2767 if (defined $main::ldap_server_dn){
2768 &main::daemon_log("$session_id ERROR: something wrong with ldap_server_dn '$main::ldap_server_dn', abort create_sources_list", 1);
2769 } else {
2770 &main::daemon_log("$session_id ERROR: no ldap_server_dn found, abort create_sources_list", 1);
2771 }
2772 }
2773 close($fh);
2775 return $result;
2776 }
2779 sub run_create_packages_list_db {
2780 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2781 my $session_id = $session->ID;
2782 my $task = POE::Wheel::Run->new(
2783 Priority => +20,
2784 Program => sub {&create_packages_list_db(undef, $session_id)},
2785 StdoutEvent => "session_run_result",
2786 StderrEvent => "session_run_debug",
2787 CloseEvent => "session_run_done",
2788 );
2789 $heap->{task}->{ $task->ID } = $task;
2790 }
2793 sub create_packages_list_db {
2794 my ($sources_file, $session_id) = @_;
2796 # it should not be possible to trigger a recreation of packages_list_db
2797 # while packages_list_db is under construction, so set flag packages_list_under_construction
2798 # which is tested befor recreation can be started
2799 if (-r $packages_list_under_construction) {
2800 daemon_log("$session_id WARNING: packages_list_db is right now under construction, please wait until this process is finished", 3);
2801 return;
2802 } else {
2803 daemon_log("$session_id INFO: create_packages_list_db: start", 5);
2804 # set packages_list_under_construction to true
2805 system("touch $packages_list_under_construction");
2806 @packages_list_statements=();
2807 }
2809 if (not defined $session_id) { $session_id = 0; }
2811 if (not defined $sources_file) {
2812 &main::daemon_log("$session_id INFO: no sources_file given for creating packages list so trigger creation of it", 5);
2813 $sources_file = &create_sources_list($session_id);
2814 }
2816 if (not defined $sources_file) {
2817 &main::daemon_log("$session_id ERROR: no sources_file given under '$sources_file', skip create_packages_list_db", 1);
2818 unlink($packages_list_under_construction);
2819 return;
2820 }
2822 my $line;
2824 open(CONFIG, "<$sources_file") or do {
2825 daemon_log( "$session_id ERROR: create_packages_list_db: Failed to open '$sources_file'", 1);
2826 unlink($packages_list_under_construction);
2827 return;
2828 };
2830 # Read lines
2831 while ($line = <CONFIG>){
2832 # Unify
2833 chop($line);
2834 $line =~ s/^\s+//;
2835 $line =~ s/^\s+/ /;
2837 # Strip comments
2838 $line =~ s/#.*$//g;
2840 # Skip empty lines
2841 if ($line =~ /^\s*$/){
2842 next;
2843 }
2845 # Interpret deb line
2846 if ($line =~ /^deb [^\s]+\s[^\s]+\s[^\s]+/){
2847 my( $baseurl, $dist, $sections ) = ($line =~ /^deb\s([^\s]+)\s+([^\s]+)\s+(.*)$/);
2848 my $section;
2849 foreach $section (split(' ', $sections)){
2850 &parse_package_info( $baseurl, $dist, $section, $session_id );
2851 }
2852 }
2853 }
2855 close (CONFIG);
2857 if(keys(%repo_dirs)) {
2858 find(\&cleanup_and_extract, keys( %repo_dirs ));
2859 &main::strip_packages_list_statements();
2860 $packages_list_db->exec_statementlist(\@packages_list_statements);
2861 }
2862 unlink($packages_list_under_construction);
2863 daemon_log("$session_id INFO: create_packages_list_db: finished", 5);
2864 return;
2865 }
2867 # This function should do some intensive task to minimize the db-traffic
2868 sub strip_packages_list_statements {
2869 my @existing_entries= @{$packages_list_db->exec_statement("SELECT * FROM $main::packages_list_tn")};
2870 my @new_statement_list=();
2871 my $hash;
2872 my $insert_hash;
2873 my $update_hash;
2874 my $delete_hash;
2875 my $known_packages_hash;
2876 my $local_timestamp=get_time();
2878 foreach my $existing_entry (@existing_entries) {
2879 $hash->{@{$existing_entry}[0]}->{@{$existing_entry}[1]}->{@{$existing_entry}[2]}= $existing_entry;
2880 }
2882 foreach my $statement (@packages_list_statements) {
2883 if($statement =~ /^INSERT/i) {
2884 # Assign the values from the insert statement
2885 my ($distribution,$package,$version,$section,$description,$template,$timestamp) = ($1,$2,$3,$4,$5,$6,$7) if $statement =~
2886 /^INSERT\s+?INTO\s+?$main::packages_list_tn\s+?VALUES\s*?\('(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)'\s*?\)$/si;
2887 if(exists($hash->{$distribution}->{$package}->{$version})) {
2888 # If section or description has changed, update the DB
2889 if(
2890 (! (@{$hash->{$distribution}->{$package}->{$version}}[3] eq $section)) or
2891 (! (@{$hash->{$distribution}->{$package}->{$version}}[4] eq $description))
2892 ) {
2893 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,undef);
2894 } else {
2895 # package is already present in database. cache this knowledge for later use
2896 @{$known_packages_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2897 }
2898 } else {
2899 # Insert a non-existing entry to db
2900 @{$insert_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2901 }
2902 } elsif ($statement =~ /^UPDATE/i) {
2903 my ($template,$package,$version) = ($1,$2,$3) if $statement =~
2904 /^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;
2905 foreach my $distribution (keys %{$hash}) {
2906 if(exists($insert_hash->{$distribution}->{$package}->{$version})) {
2907 # update the insertion hash to execute only one query per package (insert instead insert+update)
2908 @{$insert_hash->{$distribution}->{$package}->{$version}}[5]= $template;
2909 } elsif(exists($hash->{$distribution}->{$package}->{$version})) {
2910 if( ! (@{$hash->{$distribution}->{$package}->{$version}}[5] eq $template)) {
2911 my $section;
2912 my $description;
2913 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) and
2914 length(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) > 0 ) {
2915 $section= @{$update_hash->{$distribution}->{$package}->{$version}}[3];
2916 }
2917 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2918 $description= @{$update_hash->{$distribution}->{$package}->{$version}}[4];
2919 }
2920 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2921 }
2922 }
2923 }
2924 }
2925 }
2927 # Check for orphaned entries
2928 foreach my $existing_entry (@existing_entries) {
2929 my $distribution= @{$existing_entry}[0];
2930 my $package= @{$existing_entry}[1];
2931 my $version= @{$existing_entry}[2];
2932 my $section= @{$existing_entry}[3];
2934 if(
2935 exists($insert_hash->{$distribution}->{$package}->{$version}) ||
2936 exists($update_hash->{$distribution}->{$package}->{$version}) ||
2937 exists($known_packages_hash->{$distribution}->{$package}->{$version})
2938 ) {
2939 next;
2940 } else {
2941 # Insert entry to delete hash
2942 @{$delete_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section);
2943 }
2944 }
2946 # unroll the insert hash
2947 foreach my $distribution (keys %{$insert_hash}) {
2948 foreach my $package (keys %{$insert_hash->{$distribution}}) {
2949 foreach my $version (keys %{$insert_hash->{$distribution}->{$package}}) {
2950 push @new_statement_list, "INSERT INTO $main::packages_list_tn VALUES ('$distribution','$package','$version',"
2951 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[3]',"
2952 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[4]',"
2953 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[5]',"
2954 ."'$local_timestamp')";
2955 }
2956 }
2957 }
2959 # unroll the update hash
2960 foreach my $distribution (keys %{$update_hash}) {
2961 foreach my $package (keys %{$update_hash->{$distribution}}) {
2962 foreach my $version (keys %{$update_hash->{$distribution}->{$package}}) {
2963 my $set = "";
2964 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3])) {
2965 $set .= "section = '@{$update_hash->{$distribution}->{$package}->{$version}}[3]', ";
2966 }
2967 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2968 $set .= "description = '@{$update_hash->{$distribution}->{$package}->{$version}}[4]', ";
2969 }
2970 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[5])) {
2971 $set .= "template = '@{$update_hash->{$distribution}->{$package}->{$version}}[5]', ";
2972 }
2973 if(defined($set) and length($set) > 0) {
2974 $set .= "timestamp = '$local_timestamp'";
2975 } else {
2976 next;
2977 }
2978 push @new_statement_list,
2979 "UPDATE $main::packages_list_tn SET $set WHERE"
2980 ." distribution = '$distribution'"
2981 ." AND package = '$package'"
2982 ." AND version = '$version'";
2983 }
2984 }
2985 }
2987 # unroll the delete hash
2988 foreach my $distribution (keys %{$delete_hash}) {
2989 foreach my $package (keys %{$delete_hash->{$distribution}}) {
2990 foreach my $version (keys %{$delete_hash->{$distribution}->{$package}}) {
2991 my $section = @{$delete_hash->{$distribution}->{$package}->{$version}}[3];
2992 push @new_statement_list, "DELETE FROM $main::packages_list_tn WHERE distribution='$distribution' AND package='$package' AND version='$version' AND section='$section'";
2993 }
2994 }
2995 }
2997 unshift(@new_statement_list, "VACUUM");
2999 @packages_list_statements = @new_statement_list;
3000 }
3003 sub parse_package_info {
3004 my ($baseurl, $dist, $section, $session_id)= @_;
3005 my ($package);
3006 if (not defined $session_id) { $session_id = 0; }
3007 my ($path) = ($baseurl =~ m%://[^/]*(.*)$%);
3008 $repo_dirs{ "${repo_path}/pool" } = 1;
3010 foreach $package ("Packages.gz"){
3011 daemon_log("$session_id DEBUG: create_packages_list: fetch $baseurl, $dist, $section", 7);
3012 get_package( "$baseurl/dists/$dist/$section/binary-$arch/$package", "$outdir/$dist/$section", $session_id );
3013 parse_package( "$outdir/$dist/$section", $dist, $path, $session_id );
3014 }
3016 }
3019 sub get_package {
3020 my ($url, $dest, $session_id)= @_;
3021 if (not defined $session_id) { $session_id = 0; }
3023 my $tpath = dirname($dest);
3024 -d "$tpath" || mkpath "$tpath";
3026 # This is ugly, but I've no time to take a look at "how it works in perl"
3027 if(0 == system("wget '$url' -O '$dest' 2>/dev/null") ) {
3028 system("gunzip -cd '$dest' > '$dest.in'");
3029 daemon_log("$session_id DEBUG: run command: gunzip -cd '$dest' > '$dest.in'", 5);
3030 unlink($dest);
3031 daemon_log("$session_id DEBUG: delete file '$dest'", 5);
3032 } else {
3033 daemon_log("$session_id ERROR: create_packages_list_db: get_packages: fetching '$url' into '$dest' failed!", 1);
3034 }
3035 return 0;
3036 }
3039 sub parse_package {
3040 my ($path, $dist, $srv_path, $session_id)= @_;
3041 if (not defined $session_id) { $session_id = 0;}
3042 my ($package, $version, $section, $description);
3043 my $PACKAGES;
3044 my $timestamp = &get_time();
3046 if(not stat("$path.in")) {
3047 daemon_log("$session_id ERROR: create_packages_list: parse_package: file '$path.in' is not readable",1);
3048 return;
3049 }
3051 open($PACKAGES, "<$path.in");
3052 if(not defined($PACKAGES)) {
3053 daemon_log("$session_id ERROR: create_packages_list_db: parse_package: cannot open '$path.in'",1);
3054 return;
3055 }
3057 # Read lines
3058 while (<$PACKAGES>){
3059 my $line = $_;
3060 # Unify
3061 chop($line);
3063 # Use empty lines as a trigger
3064 if ($line =~ /^\s*$/){
3065 my $sql = "INSERT INTO packages_list VALUES ('$dist', '$package', '$version', '$section', '$description', '', '$timestamp')";
3066 push(@packages_list_statements, $sql);
3067 $package = "none";
3068 $version = "none";
3069 $section = "none";
3070 $description = "none";
3071 next;
3072 }
3074 # Trigger for package name
3075 if ($line =~ /^Package:\s/){
3076 ($package)= ($line =~ /^Package: (.*)$/);
3077 next;
3078 }
3080 # Trigger for version
3081 if ($line =~ /^Version:\s/){
3082 ($version)= ($line =~ /^Version: (.*)$/);
3083 next;
3084 }
3086 # Trigger for description
3087 if ($line =~ /^Description:\s/){
3088 ($description)= &encode_base64(($line =~ /^Description: (.*)$/));
3089 next;
3090 }
3092 # Trigger for section
3093 if ($line =~ /^Section:\s/){
3094 ($section)= ($line =~ /^Section: (.*)$/);
3095 next;
3096 }
3098 # Trigger for filename
3099 if ($line =~ /^Filename:\s/){
3100 my ($filename) = ($line =~ /^Filename: (.*)$/);
3101 store_fileinfo( $package, $filename, $dist, $srv_path, $version, $repo_path );
3102 next;
3103 }
3104 }
3106 close( $PACKAGES );
3107 unlink( "$path.in" );
3108 }
3111 sub store_fileinfo {
3112 my( $package, $file, $dist, $path, $vers, $srvdir) = @_;
3114 my %fileinfo = (
3115 'package' => $package,
3116 'dist' => $dist,
3117 'version' => $vers,
3118 );
3120 $repo_files{ "${srvdir}/$file" } = \%fileinfo;
3121 }
3124 sub cleanup_and_extract {
3125 my $fileinfo = $repo_files{ $File::Find::name };
3127 if( defined $fileinfo ) {
3128 my $dir = "$outdir/$fileinfo->{ 'dist' }/debconf.d";
3129 my $sql;
3130 my $package = $fileinfo->{ 'package' };
3131 my $newver = $fileinfo->{ 'version' };
3133 mkpath($dir);
3134 system( "dpkg -e '$File::Find::name' '$dir/DEBIAN'" );
3136 if( -f "$dir/DEBIAN/templates" ) {
3138 daemon_log("0 DEBUG: Found debconf templates in '$package' - $newver", 7);
3140 my $tmpl= ""; {
3141 local $/=undef;
3142 open FILE, "$dir/DEBIAN/templates";
3143 $tmpl = &encode_base64(<FILE>);
3144 close FILE;
3145 }
3146 rmtree("$dir/DEBIAN/templates");
3148 $sql= "update $main::packages_list_tn set template = '$tmpl' where package = '$package' and version = '$newver';";
3149 push @packages_list_statements, $sql;
3150 }
3151 }
3153 return;
3154 }
3157 sub register_at_foreign_servers {
3158 my ($kernel) = $_[KERNEL];
3160 # hole alle bekannten server aus known_server_db
3161 my $server_sql = "SELECT * FROM $known_server_tn";
3162 my $server_res = $known_server_db->exec_statement($server_sql);
3164 # no entries in known_server_db
3165 if (not ref(@$server_res[0]) eq "ARRAY") {
3166 # TODO
3167 }
3169 # detect already connected clients
3170 my $client_sql = "SELECT * FROM $known_clients_tn";
3171 my $client_res = $known_clients_db->exec_statement($client_sql);
3173 # send my server details to all other gosa-si-server within the network
3174 foreach my $hit (@$server_res) {
3175 my $hostname = @$hit[0];
3176 my $hostkey = &create_passwd;
3178 # add already connected clients to registration message
3179 my $myhash = &create_xml_hash('new_server', $server_address, $hostname);
3180 &add_content2xml_hash($myhash, 'key', $hostkey);
3181 map(&add_content2xml_hash($myhash, 'client', @{$_}[0].",".@{$_}[4]), @$client_res);
3183 # add locally loaded gosa-si modules to registration message
3184 my $loaded_modules = {};
3185 while (my ($package, $pck_info) = each %$known_modules) {
3186 next if ((!defined(@$pck_info[2])) || (!(ref (@$pck_info[2]) eq 'HASH')));
3187 foreach my $act_module (keys(%{@$pck_info[2]})) {
3188 $loaded_modules->{$act_module} = "";
3189 }
3190 }
3192 map(&add_content2xml_hash($myhash, "loaded_modules", $_), keys(%$loaded_modules));
3194 # add macaddress to registration message
3195 my ($host_ip, $host_port) = split(/:/, $hostname);
3196 my $local_ip = &get_local_ip_for_remote_ip($host_ip);
3197 my $network_interface= &get_interface_for_ip($local_ip);
3198 my $host_mac = &get_mac_for_interface($network_interface);
3199 &add_content2xml_hash($myhash, 'macaddress', $host_mac);
3201 # build registration message and send it
3202 my $foreign_server_msg = &create_xml_string($myhash);
3203 my $error = &send_msg_to_target($foreign_server_msg, $hostname, $ServerPackages_key, "new_server", 0);
3204 }
3206 $kernel->delay_set("register_at_foreign_servers", $foreign_servers_register_delay);
3207 return;
3208 }
3211 #==== MAIN = main ==============================================================
3212 # parse commandline options
3213 Getopt::Long::Configure( "bundling" );
3214 GetOptions("h|help" => \&usage,
3215 "c|config=s" => \$cfg_file,
3216 "f|foreground" => \$foreground,
3217 "v|verbose+" => \$verbose,
3218 "no-arp+" => \$no_arp,
3219 );
3221 # read and set config parameters
3222 &check_cmdline_param ;
3223 &read_configfile($cfg_file, %cfg_defaults);
3224 &check_pid;
3226 $SIG{CHLD} = 'IGNORE';
3228 # forward error messages to logfile
3229 if( ! $foreground ) {
3230 open( STDIN, '+>/dev/null' );
3231 open( STDOUT, '+>&STDIN' );
3232 open( STDERR, '+>&STDIN' );
3233 }
3235 # Just fork, if we are not in foreground mode
3236 if( ! $foreground ) {
3237 chdir '/' or die "Can't chdir to /: $!";
3238 $pid = fork;
3239 setsid or die "Can't start a new session: $!";
3240 umask 0;
3241 } else {
3242 $pid = $$;
3243 }
3245 # Do something useful - put our PID into the pid_file
3246 if( 0 != $pid ) {
3247 open( LOCK_FILE, ">$pid_file" );
3248 print LOCK_FILE "$pid\n";
3249 close( LOCK_FILE );
3250 if( !$foreground ) {
3251 exit( 0 )
3252 };
3253 }
3255 # parse head url and revision from svn
3256 my $server_status_hash = { 'developmental'=>'revision', 'stable'=>'release'};
3257 $server_version =~ /^\$HeadURL: (\S+) \$:\$Rev: (\d+) \$$/;
3258 $server_headURL = defined $1 ? $1 : 'unknown' ;
3259 $server_revision = defined $2 ? $2 : 'unknown' ;
3260 if ($server_headURL =~ /\/tag\// ||
3261 $server_headURL =~ /\/branches\// ) {
3262 $server_status = "stable";
3263 } else {
3264 $server_status = "developmental" ;
3265 }
3266 # Prepare log file and set permissions
3267 $root_uid = getpwnam('root');
3268 $adm_gid = getgrnam('adm');
3269 open(FH, ">>$log_file");
3270 close FH;
3271 chmod(0440, $log_file);
3272 chown($root_uid, $adm_gid, $log_file);
3273 chown($root_uid, $adm_gid, "/var/lib/gosa-si");
3275 daemon_log(" ", 1);
3276 daemon_log("$0 started!", 1);
3277 daemon_log("status: $server_status", 1);
3278 daemon_log($server_status_hash->{$server_status}.": $server_revision", 1);
3280 # Create a pool of LDAP handles
3281 $ldap_factory = ResourcePool::Factory::Net::LDAP->new($ldap_uri, version => $ldap_version);
3282 $ldap_factory->bind($ldap_admin_dn, password=>$ldap_admin_password);
3283 $ldap_pool = ResourcePool->new($ldap_factory,
3284 Max => $max_ldap_handle,
3285 #MaxTry => 1,
3286 #SleepOnFail => [0, 0, 1, 1],
3287 PreCreate => $precreate_ldap_handle,
3288 );
3291 # Buildup data bases
3292 {
3293 no strict "refs";
3295 if ($db_module eq "DBmysql") {
3296 # connect to incoming_db
3297 $incoming_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3299 # connect to gosa-si job queue
3300 $job_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3302 # connect to known_clients_db
3303 $known_clients_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3305 # connect to foreign_clients_db
3306 $foreign_clients_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3308 # connect to known_server_db
3309 $known_server_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3311 # connect to login_usr_db
3312 $login_users_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3314 # connect to fai_server_db
3315 $fai_server_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3317 # connect to fai_release_db
3318 $fai_release_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3320 # connect to packages_list_db
3321 $packages_list_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3323 # connect to messaging_db
3324 $messaging_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3326 } elsif ($db_module eq "DBsqlite") {
3327 # connect to incoming_db
3328 unlink($incoming_file_name);
3329 $incoming_db = GOSA::DBsqlite->new($incoming_file_name);
3331 # connect to gosa-si job queue
3332 $job_db = GOSA::DBsqlite->new($job_queue_file_name);
3333 chmod(0640, $job_queue_file_name);
3334 chown($root_uid, $adm_gid, $job_queue_file_name);
3336 # connect to known_clients_db
3337 $known_clients_db = GOSA::DBsqlite->new($known_clients_file_name);
3338 chmod(0640, $known_clients_file_name);
3339 chown($root_uid, $adm_gid, $known_clients_file_name);
3341 # connect to foreign_clients_db
3342 unlink($foreign_clients_file_name);
3343 $foreign_clients_db = GOSA::DBsqlite->new($foreign_clients_file_name);
3344 chmod(0640, $foreign_clients_file_name);
3345 chown($root_uid, $adm_gid, $foreign_clients_file_name);
3347 # connect to known_server_db
3348 unlink($known_server_file_name);
3349 $known_server_db = GOSA::DBsqlite->new($known_server_file_name);
3350 chmod(0640, $known_server_file_name);
3351 chown($root_uid, $adm_gid, $known_server_file_name);
3353 # connect to login_usr_db
3354 unlink($login_users_file_name);
3355 $login_users_db = GOSA::DBsqlite->new($login_users_file_name);
3356 chmod(0640, $login_users_file_name);
3357 chown($root_uid, $adm_gid, $login_users_file_name);
3359 # connect to fai_server_db
3360 unlink($fai_server_file_name);
3361 $fai_server_db = GOSA::DBsqlite->new($fai_server_file_name);
3362 chmod(0640, $fai_server_file_name);
3363 chown($root_uid, $adm_gid, $fai_server_file_name);
3365 # connect to fai_release_db
3366 unlink($fai_release_file_name);
3367 $fai_release_db = GOSA::DBsqlite->new($fai_release_file_name);
3368 chmod(0640, $fai_release_file_name);
3369 chown($root_uid, $adm_gid, $fai_release_file_name);
3371 # connect to packages_list_db
3372 #unlink($packages_list_file_name);
3373 unlink($packages_list_under_construction);
3374 $packages_list_db = GOSA::DBsqlite->new($packages_list_file_name);
3375 chmod(0640, $packages_list_file_name);
3376 chown($root_uid, $adm_gid, $packages_list_file_name);
3378 # connect to messaging_db
3379 unlink($messaging_file_name);
3380 $messaging_db = GOSA::DBsqlite->new($messaging_file_name);
3381 chmod(0640, $messaging_file_name);
3382 chown($root_uid, $adm_gid, $messaging_file_name);
3383 }
3384 }
3387 # Creating tables
3388 $messaging_db->create_table($messaging_tn, \@messaging_col_names);
3389 $packages_list_db->create_table($packages_list_tn, \@packages_list_col_names);
3390 $fai_release_db->create_table($fai_release_tn, \@fai_release_col_names);
3391 $fai_server_db->create_table($fai_server_tn, \@fai_server_col_names);
3392 $login_users_db->create_table($login_users_tn, \@login_users_col_names);
3393 $known_server_db->create_table($known_server_tn, \@known_server_col_names);
3394 $foreign_clients_db->create_table($foreign_clients_tn, \@foreign_clients_col_names);
3395 $known_clients_db->create_table($known_clients_tn, \@known_clients_col_names);
3396 $incoming_db->create_table($incoming_tn, \@incoming_col_names);
3397 $job_db->create_table($job_queue_tn, \@job_queue_col_names);
3399 # create xml object used for en/decrypting
3400 $xml = new XML::Simple();
3403 # foreign servers
3404 my @foreign_server_list;
3406 # add foreign server from cfg file
3407 if ($foreign_server_string ne "") {
3408 my @cfg_foreign_server_list = split(",", $foreign_server_string);
3409 foreach my $foreign_server (@cfg_foreign_server_list) {
3410 push(@foreign_server_list, $foreign_server);
3411 }
3413 daemon_log("0 INFO: found foreign server in config file: ".join(", ", @foreign_server_list), 5);
3414 }
3416 # Perform a DNS lookup for server registration if flag is true
3417 if ($dns_lookup eq "true") {
3418 # Add foreign server from dns
3419 my @tmp_servers;
3420 if (not $server_domain) {
3421 # Try our DNS Searchlist
3422 for my $domain(get_dns_domains()) {
3423 chomp($domain);
3424 my ($tmp_domains, $error_string) = &get_server_addresses($domain);
3425 if(@$tmp_domains) {
3426 for my $tmp_server(@$tmp_domains) {
3427 push @tmp_servers, $tmp_server;
3428 }
3429 }
3430 }
3431 if(@tmp_servers && length(@tmp_servers)==0) {
3432 daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
3433 }
3434 } else {
3435 @tmp_servers = &get_server_addresses($server_domain);
3436 if( 0 == @tmp_servers ) {
3437 daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
3438 }
3439 }
3441 daemon_log("0 INFO: found foreign server via DNS ".join(", ", @tmp_servers), 5);
3443 foreach my $server (@tmp_servers) {
3444 unshift(@foreign_server_list, $server);
3445 }
3446 } else {
3447 daemon_log("0 INFO: DNS lookup for server registration is disabled", 5);
3448 }
3451 # eliminate duplicate entries
3452 @foreign_server_list = &del_doubles(@foreign_server_list);
3453 my $all_foreign_server = join(", ", @foreign_server_list);
3454 daemon_log("0 INFO: found foreign server in config file and DNS: '$all_foreign_server'", 5);
3456 # add all found foreign servers to known_server
3457 my $cur_timestamp = &get_time();
3458 foreach my $foreign_server (@foreign_server_list) {
3460 # do not add myself to known_server_db
3461 if (&is_local($foreign_server)) { next; }
3462 ######################################
3464 my $res = $known_server_db->add_dbentry( {table=>$known_server_tn,
3465 primkey=>['hostname'],
3466 hostname=>$foreign_server,
3467 macaddress=>"",
3468 status=>'not_yet_registered',
3469 hostkey=>"none",
3470 loaded_modules => "none",
3471 timestamp=>$cur_timestamp,
3472 } );
3473 }
3476 # Import all modules
3477 &import_modules;
3479 # Check wether all modules are gosa-si valid passwd check
3480 &password_check;
3482 # Prepare for using Opsi
3483 if ($opsi_enabled eq "true") {
3484 use JSON::RPC::Client;
3485 use XML::Quote qw(:all);
3486 $opsi_url= "https://".$opsi_admin.":".$opsi_password."@".$opsi_server.":4447/rpc";
3487 $opsi_client = new JSON::RPC::Client;
3488 }
3491 POE::Component::Server::TCP->new(
3492 Alias => "TCP_SERVER",
3493 Port => $server_port,
3494 ClientInput => sub {
3495 my ($kernel, $input, $heap, $session) = @_[KERNEL, ARG0, HEAP, SESSION];
3496 my $session_id = $session->ID;
3497 my $remote_ip = $heap->{'remote_ip'};
3498 push(@msgs_to_decrypt, $input);
3499 &daemon_log("$session_id DEBUG: incoming message from '$remote_ip'", 7);
3500 $kernel->yield("msg_to_decrypt");
3501 },
3502 InlineStates => {
3503 msg_to_decrypt => \&msg_to_decrypt,
3504 next_task => \&next_task,
3505 task_result => \&handle_task_result,
3506 task_done => \&handle_task_done,
3507 task_debug => \&handle_task_debug,
3508 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!" },
3509 }
3510 );
3512 daemon_log("0 INFO: start socket for incoming xml messages at port '$server_port' ", 1);
3514 # create session for repeatedly checking the job queue for jobs
3515 POE::Session->create(
3516 inline_states => {
3517 _start => \&session_start,
3518 register_at_foreign_servers => \®ister_at_foreign_servers,
3519 sig_handler => \&sig_handler,
3520 next_task => \&next_task,
3521 task_result => \&handle_task_result,
3522 task_done => \&handle_task_done,
3523 task_debug => \&handle_task_debug,
3524 watch_for_next_tasks => \&watch_for_next_tasks,
3525 watch_for_new_messages => \&watch_for_new_messages,
3526 watch_for_delivery_messages => \&watch_for_delivery_messages,
3527 watch_for_done_messages => \&watch_for_done_messages,
3528 watch_for_new_jobs => \&watch_for_new_jobs,
3529 watch_for_modified_jobs => \&watch_for_modified_jobs,
3530 watch_for_done_jobs => \&watch_for_done_jobs,
3531 watch_for_opsi_jobs => \&watch_for_opsi_jobs,
3532 watch_for_old_known_clients => \&watch_for_old_known_clients,
3533 create_packages_list_db => \&run_create_packages_list_db,
3534 create_fai_server_db => \&run_create_fai_server_db,
3535 create_fai_release_db => \&run_create_fai_release_db,
3536 recreate_packages_db => \&run_recreate_packages_db,
3537 session_run_result => \&session_run_result,
3538 session_run_debug => \&session_run_debug,
3539 session_run_done => \&session_run_done,
3540 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!" },
3541 }
3542 );
3545 POE::Kernel->run();
3546 exit;