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;
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 Cwd;
43 use File::Spec;
44 use File::Basename;
45 use File::Find;
46 use File::Copy;
47 use File::Path;
48 use GOSA::GosaSupportDaemon;
49 use POE qw(Component::Server::TCP Wheel::Run Filter::Reference);
50 use Net::LDAP;
51 use Net::LDAP::Util qw(:escape);
52 use Time::HiRes qw( usleep);
54 # revision number of server and program name
55 my $server_headURL;
56 my $server_revision;
57 my $server_status;
58 our $prg= basename($0);
60 my $db_module = "DBsqlite";
61 {
62 no strict "refs";
63 require ("GOSA/".$db_module.".pm");
64 ("GOSA/".$db_module)->import;
65 daemon_log("0 INFO: importing database module '$db_module'", 1);
66 }
68 my $modules_path = "/usr/lib/gosa-si/modules";
69 use lib "/usr/lib/gosa-si/modules";
71 our $global_kernel;
72 my ($foreground, $ping_timeout);
73 my ($server);
74 my ($gosa_server, $job_queue_timeout, $job_queue_loop_delay);
75 my ($messaging_db_loop_delay);
76 my ($procid, $pid);
77 my ($arp_fifo);
78 my ($xml);
79 my $sources_list;
80 my $max_clients;
81 my %repo_files=();
82 my $repo_path;
83 my %repo_dirs=();
85 # Variables declared in config file are always set to 'our'
86 our (%cfg_defaults, $log_file, $pid_file,
87 $server_ip, $server_port, $ClientPackages_key, $dns_lookup,
88 $arp_activ, $gosa_unit_tag,
89 $GosaPackages_key, $gosa_timeout,
90 $foreign_server_string, $server_domain, $ServerPackages_key, $foreign_servers_register_delay,
91 $wake_on_lan_passwd, $job_synchronization, $modified_jobs_loop_delay,
92 $arp_enabled, $arp_interface,
93 $opsi_enabled, $opsi_server, $opsi_admin, $opsi_password,
94 $new_systems_ou,
95 );
97 # additional variable which should be globaly accessable
98 our $server_address;
99 our $server_mac_address;
100 our $gosa_address;
101 our $no_arp;
102 our $verbose;
103 our $forground;
104 our $cfg_file;
105 our ($ldap_uri, $ldap_base, $ldap_admin_dn, $ldap_admin_password, $ldap_server_dn);
106 our ($mysql_username, $mysql_password, $mysql_database, $mysql_host);
107 our $known_modules;
108 our $root_uid;
109 our $adm_gid;
112 # specifies the verbosity of the daemon_log
113 $verbose = 0 ;
115 # if foreground is not null, script will be not forked to background
116 $foreground = 0 ;
118 # specifies the timeout seconds while checking the online status of a registrating client
119 $ping_timeout = 5;
121 $no_arp = 0;
122 my $packages_list_under_construction = "/tmp/packages_list_creation_in_progress";
123 my @packages_list_statements;
124 my $watch_for_new_jobs_in_progress = 0;
126 # holds all incoming decrypted messages
127 our $incoming_db;
128 our $incoming_tn = 'incoming';
129 my $incoming_file_name;
130 my @incoming_col_names = ("id INTEGER PRIMARY KEY",
131 "timestamp VARCHAR(14) DEFAULT 'none'",
132 "headertag VARCHAR(255) DEFAULT 'none'",
133 "targettag VARCHAR(255) DEFAULT 'none'",
134 "xmlmessage TEXT",
135 "module VARCHAR(255) DEFAULT 'none'",
136 "sessionid VARCHAR(255) DEFAULT '0'",
137 );
139 # holds all gosa jobs
140 our $job_db;
141 our $job_queue_tn = 'jobs';
142 my $job_queue_file_name;
143 my @job_queue_col_names = ("id INTEGER PRIMARY KEY",
144 "timestamp VARCHAR(14) DEFAULT 'none'",
145 "status VARCHAR(255) DEFAULT 'none'",
146 "result TEXT",
147 "progress VARCHAR(255) DEFAULT 'none'",
148 "headertag VARCHAR(255) DEFAULT 'none'",
149 "targettag VARCHAR(255) DEFAULT 'none'",
150 "xmlmessage TEXT",
151 "macaddress VARCHAR(17) DEFAULT 'none'",
152 "plainname VARCHAR(255) DEFAULT 'none'",
153 "siserver VARCHAR(255) DEFAULT 'none'",
154 "modified INTEGER DEFAULT '0'",
155 );
157 # holds all other gosa-si-server
158 our $known_server_db;
159 our $known_server_tn = "known_server";
160 my $known_server_file_name;
161 my @known_server_col_names = ("hostname VARCHAR(255)", "macaddress VARCHAR(17)", "status VARCHAR(255)", "hostkey VARCHAR(255)", "loaded_modules TEXT", "timestamp VARCHAR(14)");
163 # holds all registrated clients
164 our $known_clients_db;
165 our $known_clients_tn = "known_clients";
166 my $known_clients_file_name;
167 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)");
169 # holds all registered clients at a foreign server
170 our $foreign_clients_db;
171 our $foreign_clients_tn = "foreign_clients";
172 my $foreign_clients_file_name;
173 my @foreign_clients_col_names = ("hostname VARCHAR(255)", "macaddress VARCHAR(17)", "regserver VARCHAR(255)", "timestamp VARCHAR(14)");
175 # holds all logged in user at each client
176 our $login_users_db;
177 our $login_users_tn = "login_users";
178 my $login_users_file_name;
179 my @login_users_col_names = ("client VARCHAR(255)", "user VARCHAR(255)", "timestamp VARCHAR(14)", "regserver VARCHAR(255) DEFAULT 'localhost'");
181 # holds all fai server, the debian release and tag
182 our $fai_server_db;
183 our $fai_server_tn = "fai_server";
184 my $fai_server_file_name;
185 our @fai_server_col_names = ("timestamp VARCHAR(14)", "server VARCHAR(255)", "fai_release VARCHAR(255)", "sections VARCHAR(255)", "tag VARCHAR(255)");
187 our $fai_release_db;
188 our $fai_release_tn = "fai_release";
189 my $fai_release_file_name;
190 our @fai_release_col_names = ("timestamp VARCHAR(14)", "fai_release VARCHAR(255)", "class VARCHAR(255)", "type VARCHAR(255)", "state VARCHAR(255)");
192 # holds all packages available from different repositories
193 our $packages_list_db;
194 our $packages_list_tn = "packages_list";
195 my $packages_list_file_name;
196 our @packages_list_col_names = ("distribution VARCHAR(255)", "package VARCHAR(255)", "version VARCHAR(255)", "section VARCHAR(255)", "description TEXT", "template LONGBLOB", "timestamp VARCHAR(14)");
197 my $outdir = "/tmp/packages_list_db";
198 my $arch = "i386";
200 # holds all messages which should be delivered to a user
201 our $messaging_db;
202 our $messaging_tn = "messaging";
203 our @messaging_col_names = ("id INTEGER", "subject TEXT", "message_from VARCHAR(255)", "message_to VARCHAR(255)",
204 "flag VARCHAR(255)", "direction VARCHAR(255)", "delivery_time VARCHAR(255)", "message TEXT", "timestamp VARCHAR(14)" );
205 my $messaging_file_name;
207 # path to directory to store client install log files
208 our $client_fai_log_dir = "/var/log/fai";
210 # queue which stores taskes until one of the $max_children children are ready to process the task
211 #my @tasks = qw();
212 my @msgs_to_decrypt = qw();
213 my $max_children = 2;
216 # loop delay for job queue to look for opsi jobs
217 my $job_queue_opsi_delay = 10;
218 our $opsi_client;
219 our $opsi_url;
221 # Lifetime of logged in user information. If no update information comes after n seconds,
222 # the user is expeceted to be no longer logged in or the host is no longer running. Because
223 # of this, the user is deleted from login_users_db
224 our $logged_in_user_date_of_expiry = 600;
227 %cfg_defaults = (
228 "general" => {
229 "log-file" => [\$log_file, "/var/run/".$prg.".log"],
230 "pid-file" => [\$pid_file, "/var/run/".$prg.".pid"],
231 },
232 "server" => {
233 "ip" => [\$server_ip, "0.0.0.0"],
234 "port" => [\$server_port, "20081"],
235 "known-clients" => [\$known_clients_file_name, '/var/lib/gosa-si/clients.db' ],
236 "known-servers" => [\$known_server_file_name, '/var/lib/gosa-si/servers.db'],
237 "incoming" => [\$incoming_file_name, '/var/lib/gosa-si/incoming.db'],
238 "login-users" => [\$login_users_file_name, '/var/lib/gosa-si/users.db'],
239 "fai-server" => [\$fai_server_file_name, '/var/lib/gosa-si/fai_server.db'],
240 "fai-release" => [\$fai_release_file_name, '/var/lib/gosa-si/fai_release.db'],
241 "packages-list" => [\$packages_list_file_name, '/var/lib/gosa-si/packages.db'],
242 "messaging" => [\$messaging_file_name, '/var/lib/gosa-si/messaging.db'],
243 "foreign-clients" => [\$foreign_clients_file_name, '/var/lib/gosa-si/foreign_clients.db'],
244 "source-list" => [\$sources_list, '/etc/apt/sources.list'],
245 "repo-path" => [\$repo_path, '/srv/www/repository'],
246 "ldap-uri" => [\$ldap_uri, ""],
247 "ldap-base" => [\$ldap_base, ""],
248 "ldap-admin-dn" => [\$ldap_admin_dn, ""],
249 "ldap-admin-password" => [\$ldap_admin_password, ""],
250 "gosa-unit-tag" => [\$gosa_unit_tag, ""],
251 "max-clients" => [\$max_clients, 10],
252 "wol-password" => [\$wake_on_lan_passwd, ""],
253 "mysql-username" => [\$mysql_username, "gosa_si"],
254 "mysql-password" => [\$mysql_password, ""],
255 "mysql-database" => [\$mysql_database, "gosa_si"],
256 "mysql-host" => [\$mysql_host, "127.0.0.1"],
257 },
258 "GOsaPackages" => {
259 "job-queue" => [\$job_queue_file_name, '/var/lib/gosa-si/jobs.db'],
260 "job-queue-loop-delay" => [\$job_queue_loop_delay, 3],
261 "messaging-db-loop-delay" => [\$messaging_db_loop_delay, 3],
262 "key" => [\$GosaPackages_key, "none"],
263 "new-systems-ou" => [\$new_systems_ou, 'ou=workstations,ou=systems'],
264 },
265 "ClientPackages" => {
266 "key" => [\$ClientPackages_key, "none"],
267 "user-date-of-expiry" => [\$logged_in_user_date_of_expiry, 600],
268 },
269 "ServerPackages"=> {
270 "address" => [\$foreign_server_string, ""],
271 "dns-lookup" => [\$dns_lookup, "true"],
272 "domain" => [\$server_domain, ""],
273 "key" => [\$ServerPackages_key, "none"],
274 "key-lifetime" => [\$foreign_servers_register_delay, 120],
275 "job-synchronization-enabled" => [\$job_synchronization, "true"],
276 "synchronization-loop" => [\$modified_jobs_loop_delay, 5],
277 },
278 "ArpHandler" => {
279 "enabled" => [\$arp_enabled, "true"],
280 "interface" => [\$arp_interface, "all"],
281 },
282 "Opsi" => {
283 "enabled" => [\$opsi_enabled, "false"],
284 "server" => [\$opsi_server, "localhost"],
285 "admin" => [\$opsi_admin, "opsi-admin"],
286 "password" => [\$opsi_password, "secret"],
287 },
289 );
292 #=== FUNCTION ================================================================
293 # NAME: usage
294 # PARAMETERS: nothing
295 # RETURNS: nothing
296 # DESCRIPTION: print out usage text to STDERR
297 #===============================================================================
298 sub usage {
299 print STDERR << "EOF" ;
300 usage: $prg [-hvf] [-c config]
302 -h : this (help) message
303 -c <file> : config file
304 -f : foreground, process will not be forked to background
305 -v : be verbose (multiple to increase verbosity)
306 -no-arp : starts $prg without connection to arp module
308 EOF
309 print "\n" ;
310 }
313 #=== FUNCTION ================================================================
314 # NAME: logging
315 # PARAMETERS: level - string - default 'info'
316 # msg - string -
317 # facility - string - default 'LOG_DAEMON'
318 # RETURNS: nothing
319 # DESCRIPTION: function for logging
320 #===============================================================================
321 sub daemon_log {
322 # log into log_file
323 my( $msg, $level ) = @_;
324 if(not defined $msg) { return }
325 if(not defined $level) { $level = 1 }
326 if(defined $log_file){
327 my $open_log_fh = sysopen(LOG_HANDLE, $log_file, O_WRONLY | O_CREAT | O_APPEND , 0440);
328 if(not $open_log_fh) {
329 print STDERR "cannot open $log_file: $!";
330 return;
331 }
332 # check owner and group of log_file and update settings if necessary
333 my ($dev, $ino, $mode, $nlink, $uid, $gid, $rdev, $size, $atime, $mtime, $ctime, $blksize, $blocks) = stat($log_file);
334 if((not $uid eq $root_uid) || (not $gid eq $adm_gid)) {
335 chown($root_uid, $adm_gid, $log_file);
336 }
338 chomp($msg);
339 #$msg =~s/\n//g; # no newlines are allowed in log messages, this is important for later log parsing
340 if($level <= $verbose){
341 my ($seconds, $minutes, $hours, $monthday, $month,
342 $year, $weekday, $yearday, $sommertime) = localtime(time);
343 $hours = $hours < 10 ? $hours = "0".$hours : $hours;
344 $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes;
345 $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds;
346 my @monthnames = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
347 $month = $monthnames[$month];
348 $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday;
349 $year+=1900;
350 my $name = $prg;
352 my $log_msg = "$month $monthday $hours:$minutes:$seconds $name $msg\n";
353 print LOG_HANDLE $log_msg;
354 if( $foreground ) {
355 print STDERR $log_msg;
356 }
357 }
358 close( LOG_HANDLE );
359 }
360 }
363 #=== FUNCTION ================================================================
364 # NAME: check_cmdline_param
365 # PARAMETERS: nothing
366 # RETURNS: nothing
367 # DESCRIPTION: validates commandline parameter
368 #===============================================================================
369 sub check_cmdline_param () {
370 my $err_config;
371 my $err_counter = 0;
372 if(not defined($cfg_file)) {
373 $cfg_file = "/etc/gosa-si/server.conf";
374 if(! -r $cfg_file) {
375 $err_config = "please specify a config file";
376 $err_counter += 1;
377 }
378 }
379 if( $err_counter > 0 ) {
380 &usage( "", 1 );
381 if( defined( $err_config)) { print STDERR "$err_config\n"}
382 print STDERR "\n";
383 exit( -1 );
384 }
385 }
388 #=== FUNCTION ================================================================
389 # NAME: check_pid
390 # PARAMETERS: nothing
391 # RETURNS: nothing
392 # DESCRIPTION: handels pid processing
393 #===============================================================================
394 sub check_pid {
395 $pid = -1;
396 # Check, if we are already running
397 if( open(LOCK_FILE, "<$pid_file") ) {
398 $pid = <LOCK_FILE>;
399 if( defined $pid ) {
400 chomp( $pid );
401 if( -f "/proc/$pid/stat" ) {
402 my($stat) = `cat /proc/$pid/stat` =~ m/$pid \((.+)\).*/;
403 if( $stat ) {
404 daemon_log("ERROR: Already running",1);
405 close( LOCK_FILE );
406 exit -1;
407 }
408 }
409 }
410 close( LOCK_FILE );
411 unlink( $pid_file );
412 }
414 # create a syslog msg if it is not to possible to open PID file
415 if (not sysopen(LOCK_FILE, $pid_file, O_WRONLY|O_CREAT|O_EXCL, 0644)) {
416 my($msg) = "Couldn't obtain lockfile '$pid_file' ";
417 if (open(LOCK_FILE, '<', $pid_file)
418 && ($pid = <LOCK_FILE>))
419 {
420 chomp($pid);
421 $msg .= "(PID $pid)\n";
422 } else {
423 $msg .= "(unable to read PID)\n";
424 }
425 if( ! ($foreground) ) {
426 openlog( $0, "cons,pid", "daemon" );
427 syslog( "warning", $msg );
428 closelog();
429 }
430 else {
431 print( STDERR " $msg " );
432 }
433 exit( -1 );
434 }
435 }
437 #=== FUNCTION ================================================================
438 # NAME: import_modules
439 # PARAMETERS: module_path - string - abs. path to the directory the modules
440 # are stored
441 # RETURNS: nothing
442 # DESCRIPTION: each file in module_path which ends with '.pm' and activation
443 # state is on is imported by "require 'file';"
444 #===============================================================================
445 sub import_modules {
446 daemon_log(" ", 1);
448 if (not -e $modules_path) {
449 daemon_log("0 ERROR: cannot find directory or directory is not readable: $modules_path", 1);
450 }
452 opendir (DIR, $modules_path) or die "ERROR while loading modules from directory $modules_path : $!\n";
453 while (defined (my $file = readdir (DIR))) {
454 if (not $file =~ /(\S*?).pm$/) {
455 next;
456 }
457 my $mod_name = $1;
459 # ArpHandler switch
460 if( $file =~ /ArpHandler.pm/ ) {
461 if( $arp_enabled eq "false" ) { next; }
462 }
464 eval { require $file; };
465 if ($@) {
466 daemon_log("0 ERROR: gosa-si-server could not load module $file", 1);
467 daemon_log("$@", 1);
468 exit;
469 } else {
470 my $info = eval($mod_name.'::get_module_info()');
471 # Only load module if get_module_info() returns a non-null object
472 if( $info ) {
473 my ($input_address, $input_key, $event_hash) = @{$info};
474 $known_modules->{$mod_name} = $info;
475 daemon_log("0 INFO: module $mod_name loaded", 5);
476 }
477 }
478 }
480 close (DIR);
481 }
483 #=== FUNCTION ================================================================
484 # NAME: password_check
485 # PARAMETERS: nothing
486 # RETURNS: nothing
487 # DESCRIPTION: escalates an critical error if two modules exist which are avaialable by
488 # the same password
489 #===============================================================================
490 sub password_check {
491 my $passwd_hash = {};
492 while (my ($mod_name, $mod_info) = each %$known_modules) {
493 my $mod_passwd = @$mod_info[1];
494 if (not defined $mod_passwd) { next; }
495 if (not exists $passwd_hash->{$mod_passwd}) {
496 $passwd_hash->{$mod_passwd} = $mod_name;
498 # escalates critical error
499 } else {
500 &daemon_log("0 ERROR: two loaded modules do have the same password. Please modify the 'key'-parameter in config file");
501 &daemon_log("0 ERROR: module='$mod_name' and module='".$passwd_hash->{$mod_passwd}."'");
502 exit( -1 );
503 }
504 }
506 }
509 #=== FUNCTION ================================================================
510 # NAME: sig_int_handler
511 # PARAMETERS: signal - string - signal arose from system
512 # RETURNS: nothing
513 # DESCRIPTION: handels tasks to be done befor signal becomes active
514 #===============================================================================
515 sub sig_int_handler {
516 my ($signal) = @_;
518 # if (defined($ldap_handle)) {
519 # $ldap_handle->disconnect;
520 # }
521 # TODO alle verbliebenden ldap verbindungen aus allen heaps beenden
524 daemon_log("shutting down gosa-si-server", 1);
525 system("kill `ps -C gosa-si-server -o pid=`");
526 }
527 $SIG{INT} = \&sig_int_handler;
530 sub check_key_and_xml_validity {
531 my ($crypted_msg, $module_key, $session_id) = @_;
532 my $msg;
533 my $msg_hash;
534 my $error_string;
535 eval{
536 $msg = &decrypt_msg($crypted_msg, $module_key);
538 if ($msg =~ /<xml>/i){
539 $msg =~ s/\s+/ /g; # just for better daemon_log
540 daemon_log("$session_id DEBUG: decrypted_msg: \n$msg", 9);
541 $msg_hash = $xml->XMLin($msg, ForceArray=>1);
543 ##############
544 # check header
545 if( not exists $msg_hash->{'header'} ) { die "no header specified"; }
546 my $header_l = $msg_hash->{'header'};
547 if( (1 > @{$header_l}) || ( ( 'HASH' eq ref @{$header_l}[0]) && (1 > keys %{@{$header_l}[0]}) ) ) { die 'empty header tag'; }
548 if( 1 < @{$header_l} ) { die 'more than one header specified'; }
549 my $header = @{$header_l}[0];
550 if( 0 == length $header) { die 'empty string in header tag'; }
552 ##############
553 # check source
554 if( not exists $msg_hash->{'source'} ) { die "no source specified"; }
555 my $source_l = $msg_hash->{'source'};
556 if( (1 > @{$source_l}) || ( ( 'HASH' eq ref @{$source_l}[0]) && (1 > keys %{@{$source_l}[0]}) ) ) { die 'empty source tag'; }
557 if( 1 < @{$source_l} ) { die 'more than one source specified'; }
558 my $source = @{$source_l}[0];
559 if( 0 == length $source) { die 'source error'; }
561 ##############
562 # check target
563 if( not exists $msg_hash->{'target'} ) { die "no target specified"; }
564 my $target_l = $msg_hash->{'target'};
565 if( (1 > @{$target_l}) || ( ('HASH' eq ref @{$target_l}[0]) && (1 > keys %{@{$target_l}[0]}) ) ) { die 'empty target tag'; }
566 }
567 };
568 if($@) {
569 daemon_log("$session_id ERROR: do not understand the message: $@", 1);
570 $msg = undef;
571 $msg_hash = undef;
572 }
574 return ($msg, $msg_hash);
575 }
578 sub check_outgoing_xml_validity {
579 my ($msg, $session_id) = @_;
581 my $msg_hash;
582 eval{
583 $msg_hash = $xml->XMLin($msg, ForceArray=>1);
585 ##############
586 # check header
587 my $header_l = $msg_hash->{'header'};
588 if( 1 != @{$header_l} ) {
589 die 'no or more than one headers specified';
590 }
591 my $header = @{$header_l}[0];
592 if( 0 == length $header) {
593 die 'header has length 0';
594 }
596 ##############
597 # check source
598 my $source_l = $msg_hash->{'source'};
599 if( 1 != @{$source_l} ) {
600 die 'no or more than 1 sources specified';
601 }
602 my $source = @{$source_l}[0];
603 if( 0 == length $source) {
604 die 'source has length 0';
605 }
607 # Check if source contains hostname instead of ip address
608 if(not $source =~ /^[a-z0-9\.]+:\d+$/i) {
609 my ($hostname,$port) = split(/:/, $source);
610 my $ip_address = inet_ntoa(scalar gethostbyname($hostname));
611 if(defined($ip_address) && $ip_address =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/ && $port =~ /^\d+$/) {
612 # Write ip address to $source variable
613 $source = "$ip_address:$port";
614 }
615 }
616 unless( $source =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
617 $source =~ /^GOSA$/i) {
618 die "source '$source' is neither a complete ip-address with port nor 'GOSA'";
619 }
621 ##############
622 # check target
623 my $target_l = $msg_hash->{'target'};
624 if( 0 == @{$target_l} ) {
625 die "no targets specified";
626 }
627 foreach my $target (@$target_l) {
628 if( 0 == length $target) {
629 die "target has length 0";
630 }
631 unless( $target =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
632 $target =~ /^GOSA$/i ||
633 $target =~ /^\*$/ ||
634 $target =~ /KNOWN_SERVER/i ||
635 $target =~ /JOBDB/i ||
636 $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 ){
637 die "target '$target' is not a complete ip-address with port or a valid target name or a mac-address";
638 }
639 }
640 };
641 if($@) {
642 daemon_log("$session_id ERROR: outgoing msg is not gosa-si envelope conform: $@", 1);
643 daemon_log("$@ ".(defined($msg) && length($msg)>0)?$msg:"Empty Message", 1);
644 $msg_hash = undef;
645 }
647 return ($msg_hash);
648 }
651 sub input_from_known_server {
652 my ($input, $remote_ip, $session_id) = @_ ;
653 my ($msg, $msg_hash, $module);
655 my $sql_statement= "SELECT * FROM known_server";
656 my $query_res = $known_server_db->select_dbentry( $sql_statement );
658 while( my ($hit_num, $hit) = each %{ $query_res } ) {
659 my $host_name = $hit->{hostname};
660 if( not $host_name =~ "^$remote_ip") {
661 next;
662 }
663 my $host_key = $hit->{hostkey};
664 daemon_log("$session_id DEBUG: input_from_known_server: host_name: $host_name", 7);
665 daemon_log("$session_id DEBUG: input_from_known_server: host_key: $host_key", 7);
667 # check if module can open msg envelope with module key
668 my ($tmp_msg, $tmp_msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
669 if( (!$tmp_msg) || (!$tmp_msg_hash) ) {
670 daemon_log("$session_id DEBUG: input_from_known_server: deciphering raise error", 7);
671 daemon_log("$@", 8);
672 next;
673 }
674 else {
675 $msg = $tmp_msg;
676 $msg_hash = $tmp_msg_hash;
677 $module = "ServerPackages";
678 daemon_log("$session_id DEBUG: check_key_and_xml_validity... ok", 7);
679 last;
680 }
681 }
683 if( (!$msg) || (!$msg_hash) || (!$module) ) {
684 daemon_log("$session_id DEBUG: Incoming message is not from a known server", 7);
685 }
687 return ($msg, $msg_hash, $module);
688 }
691 sub input_from_known_client {
692 my ($input, $remote_ip, $session_id) = @_ ;
693 my ($msg, $msg_hash, $module);
695 my $sql_statement= "SELECT * FROM known_clients";
696 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
697 while( my ($hit_num, $hit) = each %{ $query_res } ) {
698 my $host_name = $hit->{hostname};
699 if( not $host_name =~ /^$remote_ip:\d*$/) {
700 next;
701 }
702 my $host_key = $hit->{hostkey};
703 &daemon_log("$session_id DEBUG: input_from_known_client: host_name: $host_name", 7);
704 &daemon_log("$session_id DEBUG: input_from_known_client: host_key: $host_key", 7);
706 # check if module can open msg envelope with module key
707 ($msg, $msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
709 if( (!$msg) || (!$msg_hash) ) {
710 &daemon_log("$session_id DEGUG: input_from_known_client: deciphering raise error", 7);
711 &daemon_log("$@", 8);
712 next;
713 }
714 else {
715 $module = "ClientPackages";
716 daemon_log("$session_id DEBUG: check_key_and_xml_validity... ok", 7);
717 last;
718 }
719 }
721 if( (!$msg) || (!$msg_hash) || (!$module) ) {
722 &daemon_log("$session_id DEBUG: Incoming message is not from a known client", 7);
723 }
725 return ($msg, $msg_hash, $module);
726 }
729 sub input_from_unknown_host {
730 no strict "refs";
731 my ($input, $session_id) = @_ ;
732 my ($msg, $msg_hash, $module);
733 my $error_string;
735 my %act_modules = %$known_modules;
737 while( my ($mod, $info) = each(%act_modules)) {
739 # check a key exists for this module
740 my $module_key = ${$mod."_key"};
741 if( not defined $module_key ) {
742 if( $mod eq 'ArpHandler' ) {
743 next;
744 }
745 daemon_log("$session_id ERROR: no key specified in config file for $mod", 1);
746 next;
747 }
748 daemon_log("$session_id DEBUG: $mod: $module_key", 7);
750 # check if module can open msg envelope with module key
751 ($msg, $msg_hash) = &check_key_and_xml_validity($input, $module_key, $session_id);
752 if( (not defined $msg) || (not defined $msg_hash) ) {
753 next;
754 } else {
755 $module = $mod;
756 daemon_log("$session_id DEBUG: check_key_and_xml_validity... ok", 7);
757 last;
758 }
759 }
761 if( (!$msg) || (!$msg_hash) || (!$module)) {
762 daemon_log("$session_id DEBUG: Incoming message is not from an unknown host", 7);
763 }
765 return ($msg, $msg_hash, $module);
766 }
769 sub create_ciphering {
770 my ($passwd) = @_;
771 if((!defined($passwd)) || length($passwd)==0) {
772 $passwd = "";
773 }
774 $passwd = substr(md5_hex("$passwd") x 32, 0, 32);
775 my $iv = substr(md5_hex('GONICUS GmbH'),0, 16);
776 my $my_cipher = Crypt::Rijndael->new($passwd , Crypt::Rijndael::MODE_CBC());
777 $my_cipher->set_iv($iv);
778 return $my_cipher;
779 }
782 sub encrypt_msg {
783 my ($msg, $key) = @_;
784 my $my_cipher = &create_ciphering($key);
785 my $len;
786 {
787 use bytes;
788 $len= 16-length($msg)%16;
789 }
790 $msg = "\0"x($len).$msg;
791 $msg = $my_cipher->encrypt($msg);
792 chomp($msg = &encode_base64($msg));
793 # there are no newlines allowed inside msg
794 $msg=~ s/\n//g;
795 return $msg;
796 }
799 sub decrypt_msg {
801 my ($msg, $key) = @_ ;
802 $msg = &decode_base64($msg);
803 my $my_cipher = &create_ciphering($key);
804 $msg = $my_cipher->decrypt($msg);
805 $msg =~ s/\0*//g;
806 return $msg;
807 }
810 sub get_encrypt_key {
811 my ($target) = @_ ;
812 my $encrypt_key;
813 my $error = 0;
815 # target can be in known_server
816 if( not defined $encrypt_key ) {
817 my $sql_statement= "SELECT * FROM known_server WHERE hostname='$target'";
818 my $query_res = $known_server_db->select_dbentry( $sql_statement );
819 while( my ($hit_num, $hit) = each %{ $query_res } ) {
820 my $host_name = $hit->{hostname};
821 if( $host_name ne $target ) {
822 next;
823 }
824 $encrypt_key = $hit->{hostkey};
825 last;
826 }
827 }
829 # target can be in known_client
830 if( not defined $encrypt_key ) {
831 my $sql_statement= "SELECT * FROM known_clients WHERE hostname='$target'";
832 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
833 while( my ($hit_num, $hit) = each %{ $query_res } ) {
834 my $host_name = $hit->{hostname};
835 if( $host_name ne $target ) {
836 next;
837 }
838 $encrypt_key = $hit->{hostkey};
839 last;
840 }
841 }
843 return $encrypt_key;
844 }
847 #=== FUNCTION ================================================================
848 # NAME: open_socket
849 # PARAMETERS: PeerAddr string something like 192.168.1.1 or 192.168.1.1:10000
850 # [PeerPort] string necessary if port not appended by PeerAddr
851 # RETURNS: socket IO::Socket::INET
852 # DESCRIPTION: open a socket to PeerAddr
853 #===============================================================================
854 sub open_socket {
855 my ($PeerAddr, $PeerPort) = @_ ;
856 if(defined($PeerPort)){
857 $PeerAddr = $PeerAddr.":".$PeerPort;
858 }
859 my $socket;
860 $socket = new IO::Socket::INET(PeerAddr => $PeerAddr,
861 Porto => "tcp",
862 Type => SOCK_STREAM,
863 Timeout => 5,
864 );
865 if(not defined $socket) {
866 return;
867 }
868 # &daemon_log("DEBUG: open_socket: $PeerAddr", 7);
869 return $socket;
870 }
873 sub send_msg_to_target {
874 my ($msg, $address, $encrypt_key, $msg_header, $session_id) = @_ ;
875 my $error = 0;
876 my $header;
877 my $timestamp = &get_time();
878 my $new_status;
879 my $act_status;
880 my ($sql_statement, $res);
882 if( $msg_header ) {
883 $header = "'$msg_header'-";
884 } else {
885 $header = "";
886 }
888 # Patch the source ip
889 if($msg =~ /<source>0\.0\.0\.0:\d*?<\/source>/) {
890 my $remote_ip = &get_local_ip_for_remote_ip(sprintf("%s", $address =~ /^([0-9\.]*?):.*$/));
891 $msg =~ s/<source>(0\.0\.0\.0):(\d*?)<\/source>/<source>$remote_ip:$2<\/source>/s;
892 }
894 # encrypt xml msg
895 my $crypted_msg = &encrypt_msg($msg, $encrypt_key);
897 # opensocket
898 my $socket = &open_socket($address);
899 if( !$socket ) {
900 daemon_log("$session_id WARNING: cannot send ".$header."msg to $address , host not reachable", 3);
901 $error++;
902 }
904 if( $error == 0 ) {
905 # send xml msg
906 print $socket $crypted_msg."\n";
908 daemon_log("$session_id INFO: send ".$header."msg to $address", 5);
909 daemon_log("$session_id DEBUG: message:\n$msg", 9);
911 }
913 # close socket in any case
914 if( $socket ) {
915 close $socket;
916 }
918 if( $error > 0 ) { $new_status = "down"; }
919 else { $new_status = $msg_header; }
922 # known_clients
923 $sql_statement = "SELECT * FROM $known_clients_tn WHERE hostname='$address'";
924 $res = $known_clients_db->select_dbentry($sql_statement);
925 if( keys(%$res) == 1) {
926 $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
927 if ($act_status eq "down" && $new_status eq "down") {
928 $sql_statement = "DELETE FROM known_clients WHERE hostname='$address'";
929 $res = $known_clients_db->del_dbentry($sql_statement);
930 daemon_log("$session_id WARNING: failed 2x to send msg to host '$address', delete host from known_clients", 3);
931 } else {
932 $sql_statement = "UPDATE known_clients SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
933 $res = $known_clients_db->update_dbentry($sql_statement);
934 if($new_status eq "down"){
935 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
936 } else {
937 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
938 }
939 }
940 }
942 # known_server
943 $sql_statement = "SELECT * FROM $known_server_tn WHERE hostname='$address'";
944 $res = $known_server_db->select_dbentry($sql_statement);
945 if( keys(%$res) == 1) {
946 $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
947 if ($act_status eq "down" && $new_status eq "down") {
948 $sql_statement = "DELETE FROM known_server WHERE hostname='$address'";
949 $res = $known_server_db->del_dbentry($sql_statement);
950 daemon_log("$session_id WARNING: failed 2x to send a message to host '$address', delete host from known_server", 3);
951 }
952 else {
953 $sql_statement = "UPDATE known_server SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
954 $res = $known_server_db->update_dbentry($sql_statement);
955 if($new_status eq "down"){
956 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
957 } else {
958 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
959 }
960 }
961 }
962 return $error;
963 }
966 sub update_jobdb_status_for_send_msgs {
967 my ($session_id, $answer, $error) = @_;
968 &daemon_log("$session_id DEBUG: try to update job status", 7);
969 if( $answer =~ /<jobdb_id>(\d+)<\/jobdb_id>/ ) {
970 my $jobdb_id = $1;
972 $answer =~ /<header>(.*)<\/header>/;
973 my $job_header = $1;
975 $answer =~ /<target>(.*)<\/target>/;
976 my $job_target = $1;
978 # Sending msg failed
979 if( $error ) {
981 # Set jobs to done, jobs do not need to deliver their message in any case
982 if (($job_header eq "trigger_action_localboot")
983 ||($job_header eq "trigger_action_lock")
984 ||($job_header eq "trigger_action_halt")
985 ) {
986 my $sql_statement = "UPDATE $job_queue_tn SET status='done' WHERE id=$jobdb_id";
987 &daemon_log("$session_id DEBUG: $sql_statement", 7);
988 my $res = $job_db->update_dbentry($sql_statement);
990 # Reactivate jobs, jobs need to deliver their message
991 } elsif (($job_header eq "trigger_action_activate")
992 ||($job_header eq "trigger_action_update")
993 ||($job_header eq "trigger_action_reinstall")
994 ||($job_header eq "trigger_activate_new")
995 ) {
996 &reactivate_job_with_delay($session_id, $job_target, $job_header, 30 );
998 # For all other messages
999 } else {
1000 my $sql_statement = "UPDATE $job_queue_tn ".
1001 "SET status='error', result='can not deliver msg, please consult log file' ".
1002 "WHERE id=$jobdb_id";
1003 &daemon_log("$session_id DEBUG: $sql_statement", 7);
1004 my $res = $job_db->update_dbentry($sql_statement);
1005 }
1007 # Sending msg was successful
1008 } else {
1009 # Set jobs localboot, lock, activate, halt, reboot and wake to done
1010 # jobs reinstall, update, inst_update do themself setting to done
1011 if (($job_header eq "trigger_action_localboot")
1012 ||($job_header eq "trigger_action_lock")
1013 ||($job_header eq "trigger_action_activate")
1014 ||($job_header eq "trigger_action_halt")
1015 ||($job_header eq "trigger_action_reboot")
1016 ||($job_header eq "trigger_action_wake")
1017 ||($job_header eq "trigger_wake")
1018 ) {
1020 my $sql_statement = "UPDATE $job_queue_tn ".
1021 "SET status='done' ".
1022 "WHERE id=$jobdb_id AND status='processed'";
1023 &daemon_log("$session_id DEBUG: $sql_statement", 7);
1024 my $res = $job_db->update_dbentry($sql_statement);
1025 } else {
1026 &daemon_log("$session_id DEBUG: sending message succeed but cannot update job status.", 7);
1027 }
1028 }
1029 } else {
1030 &daemon_log("$session_id DEBUG: cannot update job status, msg has no jobdb_id-tag: $answer", 7);
1031 }
1032 }
1034 sub reactivate_job_with_delay {
1035 my ($session_id, $target, $header, $delay) = @_ ;
1036 # 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
1038 if (not defined $delay) { $delay = 30 } ;
1039 my $delay_timestamp = &calc_timestamp(&get_time(), "plus", $delay);
1041 my $sql = "UPDATE $job_queue_tn Set timestamp='$delay_timestamp', status='waiting' WHERE (macaddress='$target' AND headertag='$header')";
1042 my $res = $job_db->update_dbentry($sql);
1043 daemon_log("$session_id INFO: '$header'-job will be reactivated at '$delay_timestamp' ".
1044 "cause client '$target' is currently not available", 5);
1045 daemon_log("$session_id $sql", 7);
1046 return;
1047 }
1050 sub sig_handler {
1051 my ($kernel, $signal) = @_[KERNEL, ARG0] ;
1052 daemon_log("0 INFO got signal '$signal'", 1);
1053 $kernel->sig_handled();
1054 return;
1055 }
1058 sub msg_to_decrypt {
1059 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
1060 my $session_id = $session->ID;
1061 my ($msg, $msg_hash, $module);
1062 my $error = 0;
1064 # fetch new msg out of @msgs_to_decrypt
1065 my $tmp_next_msg = shift @msgs_to_decrypt;
1066 my ($next_msg, $msg_source) = split(/;/, $tmp_next_msg);
1068 # msg is from a new client or gosa
1069 ($msg, $msg_hash, $module) = &input_from_unknown_host($next_msg, $session_id);
1071 # msg is from a gosa-si-server
1072 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1073 ($msg, $msg_hash, $module) = &input_from_known_server($next_msg, $heap->{'remote_ip'}, $session_id);
1074 }
1075 # msg is from a gosa-si-client
1076 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1077 ($msg, $msg_hash, $module) = &input_from_known_client($next_msg, $heap->{'remote_ip'}, $session_id);
1078 }
1079 # an error occurred
1080 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1081 # if an incoming msg could not be decrypted (maybe a wrong key), send client a ping. If the client
1082 # could not understand a msg from its server the client cause a re-registering process
1083 my $remote_ip = $heap->{'remote_ip'};
1084 my $remote_port = $heap->{'remote_port'};
1085 my $ping_msg = "<xml> <header>gosa_ping</header> <source>$server_address</source><target>$msg_source</target></xml>";
1086 my ($test_error, $test_error_string) = &send_msg_to_target($ping_msg, "$msg_source", "dummy-key", "gosa_ping", $session_id);
1088 daemon_log("$session_id WARNING cannot understand incoming msg, send 'ping'-msg to all host with ip '".$heap->{remote_ip}.
1089 "' to cause a re-registering of the client if necessary", 3);
1090 $error++;
1091 }
1094 my $header;
1095 my $target;
1096 my $source;
1097 my $done = 0;
1098 my $sql;
1099 my $res;
1101 # check whether this message should be processed here
1102 if ($error == 0) {
1103 $header = @{$msg_hash->{'header'}}[0];
1104 $target = @{$msg_hash->{'target'}}[0];
1105 $source = @{$msg_hash->{'source'}}[0];
1106 my $not_found_in_known_clients_db = 0;
1107 my $not_found_in_known_server_db = 0;
1108 my $not_found_in_foreign_clients_db = 0;
1109 my $local_address;
1110 my $local_mac;
1111 my ($target_ip, $target_port) = split(':', $target);
1113 # Determine the local ip address if target is an ip address
1114 if ($target =~ /^\d+\.\d+\.\d+\.\d+:\d+$/) {
1115 $local_address = &get_local_ip_for_remote_ip($target_ip).":$server_port";
1116 } else {
1117 $local_address = $server_address;
1118 }
1120 # Determine the local mac address if target is a mac address
1121 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) {
1122 my $loc_ip = &get_local_ip_for_remote_ip($heap->{'remote_ip'});
1123 my $network_interface= &get_interface_for_ip($loc_ip);
1124 $local_mac = &get_mac_for_interface($network_interface);
1125 } else {
1126 $local_mac = $server_mac_address;
1127 }
1129 # target and source is equal to GOSA -> process here
1130 if (not $done) {
1131 if ($target eq "GOSA" && $source eq "GOSA") {
1132 $done = 1;
1133 &daemon_log("$session_id DEBUG: target and source is 'GOSA' -> process here", 7);
1134 }
1135 }
1137 # target is own address without forward_to_gosa-tag -> process here
1138 if (not $done) {
1139 #if ((($target eq $local_address) || ($target eq $local_mac) ) && (not exists $msg_hash->{'forward_to_gosa'})) {
1140 if (($target eq $local_address) && (not exists $msg_hash->{'forward_to_gosa'})) {
1141 $done = 1;
1142 if ($source eq "GOSA") {
1143 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1144 }
1145 &daemon_log("$session_id DEBUG: target is own address without forward_to_gosa-tag -> process here", 7);
1146 }
1147 }
1149 # target is a client address in known_clients -> process here
1150 if (not $done) {
1151 $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1152 $res = $known_clients_db->select_dbentry($sql);
1153 if (keys(%$res) > 0) {
1154 $done = 1;
1155 my $hostname = $res->{1}->{'hostname'};
1156 $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1157 my $local_address = &get_local_ip_for_remote_ip($target_ip).":$server_port";
1158 if ($source eq "GOSA") {
1159 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1160 }
1161 &daemon_log("$session_id DEBUG: target is a client address in known_clients -> process here", 7);
1163 } else {
1164 $not_found_in_known_clients_db = 1;
1165 }
1166 }
1168 # target ist own address with forward_to_gosa-tag not pointing to myself -> process here
1169 if (not $done) {
1170 my $forward_to_gosa = @{$msg_hash->{'forward_to_gosa'}}[0];
1171 my $gosa_at;
1172 my $gosa_session_id;
1173 if (($target eq $local_address) && (defined $forward_to_gosa)){
1174 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1175 if ($gosa_at ne $local_address) {
1176 $done = 1;
1177 &daemon_log("$session_id DEBUG: target is own address with forward_to_gosa-tag not pointing to myself -> process here", 7);
1178 }
1179 }
1180 }
1182 # if message should be processed here -> add message to incoming_db
1183 if ($done) {
1184 # if a job or a gosa message comes from a foreign server, fake module to GosaPackages
1185 # so gosa-si-server knows how to process this kind of messages
1186 if ($header =~ /^gosa_/ || $header =~ /^job_/) {
1187 $module = "GosaPackages";
1188 }
1190 my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1191 primkey=>[],
1192 headertag=>$header,
1193 targettag=>$target,
1194 xmlmessage=>&encode_base64($msg),
1195 timestamp=>&get_time,
1196 module=>$module,
1197 sessionid=>$session_id,
1198 } );
1200 }
1202 # target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa
1203 if (not $done) {
1204 my $forward_to_gosa = @{$msg_hash->{'forward_to_gosa'}}[0];
1205 my $gosa_at;
1206 my $gosa_session_id;
1207 if (($target eq $local_address) && (defined $forward_to_gosa)){
1208 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1209 if ($gosa_at eq $local_address) {
1210 my $session_reference = $kernel->ID_id_to_session($gosa_session_id);
1211 if( defined $session_reference ) {
1212 $heap = $session_reference->get_heap();
1213 }
1214 if(exists $heap->{'client'}) {
1215 $msg = &encrypt_msg($msg, $GosaPackages_key);
1216 $heap->{'client'}->put($msg);
1217 &daemon_log("$session_id INFO: incoming '$header' message forwarded to GOsa", 5);
1218 }
1219 $done = 1;
1220 &daemon_log("$session_id DEBUG: target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa", 7);
1221 }
1222 }
1224 }
1226 # target is a client address in foreign_clients -> forward to registration server
1227 if (not $done) {
1228 $sql = "SELECT * FROM $foreign_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1229 $res = $foreign_clients_db->select_dbentry($sql);
1230 if (keys(%$res) > 0) {
1231 my $hostname = $res->{1}->{'hostname'};
1232 my ($host_ip, $host_port) = split(/:/, $hostname);
1233 my $local_address = &get_local_ip_for_remote_ip($host_ip).":$server_port";
1234 my $regserver = $res->{1}->{'regserver'};
1235 my $sql = "SELECT * FROM $known_server_tn WHERE hostname='$regserver'";
1236 my $res = $known_server_db->select_dbentry($sql);
1237 if (keys(%$res) > 0) {
1238 my $regserver_key = $res->{1}->{'hostkey'};
1239 $msg =~ s/<source>GOSA<\/source>/<source>$local_address<\/source>/;
1240 $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1241 if ($source eq "GOSA") {
1242 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1243 }
1244 &send_msg_to_target($msg, $regserver, $regserver_key, $header, $session_id);
1245 }
1246 $done = 1;
1247 &daemon_log("$session_id DEBUG: target is a client address in foreign_clients -> forward to registration server", 7);
1248 } else {
1249 $not_found_in_foreign_clients_db = 1;
1250 }
1251 }
1253 # target is a server address -> forward to server
1254 if (not $done) {
1255 $sql = "SELECT * FROM $known_server_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1256 $res = $known_server_db->select_dbentry($sql);
1257 if (keys(%$res) > 0) {
1258 my $hostkey = $res->{1}->{'hostkey'};
1260 if ($source eq "GOSA") {
1261 $msg =~ s/<source>GOSA<\/source>/<source>$local_address<\/source>/;
1262 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1264 }
1266 &send_msg_to_target($msg, $target, $hostkey, $header, $session_id);
1267 $done = 1;
1268 &daemon_log("$session_id DEBUG: target is a server address -> forward to server", 7);
1269 } else {
1270 $not_found_in_known_server_db = 1;
1271 }
1272 }
1275 # target is not in foreign_clients_db, known_server_db or known_clients_db, maybe it is a complete new one -> process here
1276 if ( $not_found_in_foreign_clients_db
1277 && $not_found_in_known_server_db
1278 && $not_found_in_known_clients_db) {
1279 &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);
1280 if ($header =~ /^gosa_/ || $header =~ /^job_/) {
1281 $module = "GosaPackages";
1282 }
1283 my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1284 primkey=>[],
1285 headertag=>$header,
1286 targettag=>$target,
1287 xmlmessage=>&encode_base64($msg),
1288 timestamp=>&get_time,
1289 module=>$module,
1290 sessionid=>$session_id,
1291 } );
1292 $done = 1;
1293 }
1296 if (not $done) {
1297 daemon_log("$session_id ERROR: do not know what to do with this message: $msg", 1);
1298 if ($source eq "GOSA") {
1299 my %data = ('error_msg' => &encode_base64($msg), 'error_string' => "Do not know what to do with this message!");
1300 my $error_msg = &build_msg("error", $local_address, "GOSA", \%data );
1302 my $session_reference = $kernel->ID_id_to_session($session_id);
1303 if( defined $session_reference ) {
1304 $heap = $session_reference->get_heap();
1305 }
1306 if(exists $heap->{'client'}) {
1307 $error_msg = &encrypt_msg($error_msg, $GosaPackages_key);
1308 $heap->{'client'}->put($error_msg);
1309 }
1310 }
1311 }
1313 }
1315 return;
1316 }
1319 sub next_task {
1320 my ($session, $heap, $task) = @_[SESSION, HEAP, ARG0];
1321 my $running_task = POE::Wheel::Run->new(
1322 Program => sub { process_task($session, $heap, $task) },
1323 StdioFilter => POE::Filter::Reference->new(),
1324 StdoutEvent => "task_result",
1325 StderrEvent => "task_debug",
1326 CloseEvent => "task_done",
1327 );
1328 $heap->{task}->{ $running_task->ID } = $running_task;
1329 }
1331 sub handle_task_result {
1332 my ($kernel, $heap, $result) = @_[KERNEL, HEAP, ARG0];
1333 my $client_answer = $result->{'answer'};
1334 if( $client_answer =~ s/session_id=(\d+)$// ) {
1335 my $session_id = $1;
1336 if( defined $session_id ) {
1337 my $session_reference = $kernel->ID_id_to_session($session_id);
1338 if( defined $session_reference ) {
1339 $heap = $session_reference->get_heap();
1340 }
1341 }
1343 if(exists $heap->{'client'}) {
1344 $heap->{'client'}->put($client_answer);
1345 }
1346 }
1347 $kernel->sig(CHLD => "child_reap");
1348 }
1350 sub handle_task_debug {
1351 my $result = $_[ARG0];
1352 print STDERR "$result\n";
1353 }
1355 sub handle_task_done {
1356 my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1357 delete $heap->{task}->{$task_id};
1358 }
1360 sub process_task {
1361 no strict "refs";
1362 #CHECK: Not @_[...]?
1363 my ($session, $heap, $task) = @_;
1364 my $error = 0;
1365 my $answer_l;
1366 my ($answer_header, @answer_target_l, $answer_source);
1367 my $client_answer = "";
1369 # prepare all variables needed to process message
1370 #my $msg = $task->{'xmlmessage'};
1371 my $msg = &decode_base64($task->{'xmlmessage'});
1372 my $incoming_id = $task->{'id'};
1373 my $module = $task->{'module'};
1374 my $header = $task->{'headertag'};
1375 my $session_id = $task->{'sessionid'};
1376 my $msg_hash;
1377 eval {
1378 $msg_hash = $xml->XMLin($msg, ForceArray=>1);
1379 };
1380 daemon_log("ERROR: XML failure '$@'") if ($@);
1381 my $source = @{$msg_hash->{'source'}}[0];
1383 # set timestamp of incoming client uptodate, so client will not
1384 # be deleted from known_clients because of expiration
1385 my $cur_time = &get_time();
1386 my $sql = "UPDATE $known_clients_tn SET timestamp='$cur_time' WHERE hostname='$source'";
1387 my $res = $known_clients_db->exec_statement($sql);
1389 ######################
1390 # process incoming msg
1391 if( $error == 0) {
1392 daemon_log("$session_id INFO: Incoming msg (session_id=$session_id) with header '".@{$msg_hash->{'header'}}[0]."'", 5);
1393 daemon_log("$session_id DEBUG: Processing module ".$module, 7);
1394 $answer_l = &{ $module."::process_incoming_msg" }($msg, $msg_hash, $session_id);
1396 if ( 0 < @{$answer_l} ) {
1397 my $answer_str = join("\n", @{$answer_l});
1398 while ($answer_str =~ /<header>(\w+)<\/header>/g) {
1399 daemon_log("$session_id INFO: got answer message with header '$1'", 5);
1400 }
1401 daemon_log("$session_id DEBUG: $module: got answer from module: \n".$answer_str,9);
1402 } else {
1403 daemon_log("$session_id DEBUG: $module: got no answer from module!" ,7);
1404 }
1406 }
1407 if( !$answer_l ) { $error++ };
1409 ########
1410 # answer
1411 if( $error == 0 ) {
1413 foreach my $answer ( @{$answer_l} ) {
1414 # check outgoing msg to xml validity
1415 my $answer_hash = &check_outgoing_xml_validity($answer, $session_id);
1416 if( not defined $answer_hash ) { next; }
1418 $answer_header = @{$answer_hash->{'header'}}[0];
1419 @answer_target_l = @{$answer_hash->{'target'}};
1420 $answer_source = @{$answer_hash->{'source'}}[0];
1422 # deliver msg to all targets
1423 foreach my $answer_target ( @answer_target_l ) {
1425 # targets of msg are all gosa-si-clients in known_clients_db
1426 if( $answer_target eq "*" ) {
1427 # answer is for all clients
1428 my $sql_statement= "SELECT * FROM known_clients";
1429 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1430 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1431 my $host_name = $hit->{hostname};
1432 my $host_key = $hit->{hostkey};
1433 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1434 &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1435 }
1436 }
1438 # targets of msg are all gosa-si-server in known_server_db
1439 elsif( $answer_target eq "KNOWN_SERVER" ) {
1440 # answer is for all server in known_server
1441 my $sql_statement= "SELECT * FROM $known_server_tn";
1442 my $query_res = $known_server_db->select_dbentry( $sql_statement );
1443 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1444 my $host_name = $hit->{hostname};
1445 my $host_key = $hit->{hostkey};
1446 $answer =~ s/<target>\S+<\/target>/<target>$host_name<\/target>/g;
1447 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1448 &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1449 }
1450 }
1452 # target of msg is GOsa
1453 elsif( $answer_target eq "GOSA" ) {
1454 my $session_id = ($1) if $answer =~ /<session_id>(\d+?)<\/session_id>/;
1455 my $add_on = "";
1456 if( defined $session_id ) {
1457 $add_on = ".session_id=$session_id";
1458 }
1459 # answer is for GOSA and has to returned to connected client
1460 my $gosa_answer = &encrypt_msg($answer, $GosaPackages_key);
1461 $client_answer = $gosa_answer.$add_on;
1462 }
1464 # target of msg is job queue at this host
1465 elsif( $answer_target eq "JOBDB") {
1466 $answer =~ /<header>(\S+)<\/header>/;
1467 my $header;
1468 if( defined $1 ) { $header = $1; }
1469 my $error = &send_msg_to_target($answer, $server_address, $GosaPackages_key, $header, $session_id);
1470 &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1471 }
1473 # Target of msg is a mac address
1474 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 ) {
1475 daemon_log("$session_id INFO: target is mac address '$answer_target', looking for host in known_clients and foreign_clients", 5);
1477 # Looking for macaddress in known_clients
1478 my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$answer_target'";
1479 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1480 my $found_ip_flag = 0;
1481 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1482 my $host_name = $hit->{hostname};
1483 my $host_key = $hit->{hostkey};
1484 $answer =~ s/$answer_target/$host_name/g;
1485 daemon_log("$session_id INFO: found host '$host_name', associated to '$answer_target'", 5);
1486 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1487 &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1488 $found_ip_flag++ ;
1489 }
1491 # Looking for macaddress in foreign_clients
1492 if ($found_ip_flag == 0) {
1493 my $sql = "SELECT * FROM $foreign_clients_tn WHERE macaddress LIKE '$answer_target'";
1494 my $res = $foreign_clients_db->select_dbentry($sql);
1495 while( my ($hit_num, $hit) = each %{ $res } ) {
1496 my $host_name = $hit->{hostname};
1497 my $reg_server = $hit->{regserver};
1498 daemon_log("$session_id INFO: found host '$host_name' with mac '$answer_target', registered at '$reg_server'", 5);
1500 # Fetch key for reg_server
1501 my $reg_server_key;
1502 my $sql = "SELECT * FROM $known_server_tn WHERE hostname='$reg_server'";
1503 my $res = $known_server_db->select_dbentry($sql);
1504 if (exists $res->{1}) {
1505 $reg_server_key = $res->{1}->{'hostkey'};
1506 } else {
1507 daemon_log("$session_id ERROR: cannot find hostkey for '$host_name' in '$known_server_tn'", 1);
1508 daemon_log("$session_id ERROR: unable to forward answer to correct registration server, processing is aborted!", 1);
1509 $reg_server_key = undef;
1510 }
1512 # Send answer to server where client is registered
1513 if (defined $reg_server_key) {
1514 $answer =~ s/$answer_target/$host_name/g;
1515 my $error = &send_msg_to_target($answer, $reg_server, $reg_server_key, $answer_header, $session_id);
1516 &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1517 $found_ip_flag++ ;
1518 }
1519 }
1520 }
1522 # No mac to ip matching found
1523 if( $found_ip_flag == 0) {
1524 daemon_log("$session_id WARNING: no host found in known_clients or foreign_clients with mac address '$answer_target'", 3);
1525 &reactivate_job_with_delay($session_id, $answer_target, $answer_header, 30);
1526 }
1528 # Answer is for one specific host
1529 } else {
1530 # get encrypt_key
1531 my $encrypt_key = &get_encrypt_key($answer_target);
1532 if( not defined $encrypt_key ) {
1533 # unknown target
1534 daemon_log("$session_id WARNING: unknown target '$answer_target'", 3);
1535 next;
1536 }
1537 my $error = &send_msg_to_target($answer, $answer_target, $encrypt_key, $answer_header,$session_id);
1538 &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1539 }
1540 }
1541 }
1542 }
1544 my $filter = POE::Filter::Reference->new();
1545 my %result = (
1546 status => "seems ok to me",
1547 answer => $client_answer,
1548 );
1550 my $output = $filter->put( [ \%result ] );
1551 print @$output;
1554 }
1556 sub session_start {
1557 my ($kernel) = $_[KERNEL];
1558 $global_kernel = $kernel;
1559 $kernel->yield('register_at_foreign_servers');
1560 $kernel->yield('create_fai_server_db', $fai_server_tn );
1561 $kernel->yield('create_fai_release_db', $fai_release_tn );
1562 $kernel->yield('watch_for_next_tasks');
1563 $kernel->sig(USR1 => "sig_handler");
1564 $kernel->sig(USR2 => "recreate_packages_db");
1565 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1566 $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay);
1567 $kernel->delay_set('watch_for_modified_jobs', $modified_jobs_loop_delay);
1568 $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1569 $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1570 $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1571 $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1573 # Start opsi check
1574 if ($opsi_enabled eq "true") {
1575 $kernel->delay_set('watch_for_opsi_jobs', $job_queue_opsi_delay);
1576 }
1578 }
1581 sub watch_for_done_jobs {
1582 #CHECK: $heap for what?
1583 my ($kernel,$heap) = @_[KERNEL, HEAP];
1585 my $sql_statement = "SELECT * FROM ".$job_queue_tn." WHERE ((status='done') AND (modified='0'))";
1586 my $res = $job_db->select_dbentry( $sql_statement );
1588 while( my ($id, $hit) = each %{$res} ) {
1589 my $jobdb_id = $hit->{id};
1590 my $sql_statement = "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1591 my $res = $job_db->del_dbentry($sql_statement);
1592 }
1594 $kernel->delay_set('watch_for_done_jobs',$job_queue_loop_delay);
1595 }
1598 sub watch_for_opsi_jobs {
1599 my ($kernel) = $_[KERNEL];
1601 # 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
1602 # opsi install job is to parse the xml message. There is still the correct header.
1603 my $sql_statement = "SELECT * FROM ".$job_queue_tn." WHERE ((xmlmessage LIKE '%opsi_install_client</header>%') AND (status='processing') AND (siserver='localhost'))";
1604 my $res = $job_db->select_dbentry( $sql_statement );
1606 # Ask OPSI for an update of the running jobs
1607 while (my ($id, $hit) = each %$res ) {
1608 # Determine current parameters of the job
1609 my $hostId = $hit->{'plainname'};
1610 my $macaddress = $hit->{'macaddress'};
1611 my $progress = $hit->{'progress'};
1613 my $result= {};
1615 # For hosts, only return the products that are or get installed
1616 my $callobj;
1617 $callobj = {
1618 method => 'getProductStates_hash',
1619 params => [ $hostId ],
1620 id => 1,
1621 };
1623 my $hres = $opsi_client->call($opsi_url, $callobj);
1624 #my ($hres_err, $hres_err_string) = &check_opsi_res($hres);
1625 if (not &check_opsi_res($hres)) {
1626 my $htmp= $hres->result->{$hostId};
1628 # Check state != not_installed or action == setup -> load and add
1629 my $products= 0;
1630 my $installed= 0;
1631 my $installing = 0;
1632 my $error= 0;
1633 my @installed_list;
1634 my @error_list;
1635 my $act_status = "none";
1636 foreach my $product (@{$htmp}){
1638 if ($product->{'installationStatus'} ne "not_installed" or
1639 $product->{'actionRequest'} eq "setup"){
1641 # Increase number of products for this host
1642 $products++;
1644 if ($product->{'installationStatus'} eq "failed"){
1645 $result->{$product->{'productId'}}= "error";
1646 unshift(@error_list, $product->{'productId'});
1647 $error++;
1648 }
1649 if ($product->{'installationStatus'} eq "installed" && $product->{'actionRequest'} eq "none"){
1650 $result->{$product->{'productId'}}= "installed";
1651 unshift(@installed_list, $product->{'productId'});
1652 $installed++;
1653 }
1654 if ($product->{'installationStatus'} eq "installing"){
1655 $result->{$product->{'productId'}}= "installing";
1656 $installing++;
1657 $act_status = "installing - ".$product->{'productId'};
1658 }
1659 }
1660 }
1662 # Estimate "rough" progress, avoid division by zero
1663 if ($products == 0) {
1664 $result->{'progress'}= 0;
1665 } else {
1666 $result->{'progress'}= int($installed * 100 / $products);
1667 }
1669 # Set updates in job queue
1670 if ((not $error) && (not $installing) && ($installed)) {
1671 $act_status = "installed - ".join(", ", @installed_list);
1672 }
1673 if ($error) {
1674 $act_status = "error - ".join(", ", @error_list);
1675 }
1676 if ($progress ne $result->{'progress'} ) {
1677 # Updating progress and result
1678 my $update_statement = "UPDATE $job_queue_tn SET modified='1', progress='".$result->{'progress'}."', result='$act_status' WHERE macaddress='$macaddress' AND siserver='localhost'";
1679 my $update_res = $job_db->update_dbentry($update_statement);
1680 }
1681 if ($progress eq 100) {
1682 # Updateing status
1683 my $done_statement = "UPDATE $job_queue_tn SET modified='1', ";
1684 if ($error) {
1685 $done_statement .= "status='error'";
1686 } else {
1687 $done_statement .= "status='done'";
1688 }
1689 $done_statement .= " WHERE macaddress='$macaddress' AND siserver='localhost'";
1690 my $done_res = $job_db->update_dbentry($done_statement);
1691 }
1694 }
1695 }
1697 $kernel->delay_set('watch_for_opsi_jobs', $job_queue_opsi_delay);
1698 }
1701 # If a job got an update or was modified anyway, send to all other si-server an update message of this jobs.
1702 sub watch_for_modified_jobs {
1703 my ($kernel,$heap) = @_[KERNEL, HEAP];
1705 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE (modified='1')";
1706 my $res = $job_db->select_dbentry( $sql_statement );
1708 # if db contains no jobs which should be update, do nothing
1709 if (keys %$res != 0) {
1711 if ($job_synchronization eq "true") {
1712 # make out of the db result a gosa-si message
1713 my $update_msg = &db_res2si_msg ($res, "foreign_job_updates", "KNOWN_SERVER", "MY_LOCAL_ADDRESS");
1715 # update all other SI-server
1716 &inform_all_other_si_server($update_msg);
1717 }
1719 # set jobs all jobs to modified = 0, wait until the next modification for updates of other si-server
1720 $sql_statement = "UPDATE $job_queue_tn SET modified='0' ";
1721 $res = $job_db->update_dbentry($sql_statement);
1722 }
1724 $kernel->delay_set('watch_for_modified_jobs', $modified_jobs_loop_delay);
1725 }
1728 sub watch_for_new_jobs {
1729 if($watch_for_new_jobs_in_progress == 0) {
1730 $watch_for_new_jobs_in_progress = 1;
1731 my ($kernel,$heap) = @_[KERNEL, HEAP];
1733 # check gosa job quaeue for jobs with executable timestamp
1734 my $timestamp = &get_time();
1735 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE status='waiting' AND (CAST(timestamp AS UNSIGNED)) < $timestamp ORDER BY timestamp";
1736 my $res = $job_db->exec_statement( $sql_statement );
1738 # Merge all new jobs that would do the same actions
1739 my @drops;
1740 my $hits;
1741 foreach my $hit (reverse @{$res} ) {
1742 my $macaddress= lc @{$hit}[8];
1743 my $headertag= @{$hit}[5];
1744 if(
1745 defined($hits->{$macaddress}) &&
1746 defined($hits->{$macaddress}->{$headertag}) &&
1747 defined($hits->{$macaddress}->{$headertag}[0])
1748 ) {
1749 push @drops, "DELETE FROM $job_queue_tn WHERE id = $hits->{$macaddress}->{$headertag}[0]";
1750 }
1751 $hits->{$macaddress}->{$headertag}= $hit;
1752 }
1754 # Delete new jobs with a matching job in state 'processing'
1755 foreach my $macaddress (keys %{$hits}) {
1756 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1757 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1758 if(defined($jobdb_id)) {
1759 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND headertag='$jobdb_headertag' AND status='processing'";
1760 my $res = $job_db->exec_statement( $sql_statement );
1761 foreach my $hit (@{$res}) {
1762 push @drops, "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1763 }
1764 } else {
1765 daemon_log("J ERROR: Job without id exists for macaddress $macaddress!", 1);
1766 }
1767 }
1768 }
1770 # Commit deletion
1771 $job_db->exec_statementlist(\@drops);
1773 # Look for new jobs that could be executed
1774 foreach my $macaddress (keys %{$hits}) {
1776 # Look if there is an executing job
1777 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='processing'";
1778 my $res = $job_db->exec_statement( $sql_statement );
1780 # Skip new jobs for host if there is a processing job
1781 if(defined($res) and defined @{$res}[0]) {
1782 # Prevent race condition if there is a trigger_activate job waiting and a goto-activation job processing
1783 my $row = @{$res}[0] if (ref $res eq 'ARRAY');
1784 if(@{$row}[5] eq 'trigger_action_reinstall') {
1785 my $sql_statement_2 = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='waiting' AND headertag = 'trigger_activate_new'";
1786 my $res_2 = $job_db->exec_statement( $sql_statement_2 );
1787 if(defined($res_2) and defined @{$res_2}[0]) {
1788 # Set status from goto-activation to 'waiting' and update timestamp
1789 $job_db->exec_statement("UPDATE $job_queue_tn SET status='waiting' WHERE macaddress LIKE '$macaddress' AND headertag = 'trigger_action_reinstall'");
1790 $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'");
1791 }
1792 }
1793 next;
1794 }
1796 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1797 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1798 if(defined($jobdb_id)) {
1799 my $job_msg = @{$hits->{$macaddress}->{$jobdb_headertag}}[7];
1801 daemon_log("J DEBUG: its time to execute $job_msg", 7);
1802 my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$macaddress'";
1803 my $res_hash = $known_clients_db->select_dbentry( $sql_statement );
1805 # expect macaddress is unique!!!!!!
1806 my $target = $res_hash->{1}->{hostname};
1808 # change header
1809 $job_msg =~ s/<header>job_/<header>gosa_/;
1811 # add sqlite_id
1812 $job_msg =~ s/<\/xml>$/<jobdb_id>$jobdb_id<\/jobdb_id><\/xml>/;
1814 $job_msg =~ /<header>(\S+)<\/header>/;
1815 my $header = $1 ;
1816 my $func_error = &send_msg_to_target($job_msg, $server_address, $GosaPackages_key, $header, "J");
1818 # update status in job queue to ...
1819 # ... 'processing', for jobs: 'reinstall', 'update'
1820 if (($header =~ /gosa_trigger_action_reinstall/)
1821 || ($header =~ /gosa_trigger_activate_new/)
1822 || ($header =~ /gosa_trigger_action_update/)) {
1823 my $sql_statement = "UPDATE $job_queue_tn SET status='processing' WHERE id=$jobdb_id";
1824 my $dbres = $job_db->update_dbentry($sql_statement);
1825 }
1827 # ... 'done', for all other jobs, they are no longer needed in the jobqueue
1828 else {
1829 my $sql_statement = "UPDATE $job_queue_tn SET status='done' WHERE id=$jobdb_id";
1830 my $dbres = $job_db->update_dbentry($sql_statement);
1831 }
1834 # We don't want parallel processing
1835 last;
1836 }
1837 }
1838 }
1840 $watch_for_new_jobs_in_progress = 0;
1841 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1842 }
1843 }
1846 sub watch_for_new_messages {
1847 my ($kernel,$heap) = @_[KERNEL, HEAP];
1848 my @coll_user_msg; # collection list of outgoing messages
1850 # check messaging_db for new incoming messages with executable timestamp
1851 my $timestamp = &get_time();
1852 my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( (CAST(timestamp AS UNSIGNED))<$timestamp AND flag='n' AND direction='in' )";
1853 my $res = $messaging_db->exec_statement( $sql_statement );
1854 foreach my $hit (@{$res}) {
1856 # create outgoing messages
1857 my $message_to = @{$hit}[3];
1858 # translate message_to to plain login name
1859 my @message_to_l = split(/,/, $message_to);
1860 my %receiver_h;
1861 foreach my $receiver (@message_to_l) {
1862 if ($receiver =~ /^u_([\s\S]*)$/) {
1863 $receiver_h{$1} = 0;
1864 } elsif ($receiver =~ /^g_([\s\S]*)$/) {
1865 my $group_name = $1;
1866 # fetch all group members from ldap and add them to receiver hash
1867 my $ldap_handle = &get_ldap_handle();
1868 if (defined $ldap_handle) {
1869 my $mesg = $ldap_handle->search(
1870 base => $ldap_base,
1871 scope => 'sub',
1872 attrs => ['memberUid'],
1873 filter => "cn=$group_name",
1874 );
1875 if ($mesg->count) {
1876 my @entries = $mesg->entries;
1877 foreach my $entry (@entries) {
1878 my @receivers= $entry->get_value("memberUid");
1879 foreach my $receiver (@receivers) {
1880 $receiver_h{$receiver} = 0;
1881 }
1882 }
1883 }
1884 # translating errors ?
1885 if ($mesg->code) {
1886 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: $mesg->error", 1);
1887 }
1888 # ldap handle error ?
1889 } else {
1890 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: no ldap handle available", 1);
1891 }
1892 } else {
1893 my $sbjct = &encode_base64(@{$hit}[1]);
1894 my $msg = &encode_base64(@{$hit}[7]);
1895 &daemon_log("M WARNING: unknown receiver '$receiver' for a user-message '$sbjct - $msg'", 3);
1896 }
1897 }
1898 my @receiver_l = keys(%receiver_h);
1900 my $message_id = @{$hit}[0];
1902 #add each outgoing msg to messaging_db
1903 my $receiver;
1904 foreach $receiver (@receiver_l) {
1905 my $sql_statement = "INSERT INTO $messaging_tn (id, subject, message_from, message_to, flag, direction, delivery_time, message, timestamp) ".
1906 "VALUES ('".
1907 $message_id."', '". # id
1908 @{$hit}[1]."', '". # subject
1909 @{$hit}[2]."', '". # message_from
1910 $receiver."', '". # message_to
1911 "none"."', '". # flag
1912 "out"."', '". # direction
1913 @{$hit}[6]."', '". # delivery_time
1914 @{$hit}[7]."', '". # message
1915 $timestamp."'". # timestamp
1916 ")";
1917 &daemon_log("M DEBUG: $sql_statement", 1);
1918 my $res = $messaging_db->exec_statement($sql_statement);
1919 &daemon_log("M INFO: message '".@{$hit}[0]."' is prepared for delivery to receiver '$receiver'", 5);
1920 }
1922 # set incoming message to flag d=deliverd
1923 $sql_statement = "UPDATE $messaging_tn SET flag='p' WHERE id='$message_id'";
1924 &daemon_log("M DEBUG: $sql_statement", 7);
1925 $res = $messaging_db->update_dbentry($sql_statement);
1926 &daemon_log("M INFO: message '$message_id' is set to flag 'p' (processed)", 5);
1927 }
1929 $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1930 return;
1931 }
1933 sub watch_for_delivery_messages {
1934 my ($kernel, $heap) = @_[KERNEL, HEAP];
1936 # select outgoing messages
1937 my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( flag='p' AND direction='out' )";
1938 #&daemon_log("0 DEBUG: $sql", 7);
1939 my $res = $messaging_db->exec_statement( $sql_statement );
1941 # build out msg for each usr
1942 foreach my $hit (@{$res}) {
1943 my $receiver = @{$hit}[3];
1944 my $msg_id = @{$hit}[0];
1945 my $subject = @{$hit}[1];
1946 my $message = @{$hit}[7];
1948 # resolve usr -> host where usr is logged in
1949 my $sql = "SELECT * FROM $login_users_tn WHERE (user='$receiver')";
1950 #&daemon_log("0 DEBUG: $sql", 7);
1951 my $res = $login_users_db->exec_statement($sql);
1953 # receiver is logged in nowhere
1954 if (not ref(@$res[0]) eq "ARRAY") { next; }
1956 # receiver ist logged in at a client registered at local server
1957 my $send_succeed = 0;
1958 foreach my $hit (@$res) {
1959 my $receiver_host = @$hit[0];
1960 my $delivered2host = 0;
1961 &daemon_log("M DEBUG: user '$receiver' is logged in at host '$receiver_host'", 7);
1963 # Looking for host in know_clients_db
1964 my $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$receiver_host')";
1965 my $res = $known_clients_db->exec_statement($sql);
1967 # Host is known in known_clients_db
1968 if (ref(@$res[0]) eq "ARRAY") {
1969 my $receiver_key = @{@{$res}[0]}[2];
1970 my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
1971 my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data );
1972 my $error = &send_msg_to_target($out_msg, $receiver_host, $receiver_key, "usr_msg", 0);
1973 if ($error == 0 ) {
1974 $send_succeed++ ;
1975 $delivered2host++ ;
1976 &daemon_log("M DEBUG: send message for user '$receiver' to host '$receiver_host'", 7);
1977 } else {
1978 &daemon_log("M DEBUG: cannot send message for user '$receiver' to host '$receiver_host'", 7);
1979 }
1980 }
1982 # Message already send, do not need to do anything more, otherwise ...
1983 if ($delivered2host) { next;}
1985 # ...looking for host in foreign_clients_db
1986 $sql = "SELECT * FROM $foreign_clients_tn WHERE (hostname='$receiver_host')";
1987 $res = $foreign_clients_db->exec_statement($sql);
1989 # Host is known in foreign_clients_db
1990 if (ref(@$res[0]) eq "ARRAY") {
1991 my $registration_server = @{@{$res}[0]}[2];
1993 # Fetch encryption key for registration server
1994 my $sql = "SELECT * FROM $known_server_tn WHERE (hostname='$registration_server')";
1995 my $res = $known_server_db->exec_statement($sql);
1996 if (ref(@$res[0]) eq "ARRAY") {
1997 my $registration_server_key = @{@{$res}[0]}[3];
1998 my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
1999 my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data );
2000 my $error = &send_msg_to_target($out_msg, $registration_server, $registration_server_key, "usr_msg", 0);
2001 if ($error == 0 ) {
2002 $send_succeed++ ;
2003 $delivered2host++ ;
2004 &daemon_log("M DEBUG: send message for user '$receiver' to server '$registration_server'", 7);
2005 } else {
2006 &daemon_log("M ERROR: cannot send message for user '$receiver' to server '$registration_server'", 1);
2007 }
2009 } else {
2010 &daemon_log("M ERROR: host '$receiver_host' is reported to be ".
2011 "registrated at server '$registration_server', ".
2012 "but no data available in known_server_db ", 1);
2013 }
2014 }
2016 if (not $delivered2host) {
2017 &daemon_log("M ERROR: unable to send user message to host '$receiver_host'", 1);
2018 }
2019 }
2021 if ($send_succeed) {
2022 # set outgoing msg at db to deliverd
2023 my $sql = "UPDATE $messaging_tn SET flag='d' WHERE (id='$msg_id' AND direction='out' AND message_to='$receiver')";
2024 my $res = $messaging_db->exec_statement($sql);
2025 &daemon_log("M INFO: send message for user '$receiver' to logged in hosts", 5);
2026 } else {
2027 &daemon_log("M WARNING: failed to deliver message for user '$receiver'", 3);
2028 }
2029 }
2031 $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
2032 return;
2033 }
2036 sub watch_for_done_messages {
2037 my ($kernel,$heap) = @_[KERNEL, HEAP];
2039 my $sql = "SELECT * FROM $messaging_tn WHERE (flag='p' AND direction='in')";
2040 #&daemon_log("0 DEBUG: $sql", 7);
2041 my $res = $messaging_db->exec_statement($sql);
2043 foreach my $hit (@{$res}) {
2044 my $msg_id = @{$hit}[0];
2046 my $sql = "SELECT * FROM $messaging_tn WHERE (id='$msg_id' AND direction='out' AND (NOT flag='s'))";
2047 #&daemon_log("0 DEBUG: $sql", 7);
2048 my $res = $messaging_db->exec_statement($sql);
2050 # not all usr msgs have been seen till now
2051 if ( ref(@$res[0]) eq "ARRAY") { next; }
2053 $sql = "DELETE FROM $messaging_tn WHERE (id='$msg_id')";
2054 #&daemon_log("0 DEBUG: $sql", 7);
2055 $res = $messaging_db->exec_statement($sql);
2057 }
2059 $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
2060 return;
2061 }
2064 sub watch_for_old_known_clients {
2065 my ($kernel,$heap) = @_[KERNEL, HEAP];
2067 my $sql_statement = "SELECT * FROM $known_clients_tn";
2068 my $res = $known_clients_db->select_dbentry( $sql_statement );
2070 my $cur_time = int(&get_time());
2072 while ( my ($hit_num, $hit) = each %$res) {
2073 my $expired_timestamp = int($hit->{'timestamp'});
2074 $expired_timestamp =~ /(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)/;
2075 my $dt = DateTime->new( year => $1,
2076 month => $2,
2077 day => $3,
2078 hour => $4,
2079 minute => $5,
2080 second => $6,
2081 );
2083 $dt->add( seconds => 2 * int($hit->{'keylifetime'}) );
2084 $expired_timestamp = $dt->ymd('').$dt->hms('');
2085 if ($cur_time > $expired_timestamp) {
2086 my $hostname = $hit->{'hostname'};
2087 my $del_sql = "DELETE FROM $known_clients_tn WHERE hostname='$hostname'";
2088 my $del_res = $known_clients_db->exec_statement($del_sql);
2090 &main::daemon_log("0 INFO: timestamp '".$hit->{'timestamp'}."' of client '$hostname' is expired('$expired_timestamp'), client will be deleted from known_clients_db", 5);
2091 }
2093 }
2095 $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
2096 }
2099 sub watch_for_next_tasks {
2100 my ($kernel,$heap) = @_[KERNEL, HEAP];
2102 my $sql = "SELECT * FROM $incoming_tn";
2103 my $res = $incoming_db->select_dbentry($sql);
2105 while ( my ($hit_num, $hit) = each %$res) {
2106 my $headertag = $hit->{'headertag'};
2107 if ($headertag =~ /^answer_(\d+)/) {
2108 # do not start processing, this message is for a still running POE::Wheel
2109 next;
2110 }
2111 my $message_id = $hit->{'id'};
2112 my $session_id = $hit->{'sessionid'};
2113 &daemon_log("$session_id DEBUG: start processing for message with incoming id: '$message_id'", 7);
2114 $kernel->yield('next_task', $hit);
2116 my $sql = "DELETE FROM $incoming_tn WHERE id=$message_id";
2117 my $res = $incoming_db->exec_statement($sql);
2118 }
2120 $kernel->delay_set('watch_for_next_tasks', 1);
2121 }
2124 sub get_ldap_handle {
2125 my ($session_id) = @_;
2126 my $heap;
2127 my $ldap_handle;
2129 if (not defined $session_id ) { $session_id = 0 };
2130 if ($session_id =~ /[^0-9]*/) { $session_id = 0 };
2132 if ($session_id == 0) {
2133 daemon_log("$session_id DEBUG: get_ldap_handle invoked without a session_id, create a new ldap_handle", 7);
2134 $ldap_handle = Net::LDAP->new( $ldap_uri );
2135 if (defined $ldap_handle) {
2136 $ldap_handle->bind($ldap_admin_dn, password => $ldap_admin_password) or daemon_log("$session_id ERROR: Bind to LDAP $ldap_uri as $ldap_admin_dn failed!");
2137 } else {
2138 daemon_log("$session_id ERROR: creation of a new LDAP handle failed (ldap_uri '$ldap_uri')");
2139 }
2141 } else {
2142 my $session_reference = $global_kernel->ID_id_to_session($session_id);
2143 if( defined $session_reference ) {
2144 $heap = $session_reference->get_heap();
2145 }
2147 if (not defined $heap) {
2148 daemon_log("$session_id DEBUG: cannot get heap for session_id '$session_id'", 7);
2149 return;
2150 }
2152 # TODO: This "if" is nonsense, because it doesn't prove that the
2153 # used handle is still valid - or if we've to reconnect...
2154 #if (not exists $heap->{ldap_handle}) {
2155 $ldap_handle = Net::LDAP->new( $ldap_uri );
2156 $ldap_handle->bind($ldap_admin_dn, password => $ldap_admin_password) or daemon_log("$session_id ERROR: Bind to LDAP $ldap_uri as $ldap_admin_dn failed!");
2157 $heap->{ldap_handle} = $ldap_handle;
2158 #}
2159 }
2160 return $ldap_handle;
2161 }
2164 sub change_fai_state {
2165 my ($st, $targets, $session_id) = @_;
2166 $session_id = 0 if not defined $session_id;
2167 # Set FAI state to localboot
2168 my %mapActions= (
2169 reboot => '',
2170 update => 'softupdate',
2171 localboot => 'localboot',
2172 reinstall => 'install',
2173 rescan => '',
2174 wake => '',
2175 memcheck => 'memcheck',
2176 sysinfo => 'sysinfo',
2177 install => 'install',
2178 );
2180 # Return if this is unknown
2181 if (!exists $mapActions{ $st }){
2182 daemon_log("$session_id ERROR: unknown action '$st', can not translate ot FAIstate", 1);
2183 return;
2184 }
2186 my $state= $mapActions{ $st };
2188 my $ldap_handle = &get_ldap_handle($session_id);
2189 if( defined($ldap_handle) ) {
2191 # Build search filter for hosts
2192 my $search= "(&(objectClass=GOhard)";
2193 foreach (@{$targets}){
2194 $search.= "(macAddress=$_)";
2195 }
2196 $search.= ")";
2198 # If there's any host inside of the search string, procress them
2199 if (!($search =~ /macAddress/)){
2200 daemon_log("$session_id ERROR: no macAddress found in filter statement for LDAP search: '$search'", 1);
2201 return;
2202 }
2204 # Perform search for Unit Tag
2205 my $mesg = $ldap_handle->search(
2206 base => $ldap_base,
2207 scope => 'sub',
2208 attrs => ['dn', 'FAIstate', 'objectClass'],
2209 filter => "$search"
2210 );
2212 if ($mesg->count) {
2213 my @entries = $mesg->entries;
2214 if (0 == @entries) {
2215 daemon_log("$session_id ERROR: ldap search failed: ldap_base=$ldap_base, filter=$search", 1);
2216 }
2218 foreach my $entry (@entries) {
2219 # Only modify entry if it is not set to '$state'
2220 if ($entry->get_value("FAIstate") ne "$state"){
2221 daemon_log("$session_id INFO: Setting FAIstate to '$state' for ".$entry->dn, 5);
2222 my $result;
2223 my %tmp = map { $_ => 1 } $entry->get_value("objectClass");
2224 if (exists $tmp{'FAIobject'}){
2225 if ($state eq ''){
2226 $result= $ldap_handle->modify($entry->dn, changes => [ delete => [ FAIstate => [] ] ]);
2227 } else {
2228 $result= $ldap_handle->modify($entry->dn, changes => [ replace => [ FAIstate => $state ] ]);
2229 }
2230 } elsif ($state ne ''){
2231 $result= $ldap_handle->modify($entry->dn, changes => [ add => [ objectClass => 'FAIobject' ], add => [ FAIstate => $state ] ]);
2232 }
2234 # Errors?
2235 if ($result->code){
2236 daemon_log("$session_id Error: Setting FAIstate to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
2237 }
2238 } else {
2239 daemon_log("$session_id DEBUG FAIstate at host '".$entry->dn."' already at state '$st'", 7);
2240 }
2241 }
2242 } else {
2243 daemon_log("$session_id ERROR: LDAP search failed: ldap_base=$ldap_base, filter=$search", 1);
2244 }
2246 # if no ldap handle defined
2247 } else {
2248 daemon_log("$session_id ERROR: no LDAP handle defined for update FAIstate", 1);
2249 }
2251 return;
2252 }
2255 sub change_goto_state {
2256 my ($st, $targets, $session_id) = @_;
2257 $session_id = 0 if not defined $session_id;
2259 # Switch on or off?
2260 my $state= $st eq 'active' ? 'active': 'locked';
2262 my $ldap_handle = &get_ldap_handle($session_id);
2263 if( defined($ldap_handle) ) {
2265 # Build search filter for hosts
2266 my $search= "(&(objectClass=GOhard)";
2267 foreach (@{$targets}){
2268 $search.= "(macAddress=$_)";
2269 }
2270 $search.= ")";
2272 # If there's any host inside of the search string, procress them
2273 if (!($search =~ /macAddress/)){
2274 return;
2275 }
2277 # Perform search for Unit Tag
2278 my $mesg = $ldap_handle->search(
2279 base => $ldap_base,
2280 scope => 'sub',
2281 attrs => ['dn', 'gotoMode'],
2282 filter => "$search"
2283 );
2285 if ($mesg->count) {
2286 my @entries = $mesg->entries;
2287 foreach my $entry (@entries) {
2289 # Only modify entry if it is not set to '$state'
2290 if ($entry->get_value("gotoMode") ne $state){
2292 daemon_log("$session_id INFO: Setting gotoMode to '$state' for ".$entry->dn, 5);
2293 my $result;
2294 $result= $ldap_handle->modify($entry->dn, changes => [replace => [ gotoMode => $state ] ]);
2296 # Errors?
2297 if ($result->code){
2298 &daemon_log("$session_id Error: Setting gotoMode to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
2299 }
2301 }
2302 }
2303 } else {
2304 daemon_log("$session_id ERROR: LDAP search failed in function change_goto_state: ldap_base=$ldap_base, filter=$search", 1);
2305 }
2307 }
2308 }
2311 sub run_recreate_packages_db {
2312 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2313 my $session_id = $session->ID;
2314 &main::daemon_log("$session_id INFO: Recreating FAI Packages DB ('$fai_release_tn', '$fai_server_tn', '$packages_list_tn')", 5);
2315 $kernel->yield('create_fai_release_db', $fai_release_tn);
2316 $kernel->yield('create_fai_server_db', $fai_server_tn);
2317 return;
2318 }
2321 sub run_create_fai_server_db {
2322 my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
2323 my $session_id = $session->ID;
2324 my $task = POE::Wheel::Run->new(
2325 Program => sub { &create_fai_server_db($table_name,$kernel, undef, $session_id) },
2326 StdoutEvent => "session_run_result",
2327 StderrEvent => "session_run_debug",
2328 CloseEvent => "session_run_done",
2329 );
2331 $heap->{task}->{ $task->ID } = $task;
2332 return;
2333 }
2336 sub create_fai_server_db {
2337 my ($table_name, $kernel, $dont_create_packages_list, $session_id) = @_;
2338 my $result;
2340 if (not defined $session_id) { $session_id = 0; }
2341 my $ldap_handle = &get_ldap_handle();
2342 if(defined($ldap_handle)) {
2343 daemon_log("$session_id INFO: create_fai_server_db: start", 5);
2344 my $mesg= $ldap_handle->search(
2345 base => $ldap_base,
2346 scope => 'sub',
2347 attrs => ['FAIrepository', 'gosaUnitTag'],
2348 filter => "(&(FAIrepository=*)(objectClass=FAIrepositoryServer))",
2349 );
2350 if($mesg->{'resultCode'} == 0 &&
2351 $mesg->count != 0) {
2352 foreach my $entry (@{$mesg->{entries}}) {
2353 if($entry->exists('FAIrepository')) {
2354 # Add an entry for each Repository configured for server
2355 foreach my $repo(@{$entry->get_value('FAIrepository', asref => 1)}) {
2356 my($tmp_url,$tmp_server,$tmp_release,$tmp_sections) = split(/\|/, $repo);
2357 my $tmp_tag= $entry->get_value('gosaUnitTag') || "";
2358 $result= $fai_server_db->add_dbentry( {
2359 table => $table_name,
2360 primkey => ['server', 'fai_release', 'tag'],
2361 server => $tmp_url,
2362 fai_release => $tmp_release,
2363 sections => $tmp_sections,
2364 tag => (length($tmp_tag)>0)?$tmp_tag:"",
2365 } );
2366 }
2367 }
2368 }
2369 }
2370 daemon_log("$session_id INFO: create_fai_server_db: finished", 5);
2372 # TODO: Find a way to post the 'create_packages_list_db' event
2373 if(not defined($dont_create_packages_list)) {
2374 &create_packages_list_db(undef, undef, $session_id);
2375 }
2376 }
2378 $ldap_handle->disconnect;
2379 return $result;
2380 }
2383 sub run_create_fai_release_db {
2384 my ($session, $heap, $table_name) = @_[SESSION, HEAP, ARG0];
2385 my $session_id = $session->ID;
2386 my $task = POE::Wheel::Run->new(
2387 Program => sub { &create_fai_release_db($table_name, $session_id) },
2388 StdoutEvent => "session_run_result",
2389 StderrEvent => "session_run_debug",
2390 CloseEvent => "session_run_done",
2391 );
2393 $heap->{task}->{ $task->ID } = $task;
2394 return;
2395 }
2398 sub create_fai_release_db {
2399 my ($table_name, $session_id) = @_;
2400 my $result;
2402 # used for logging
2403 if (not defined $session_id) { $session_id = 0; }
2405 my $ldap_handle = &get_ldap_handle();
2406 if(defined($ldap_handle)) {
2407 daemon_log("$session_id INFO: create_fai_release_db: start",5);
2408 my $mesg= $ldap_handle->search(
2409 base => $ldap_base,
2410 scope => 'sub',
2411 attrs => [],
2412 filter => "(&(objectClass=organizationalUnit)(ou=fai))",
2413 );
2414 if(($mesg->code == 0) && ($mesg->count != 0))
2415 {
2416 daemon_log("$session_id DEBUG: create_fai_release_db: count " . $mesg->count,8);
2418 # Walk through all possible FAI container ou's
2419 my @sql_list;
2420 my $timestamp= &get_time();
2421 foreach my $ou (@{$mesg->{entries}}) {
2422 my $tmp_classes= resolve_fai_classes($ou->dn, $ldap_handle, $session_id);
2423 if(defined($tmp_classes) && ref($tmp_classes) eq 'HASH') {
2424 my @tmp_array=get_fai_release_entries($tmp_classes);
2425 if(@tmp_array) {
2426 foreach my $entry (@tmp_array) {
2427 if(defined($entry) && ref($entry) eq 'HASH') {
2428 my $sql=
2429 "INSERT INTO $table_name "
2430 ."(timestamp, fai_release, class, type, state) VALUES ("
2431 .$timestamp.","
2432 ."'".$entry->{'release'}."',"
2433 ."'".$entry->{'class'}."',"
2434 ."'".$entry->{'type'}."',"
2435 ."'".$entry->{'state'}."')";
2436 push @sql_list, $sql;
2437 }
2438 }
2439 }
2440 }
2441 }
2443 daemon_log("$session_id DEBUG: create_fai_release_db: Inserting ".scalar @sql_list." entries to DB",8);
2444 if(@sql_list) {
2445 unshift @sql_list, "VACUUM";
2446 unshift @sql_list, "DELETE FROM $table_name";
2447 $fai_release_db->exec_statementlist(\@sql_list);
2448 }
2449 daemon_log("$session_id DEBUG: create_fai_release_db: Done with inserting",7);
2450 } else {
2451 daemon_log("$session_id INFO: create_fai_release_db: error: " . $mesg->code, 5);
2452 }
2453 daemon_log("$session_id INFO: create_fai_release_db: finished",5);
2454 }
2455 $ldap_handle->disconnect;
2456 return $result;
2457 }
2459 sub get_fai_types {
2460 my $tmp_classes = shift || return undef;
2461 my @result;
2463 foreach my $type(keys %{$tmp_classes}) {
2464 if(defined($tmp_classes->{$type}[0]) && (!($tmp_classes->{$type}[0] =~ /^.*?removed.*?$/))) {
2465 my $entry = {
2466 type => $type,
2467 state => $tmp_classes->{$type}[0],
2468 };
2469 push @result, $entry;
2470 }
2471 }
2473 return @result;
2474 }
2476 sub get_fai_state {
2477 my $result = "";
2478 my $tmp_classes = shift || return $result;
2480 foreach my $type(keys %{$tmp_classes}) {
2481 if(defined($tmp_classes->{$type}[0])) {
2482 $result = $tmp_classes->{$type}[0];
2484 # State is equal for all types in class
2485 last;
2486 }
2487 }
2489 return $result;
2490 }
2492 sub resolve_fai_classes {
2493 my ($fai_base, $ldap_handle, $session_id) = @_;
2494 if (not defined $session_id) { $session_id = 0; }
2495 my $result;
2496 my @possible_fai_classes= ("FAIscript", "FAIhook", "FAIpartitionTable", "FAItemplate", "FAIvariable", "FAIprofile", "FAIpackageList");
2497 my $fai_filter= "(|(&(objectClass=FAIclass)(|(objectClass=".join(")(objectClass=", @possible_fai_classes).")))(objectClass=FAIbranch))";
2498 my $fai_classes;
2500 daemon_log("$session_id DEBUG: Searching for FAI entries in base $fai_base",7);
2501 my $mesg= $ldap_handle->search(
2502 base => $fai_base,
2503 scope => 'sub',
2504 attrs => ['cn','objectClass','FAIstate'],
2505 filter => $fai_filter,
2506 );
2507 daemon_log("$session_id DEBUG: Found ".$mesg->count()." FAI entries",7);
2509 if($mesg->{'resultCode'} == 0 &&
2510 $mesg->count != 0) {
2511 foreach my $entry (@{$mesg->{entries}}) {
2512 if($entry->exists('cn')) {
2513 my $tmp_dn= $entry->dn();
2514 $tmp_dn= substr( $tmp_dn, 0, length($tmp_dn)
2515 - length($fai_base) - 1 );
2517 # Skip classname and ou dn parts for class
2518 my $tmp_release = ($1) if $tmp_dn =~ /^[^,]+,[^,]+,(.*?)$/;
2520 # Skip classes without releases
2521 if((!defined($tmp_release)) || length($tmp_release)==0) {
2522 next;
2523 }
2525 my $tmp_cn= $entry->get_value('cn');
2526 my $tmp_state= $entry->get_value('FAIstate');
2528 my $tmp_type;
2529 # Get FAI type
2530 for my $oclass(@{$entry->get_value('objectClass', asref => 1)}) {
2531 if(grep $_ eq $oclass, @possible_fai_classes) {
2532 $tmp_type= $oclass;
2533 last;
2534 }
2535 }
2537 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2538 # A Subrelease
2539 my @sub_releases = split(/,/, $tmp_release);
2541 # Walk through subreleases and build hash tree
2542 my $hash;
2543 while(my $tmp_sub_release = pop @sub_releases) {
2544 $hash .= "\{'$tmp_sub_release'\}->";
2545 }
2546 eval('push @{$fai_classes->'.$hash.'{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";');
2547 } else {
2548 # A branch, no subrelease
2549 push @{$fai_classes->{$tmp_release}->{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";
2550 }
2551 } elsif (!$entry->exists('cn')) {
2552 my $tmp_dn= $entry->dn();
2553 $tmp_dn= substr( $tmp_dn, 0, length($tmp_dn)
2554 - length($fai_base) - 1 );
2555 my $tmp_release = ($1) if $tmp_dn =~ /^(.*?)$/;
2557 # Skip classes without releases
2558 if((!defined($tmp_release)) || length($tmp_release)==0) {
2559 next;
2560 }
2562 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2563 # A Subrelease
2564 my @sub_releases= split(/,/, $tmp_release);
2566 # Walk through subreleases and build hash tree
2567 my $hash;
2568 while(my $tmp_sub_release = pop @sub_releases) {
2569 $hash .= "\{'$tmp_sub_release'\}->";
2570 }
2571 # Remove the last two characters
2572 chop($hash);
2573 chop($hash);
2575 eval('$fai_classes->'.$hash.'= {}');
2576 } else {
2577 # A branch, no subrelease
2578 if(!exists($fai_classes->{$tmp_release})) {
2579 $fai_classes->{$tmp_release} = {};
2580 }
2581 }
2582 }
2583 }
2585 # The hash is complete, now we can honor the copy-on-write based missing entries
2586 foreach my $release (keys %$fai_classes) {
2587 $result->{$release}= deep_copy(apply_fai_inheritance($fai_classes->{$release}));
2588 }
2589 }
2590 return $result;
2591 }
2593 sub apply_fai_inheritance {
2594 my $fai_classes = shift || return {};
2595 my $tmp_classes;
2597 # Get the classes from the branch
2598 foreach my $class (keys %{$fai_classes}) {
2599 # Skip subreleases
2600 if($class =~ /^ou=.*$/) {
2601 next;
2602 } else {
2603 $tmp_classes->{$class}= deep_copy($fai_classes->{$class});
2604 }
2605 }
2607 # Apply to each subrelease
2608 foreach my $subrelease (keys %{$fai_classes}) {
2609 if($subrelease =~ /ou=/) {
2610 foreach my $tmp_class (keys %{$tmp_classes}) {
2611 if(!exists($fai_classes->{$subrelease}->{$tmp_class})) {
2612 $fai_classes->{$subrelease}->{$tmp_class} =
2613 deep_copy($tmp_classes->{$tmp_class});
2614 } else {
2615 foreach my $type (keys %{$tmp_classes->{$tmp_class}}) {
2616 if(!exists($fai_classes->{$subrelease}->{$tmp_class}->{$type})) {
2617 $fai_classes->{$subrelease}->{$tmp_class}->{$type}=
2618 deep_copy($tmp_classes->{$tmp_class}->{$type});
2619 }
2620 }
2621 }
2622 }
2623 }
2624 }
2626 # Find subreleases in deeper levels
2627 foreach my $subrelease (keys %{$fai_classes}) {
2628 if($subrelease =~ /ou=/) {
2629 foreach my $subsubrelease (keys %{$fai_classes->{$subrelease}}) {
2630 if($subsubrelease =~ /ou=/) {
2631 apply_fai_inheritance($fai_classes->{$subrelease});
2632 }
2633 }
2634 }
2635 }
2637 return $fai_classes;
2638 }
2640 sub get_fai_release_entries {
2641 my $tmp_classes = shift || return;
2642 my $parent = shift || "";
2643 my @result = shift || ();
2645 foreach my $entry (keys %{$tmp_classes}) {
2646 if(defined($entry)) {
2647 if($entry =~ /^ou=.*$/) {
2648 my $release_name = $entry;
2649 $release_name =~ s/ou=//g;
2650 if(length($parent)>0) {
2651 $release_name = $parent."/".$release_name;
2652 }
2653 my @bufentries = get_fai_release_entries($tmp_classes->{$entry}, $release_name, @result);
2654 foreach my $bufentry(@bufentries) {
2655 push @result, $bufentry;
2656 }
2657 } else {
2658 my @types = get_fai_types($tmp_classes->{$entry});
2659 foreach my $type (@types) {
2660 push @result,
2661 {
2662 'class' => $entry,
2663 'type' => $type->{'type'},
2664 'release' => $parent,
2665 'state' => $type->{'state'},
2666 };
2667 }
2668 }
2669 }
2670 }
2672 return @result;
2673 }
2675 sub deep_copy {
2676 my $this = shift;
2677 if (not ref $this) {
2678 $this;
2679 } elsif (ref $this eq "ARRAY") {
2680 [map deep_copy($_), @$this];
2681 } elsif (ref $this eq "HASH") {
2682 +{map { $_ => deep_copy($this->{$_}) } keys %$this};
2683 } else { die "what type is $_?" }
2684 }
2687 sub session_run_result {
2688 my ($kernel, $heap, $client_answer) = @_[KERNEL, HEAP, ARG0];
2689 $kernel->sig(CHLD => "child_reap");
2690 }
2692 sub session_run_debug {
2693 my $result = $_[ARG0];
2694 print STDERR "$result\n";
2695 }
2697 sub session_run_done {
2698 my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
2699 delete $heap->{task}->{$task_id};
2700 }
2703 sub create_sources_list {
2704 my $session_id = shift;
2705 my $ldap_handle = &main::get_ldap_handle;
2706 my $result="/tmp/gosa_si_tmp_sources_list";
2708 # Remove old file
2709 if(stat($result)) {
2710 unlink($result);
2711 &main::daemon_log("$session_id DEBUG: remove an old version of '$result'", 7);
2712 }
2714 my $fh;
2715 open($fh, ">$result");
2716 if (not defined $fh) {
2717 &main::daemon_log("$session_id DEBUG: cannot open '$result' for writing", 7);
2718 return undef;
2719 }
2720 if(defined($main::ldap_server_dn) and length($main::ldap_server_dn) > 0) {
2721 my $mesg=$ldap_handle->search(
2722 base => $main::ldap_server_dn,
2723 scope => 'base',
2724 attrs => 'FAIrepository',
2725 filter => 'objectClass=FAIrepositoryServer'
2726 );
2727 if($mesg->count) {
2728 foreach my $entry(@{$mesg->{'entries'}}) {
2729 foreach my $value(@{$entry->get_value('FAIrepository', asref => 1)}) {
2730 my ($server, $tag, $release, $sections)= split /\|/, $value;
2731 my $line = "deb $server $release";
2732 $sections =~ s/,/ /g;
2733 $line.= " $sections";
2734 print $fh $line."\n";
2735 }
2736 }
2737 }
2738 } else {
2739 if (defined $main::ldap_server_dn){
2740 &main::daemon_log("$session_id ERROR: something wrong with ldap_server_dn '$main::ldap_server_dn', abort create_sources_list", 1);
2741 } else {
2742 &main::daemon_log("$session_id ERROR: no ldap_server_dn found, abort create_sources_list", 1);
2743 }
2744 }
2745 close($fh);
2747 return $result;
2748 }
2751 sub run_create_packages_list_db {
2752 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2753 my $session_id = $session->ID;
2755 my $task = POE::Wheel::Run->new(
2756 Priority => +20,
2757 Program => sub {&create_packages_list_db(undef, undef, $session_id)},
2758 StdoutEvent => "session_run_result",
2759 StderrEvent => "session_run_debug",
2760 CloseEvent => "session_run_done",
2761 );
2762 $heap->{task}->{ $task->ID } = $task;
2763 }
2766 sub create_packages_list_db {
2767 my ($ldap_handle, $sources_file, $session_id) = @_;
2769 # it should not be possible to trigger a recreation of packages_list_db
2770 # while packages_list_db is under construction, so set flag packages_list_under_construction
2771 # which is tested befor recreation can be started
2772 if (-r $packages_list_under_construction) {
2773 daemon_log("$session_id WARNING: packages_list_db is right now under construction, please wait until this process is finished", 3);
2774 return;
2775 } else {
2776 daemon_log("$session_id INFO: create_packages_list_db: start", 5);
2777 # set packages_list_under_construction to true
2778 system("touch $packages_list_under_construction");
2779 @packages_list_statements=();
2780 }
2782 if (not defined $session_id) { $session_id = 0; }
2783 if (not defined $ldap_handle) {
2784 $ldap_handle= &get_ldap_handle();
2786 if (not defined $ldap_handle) {
2787 daemon_log("$session_id ERROR: no ldap_handle available to create_packages_list_db", 1);
2788 unlink($packages_list_under_construction);
2789 return;
2790 }
2791 }
2792 if (not defined $sources_file) {
2793 &main::daemon_log("$session_id INFO: no sources_file given for creating packages list so trigger creation of it", 5);
2794 $sources_file = &create_sources_list($session_id);
2795 }
2797 if (not defined $sources_file) {
2798 &main::daemon_log("$session_id ERROR: no sources_file given under '$sources_file', skip create_packages_list_db", 1);
2799 unlink($packages_list_under_construction);
2800 return;
2801 }
2803 my $line;
2805 open(CONFIG, "<$sources_file") or do {
2806 daemon_log( "$session_id ERROR: create_packages_list_db: Failed to open '$sources_file'", 1);
2807 unlink($packages_list_under_construction);
2808 return;
2809 };
2811 # Read lines
2812 while ($line = <CONFIG>){
2813 # Unify
2814 chop($line);
2815 $line =~ s/^\s+//;
2816 $line =~ s/^\s+/ /;
2818 # Strip comments
2819 $line =~ s/#.*$//g;
2821 # Skip empty lines
2822 if ($line =~ /^\s*$/){
2823 next;
2824 }
2826 # Interpret deb line
2827 if ($line =~ /^deb [^\s]+\s[^\s]+\s[^\s]+/){
2828 my( $baseurl, $dist, $sections ) = ($line =~ /^deb\s([^\s]+)\s+([^\s]+)\s+(.*)$/);
2829 my $section;
2830 foreach $section (split(' ', $sections)){
2831 &parse_package_info( $baseurl, $dist, $section, $session_id );
2832 }
2833 }
2834 }
2836 close (CONFIG);
2838 if(keys(%repo_dirs)) {
2839 find(\&cleanup_and_extract, keys( %repo_dirs ));
2840 &main::strip_packages_list_statements();
2841 $packages_list_db->exec_statementlist(\@packages_list_statements);
2842 }
2843 unlink($packages_list_under_construction);
2844 daemon_log("$session_id INFO: create_packages_list_db: finished", 5);
2845 return;
2846 }
2848 # This function should do some intensive task to minimize the db-traffic
2849 sub strip_packages_list_statements {
2850 my @existing_entries= @{$packages_list_db->exec_statement("SELECT * FROM $main::packages_list_tn")};
2851 my @new_statement_list=();
2852 my $hash;
2853 my $insert_hash;
2854 my $update_hash;
2855 my $delete_hash;
2856 my $known_packages_hash;
2857 my $local_timestamp=get_time();
2859 foreach my $existing_entry (@existing_entries) {
2860 $hash->{@{$existing_entry}[0]}->{@{$existing_entry}[1]}->{@{$existing_entry}[2]}= $existing_entry;
2861 }
2863 foreach my $statement (@packages_list_statements) {
2864 if($statement =~ /^INSERT/i) {
2865 # Assign the values from the insert statement
2866 my ($distribution,$package,$version,$section,$description,$template,$timestamp) = ($1,$2,$3,$4,$5,$6,$7) if $statement =~
2867 /^INSERT\s+?INTO\s+?$main::packages_list_tn\s+?VALUES\s*?\('(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)'\s*?\)$/si;
2868 if(exists($hash->{$distribution}->{$package}->{$version})) {
2869 # If section or description has changed, update the DB
2870 if(
2871 (! (@{$hash->{$distribution}->{$package}->{$version}}[3] eq $section)) or
2872 (! (@{$hash->{$distribution}->{$package}->{$version}}[4] eq $description))
2873 ) {
2874 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,undef);
2875 } else {
2876 # package is already present in database. cache this knowledge for later use
2877 @{$known_packages_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2878 }
2879 } else {
2880 # Insert a non-existing entry to db
2881 @{$insert_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2882 }
2883 } elsif ($statement =~ /^UPDATE/i) {
2884 my ($template,$package,$version) = ($1,$2,$3) if $statement =~
2885 /^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;
2886 foreach my $distribution (keys %{$hash}) {
2887 if(exists($insert_hash->{$distribution}->{$package}->{$version})) {
2888 # update the insertion hash to execute only one query per package (insert instead insert+update)
2889 @{$insert_hash->{$distribution}->{$package}->{$version}}[5]= $template;
2890 } elsif(exists($hash->{$distribution}->{$package}->{$version})) {
2891 if( ! (@{$hash->{$distribution}->{$package}->{$version}}[5] eq $template)) {
2892 my $section;
2893 my $description;
2894 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) and
2895 length(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) > 0 ) {
2896 $section= @{$update_hash->{$distribution}->{$package}->{$version}}[3];
2897 }
2898 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2899 $description= @{$update_hash->{$distribution}->{$package}->{$version}}[4];
2900 }
2901 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2902 }
2903 }
2904 }
2905 }
2906 }
2908 # Check for orphaned entries
2909 foreach my $existing_entry (@existing_entries) {
2910 my $distribution= @{$existing_entry}[0];
2911 my $package= @{$existing_entry}[1];
2912 my $version= @{$existing_entry}[2];
2913 my $section= @{$existing_entry}[3];
2915 if(
2916 exists($insert_hash->{$distribution}->{$package}->{$version}) ||
2917 exists($update_hash->{$distribution}->{$package}->{$version}) ||
2918 exists($known_packages_hash->{$distribution}->{$package}->{$version})
2919 ) {
2920 next;
2921 } else {
2922 # Insert entry to delete hash
2923 @{$delete_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section);
2924 }
2925 }
2927 # unroll the insert hash
2928 foreach my $distribution (keys %{$insert_hash}) {
2929 foreach my $package (keys %{$insert_hash->{$distribution}}) {
2930 foreach my $version (keys %{$insert_hash->{$distribution}->{$package}}) {
2931 push @new_statement_list, "INSERT INTO $main::packages_list_tn VALUES ('$distribution','$package','$version',"
2932 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[3]',"
2933 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[4]',"
2934 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[5]',"
2935 ."'$local_timestamp')";
2936 }
2937 }
2938 }
2940 # unroll the update hash
2941 foreach my $distribution (keys %{$update_hash}) {
2942 foreach my $package (keys %{$update_hash->{$distribution}}) {
2943 foreach my $version (keys %{$update_hash->{$distribution}->{$package}}) {
2944 my $set = "";
2945 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3])) {
2946 $set .= "section = '@{$update_hash->{$distribution}->{$package}->{$version}}[3]', ";
2947 }
2948 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2949 $set .= "description = '@{$update_hash->{$distribution}->{$package}->{$version}}[4]', ";
2950 }
2951 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[5])) {
2952 $set .= "template = '@{$update_hash->{$distribution}->{$package}->{$version}}[5]', ";
2953 }
2954 if(defined($set) and length($set) > 0) {
2955 $set .= "timestamp = '$local_timestamp'";
2956 } else {
2957 next;
2958 }
2959 push @new_statement_list,
2960 "UPDATE $main::packages_list_tn SET $set WHERE"
2961 ." distribution = '$distribution'"
2962 ." AND package = '$package'"
2963 ." AND version = '$version'";
2964 }
2965 }
2966 }
2968 # unroll the delete hash
2969 foreach my $distribution (keys %{$delete_hash}) {
2970 foreach my $package (keys %{$delete_hash->{$distribution}}) {
2971 foreach my $version (keys %{$delete_hash->{$distribution}->{$package}}) {
2972 my $section = @{$delete_hash->{$distribution}->{$package}->{$version}}[3];
2973 push @new_statement_list, "DELETE FROM $main::packages_list_tn WHERE distribution='$distribution' AND package='$package' AND version='$version' AND section='$section'";
2974 }
2975 }
2976 }
2978 unshift(@new_statement_list, "VACUUM");
2980 @packages_list_statements = @new_statement_list;
2981 }
2984 sub parse_package_info {
2985 my ($baseurl, $dist, $section, $session_id)= @_;
2986 my ($package);
2987 if (not defined $session_id) { $session_id = 0; }
2988 my ($path) = ($baseurl =~ m%://[^/]*(.*)$%);
2989 $repo_dirs{ "${repo_path}/pool" } = 1;
2991 foreach $package ("Packages.gz"){
2992 daemon_log("$session_id DEBUG: create_packages_list: fetch $baseurl, $dist, $section", 7);
2993 get_package( "$baseurl/dists/$dist/$section/binary-$arch/$package", "$outdir/$dist/$section", $session_id );
2994 parse_package( "$outdir/$dist/$section", $dist, $path, $session_id );
2995 }
2997 }
3000 sub get_package {
3001 my ($url, $dest, $session_id)= @_;
3002 if (not defined $session_id) { $session_id = 0; }
3004 my $tpath = dirname($dest);
3005 -d "$tpath" || mkpath "$tpath";
3007 # This is ugly, but I've no time to take a look at "how it works in perl"
3008 if(0 == system("wget '$url' -O '$dest' 2>/dev/null") ) {
3009 system("gunzip -cd '$dest' > '$dest.in'");
3010 daemon_log("$session_id DEBUG: run command: gunzip -cd '$dest' > '$dest.in'", 5);
3011 unlink($dest);
3012 daemon_log("$session_id DEBUG: delete file '$dest'", 5);
3013 } else {
3014 daemon_log("$session_id ERROR: create_packages_list_db: get_packages: fetching '$url' into '$dest' failed!", 1);
3015 }
3016 return 0;
3017 }
3020 sub parse_package {
3021 my ($path, $dist, $srv_path, $session_id)= @_;
3022 if (not defined $session_id) { $session_id = 0;}
3023 my ($package, $version, $section, $description);
3024 my $PACKAGES;
3025 my $timestamp = &get_time();
3027 if(not stat("$path.in")) {
3028 daemon_log("$session_id ERROR: create_packages_list: parse_package: file '$path.in' is not readable",1);
3029 return;
3030 }
3032 open($PACKAGES, "<$path.in");
3033 if(not defined($PACKAGES)) {
3034 daemon_log("$session_id ERROR: create_packages_list_db: parse_package: cannot open '$path.in'",1);
3035 return;
3036 }
3038 # Read lines
3039 while (<$PACKAGES>){
3040 my $line = $_;
3041 # Unify
3042 chop($line);
3044 # Use empty lines as a trigger
3045 if ($line =~ /^\s*$/){
3046 my $sql = "INSERT INTO packages_list VALUES ('$dist', '$package', '$version', '$section', '$description', '', '$timestamp')";
3047 push(@packages_list_statements, $sql);
3048 $package = "none";
3049 $version = "none";
3050 $section = "none";
3051 $description = "none";
3052 next;
3053 }
3055 # Trigger for package name
3056 if ($line =~ /^Package:\s/){
3057 ($package)= ($line =~ /^Package: (.*)$/);
3058 next;
3059 }
3061 # Trigger for version
3062 if ($line =~ /^Version:\s/){
3063 ($version)= ($line =~ /^Version: (.*)$/);
3064 next;
3065 }
3067 # Trigger for description
3068 if ($line =~ /^Description:\s/){
3069 ($description)= &encode_base64(($line =~ /^Description: (.*)$/));
3070 next;
3071 }
3073 # Trigger for section
3074 if ($line =~ /^Section:\s/){
3075 ($section)= ($line =~ /^Section: (.*)$/);
3076 next;
3077 }
3079 # Trigger for filename
3080 if ($line =~ /^Filename:\s/){
3081 my ($filename) = ($line =~ /^Filename: (.*)$/);
3082 store_fileinfo( $package, $filename, $dist, $srv_path, $version, $repo_path );
3083 next;
3084 }
3085 }
3087 close( $PACKAGES );
3088 unlink( "$path.in" );
3089 }
3092 sub store_fileinfo {
3093 my( $package, $file, $dist, $path, $vers, $srvdir) = @_;
3095 my %fileinfo = (
3096 'package' => $package,
3097 'dist' => $dist,
3098 'version' => $vers,
3099 );
3101 $repo_files{ "${srvdir}/$file" } = \%fileinfo;
3102 }
3105 sub cleanup_and_extract {
3106 my $fileinfo = $repo_files{ $File::Find::name };
3108 if( defined $fileinfo ) {
3109 my $dir = "$outdir/$fileinfo->{ 'dist' }/debconf.d";
3110 my $sql;
3111 my $package = $fileinfo->{ 'package' };
3112 my $newver = $fileinfo->{ 'version' };
3114 mkpath($dir);
3115 system( "dpkg -e '$File::Find::name' '$dir/DEBIAN'" );
3117 if( -f "$dir/DEBIAN/templates" ) {
3119 daemon_log("0 DEBUG: Found debconf templates in '$package' - $newver", 7);
3121 my $tmpl= ""; {
3122 local $/=undef;
3123 open FILE, "$dir/DEBIAN/templates";
3124 $tmpl = &encode_base64(<FILE>);
3125 close FILE;
3126 }
3127 rmtree("$dir/DEBIAN/templates");
3129 $sql= "update $main::packages_list_tn set template = '$tmpl' where package = '$package' and version = '$newver';";
3130 push @packages_list_statements, $sql;
3131 }
3132 }
3134 return;
3135 }
3138 sub register_at_foreign_servers {
3139 my ($kernel) = $_[KERNEL];
3141 # hole alle bekannten server aus known_server_db
3142 my $server_sql = "SELECT * FROM $known_server_tn";
3143 my $server_res = $known_server_db->exec_statement($server_sql);
3145 # no entries in known_server_db
3146 if (not ref(@$server_res[0]) eq "ARRAY") {
3147 # TODO
3148 }
3150 # detect already connected clients
3151 my $client_sql = "SELECT * FROM $known_clients_tn";
3152 my $client_res = $known_clients_db->exec_statement($client_sql);
3154 # send my server details to all other gosa-si-server within the network
3155 foreach my $hit (@$server_res) {
3156 my $hostname = @$hit[0];
3157 my $hostkey = &create_passwd;
3159 # add already connected clients to registration message
3160 my $myhash = &create_xml_hash('new_server', $server_address, $hostname);
3161 &add_content2xml_hash($myhash, 'key', $hostkey);
3162 map(&add_content2xml_hash($myhash, 'client', @{$_}[0].",".@{$_}[4]), @$client_res);
3164 # add locally loaded gosa-si modules to registration message
3165 my $loaded_modules = {};
3166 while (my ($package, $pck_info) = each %$known_modules) {
3167 next if ((!defined(@$pck_info[2])) || (!(ref (@$pck_info[2]) eq 'HASH')));
3168 foreach my $act_module (keys(%{@$pck_info[2]})) {
3169 $loaded_modules->{$act_module} = "";
3170 }
3171 }
3173 map(&add_content2xml_hash($myhash, "loaded_modules", $_), keys(%$loaded_modules));
3175 # add macaddress to registration message
3176 my ($host_ip, $host_port) = split(/:/, $hostname);
3177 my $local_ip = &get_local_ip_for_remote_ip($host_ip);
3178 my $network_interface= &get_interface_for_ip($local_ip);
3179 my $host_mac = &get_mac_for_interface($network_interface);
3180 &add_content2xml_hash($myhash, 'macaddress', $host_mac);
3182 # build registration message and send it
3183 my $foreign_server_msg = &create_xml_string($myhash);
3184 my $error = &send_msg_to_target($foreign_server_msg, $hostname, $ServerPackages_key, "new_server", 0);
3185 }
3187 $kernel->delay_set("register_at_foreign_servers", $foreign_servers_register_delay);
3188 return;
3189 }
3192 #==== MAIN = main ==============================================================
3193 # parse commandline options
3194 Getopt::Long::Configure( "bundling" );
3195 GetOptions("h|help" => \&usage,
3196 "c|config=s" => \$cfg_file,
3197 "f|foreground" => \$foreground,
3198 "v|verbose+" => \$verbose,
3199 "no-arp+" => \$no_arp,
3200 );
3202 # read and set config parameters
3203 &check_cmdline_param ;
3204 &read_configfile($cfg_file, %cfg_defaults);
3205 &check_pid;
3207 $SIG{CHLD} = 'IGNORE';
3209 # forward error messages to logfile
3210 if( ! $foreground ) {
3211 open( STDIN, '+>/dev/null' );
3212 open( STDOUT, '+>&STDIN' );
3213 open( STDERR, '+>&STDIN' );
3214 }
3216 # Just fork, if we are not in foreground mode
3217 if( ! $foreground ) {
3218 chdir '/' or die "Can't chdir to /: $!";
3219 $pid = fork;
3220 setsid or die "Can't start a new session: $!";
3221 umask 0;
3222 } else {
3223 $pid = $$;
3224 }
3226 # Do something useful - put our PID into the pid_file
3227 if( 0 != $pid ) {
3228 open( LOCK_FILE, ">$pid_file" );
3229 print LOCK_FILE "$pid\n";
3230 close( LOCK_FILE );
3231 if( !$foreground ) {
3232 exit( 0 )
3233 };
3234 }
3236 # parse head url and revision from svn
3237 my $server_status_hash = { 'developmental'=>'revision', 'stable'=>'release'};
3238 $server_version =~ /^\$HeadURL: (\S+) \$:\$Rev: (\d+) \$$/;
3239 $server_headURL = defined $1 ? $1 : 'unknown' ;
3240 $server_revision = defined $2 ? $2 : 'unknown' ;
3241 if ($server_headURL =~ /\/tag\// ||
3242 $server_headURL =~ /\/branches\// ) {
3243 $server_status = "stable";
3244 } else {
3245 $server_status = "developmental" ;
3246 }
3247 # Prepare log file and set permissions
3248 $root_uid = getpwnam('root');
3249 $adm_gid = getgrnam('adm');
3250 open(FH, ">>$log_file");
3251 close FH;
3252 chmod(0440, $log_file);
3253 chown($root_uid, $adm_gid, $log_file);
3254 chown($root_uid, $adm_gid, "/var/lib/gosa-si");
3256 daemon_log(" ", 1);
3257 daemon_log("$0 started!", 1);
3258 daemon_log("status: $server_status", 1);
3259 daemon_log($server_status_hash->{$server_status}.": $server_revision", 1);
3261 {
3262 no strict "refs";
3264 if ($db_module eq "DBmysql") {
3265 # connect to incoming_db
3266 $incoming_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3268 # connect to gosa-si job queue
3269 $job_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3271 # connect to known_clients_db
3272 $known_clients_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3274 # connect to foreign_clients_db
3275 $foreign_clients_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3277 # connect to known_server_db
3278 $known_server_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3280 # connect to login_usr_db
3281 $login_users_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3283 # connect to fai_server_db
3284 $fai_server_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3286 # connect to fai_release_db
3287 $fai_release_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3289 # connect to packages_list_db
3290 $packages_list_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3292 # connect to messaging_db
3293 $messaging_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3295 } elsif ($db_module eq "DBsqlite") {
3296 # connect to incoming_db
3297 unlink($incoming_file_name);
3298 $incoming_db = GOSA::DBsqlite->new($incoming_file_name);
3300 # connect to gosa-si job queue
3301 unlink($job_queue_file_name); ## just for debugging
3302 $job_db = GOSA::DBsqlite->new($job_queue_file_name);
3303 chmod(0640, $job_queue_file_name);
3304 chown($root_uid, $adm_gid, $job_queue_file_name);
3306 # connect to known_clients_db
3307 unlink($known_clients_file_name); ## just for debugging
3308 $known_clients_db = GOSA::DBsqlite->new($known_clients_file_name);
3309 chmod(0640, $known_clients_file_name);
3310 chown($root_uid, $adm_gid, $known_clients_file_name);
3312 # connect to foreign_clients_db
3313 unlink($foreign_clients_file_name);
3314 $foreign_clients_db = GOSA::DBsqlite->new($foreign_clients_file_name);
3315 chmod(0640, $foreign_clients_file_name);
3316 chown($root_uid, $adm_gid, $foreign_clients_file_name);
3318 # connect to known_server_db
3319 unlink($known_server_file_name);
3320 $known_server_db = GOSA::DBsqlite->new($known_server_file_name);
3321 chmod(0640, $known_server_file_name);
3322 chown($root_uid, $adm_gid, $known_server_file_name);
3324 # connect to login_usr_db
3325 unlink($login_users_file_name);
3326 $login_users_db = GOSA::DBsqlite->new($login_users_file_name);
3327 chmod(0640, $login_users_file_name);
3328 chown($root_uid, $adm_gid, $login_users_file_name);
3330 # connect to fai_server_db
3331 unlink($fai_server_file_name);
3332 $fai_server_db = GOSA::DBsqlite->new($fai_server_file_name);
3333 chmod(0640, $fai_server_file_name);
3334 chown($root_uid, $adm_gid, $fai_server_file_name);
3336 # connect to fai_release_db
3337 unlink($fai_release_file_name);
3338 $fai_release_db = GOSA::DBsqlite->new($fai_release_file_name);
3339 chmod(0640, $fai_release_file_name);
3340 chown($root_uid, $adm_gid, $fai_release_file_name);
3342 # connect to packages_list_db
3343 #unlink($packages_list_file_name);
3344 unlink($packages_list_under_construction);
3345 $packages_list_db = GOSA::DBsqlite->new($packages_list_file_name);
3346 chmod(0640, $packages_list_file_name);
3347 chown($root_uid, $adm_gid, $packages_list_file_name);
3349 # connect to messaging_db
3350 unlink($messaging_file_name);
3351 $messaging_db = GOSA::DBsqlite->new($messaging_file_name);
3352 chmod(0640, $messaging_file_name);
3353 chown($root_uid, $adm_gid, $messaging_file_name);
3354 }
3355 }
3358 # Creating tables
3359 $messaging_db->create_table($messaging_tn, \@messaging_col_names);
3360 $packages_list_db->create_table($packages_list_tn, \@packages_list_col_names);
3361 $fai_release_db->create_table($fai_release_tn, \@fai_release_col_names);
3362 $fai_server_db->create_table($fai_server_tn, \@fai_server_col_names);
3363 $login_users_db->create_table($login_users_tn, \@login_users_col_names);
3364 $known_server_db->create_table($known_server_tn, \@known_server_col_names);
3365 $foreign_clients_db->create_table($foreign_clients_tn, \@foreign_clients_col_names);
3366 $known_clients_db->create_table($known_clients_tn, \@known_clients_col_names);
3367 $incoming_db->create_table($incoming_tn, \@incoming_col_names);
3368 $job_db->create_table($job_queue_tn, \@job_queue_col_names);
3370 # create xml object used for en/decrypting
3371 $xml = new XML::Simple();
3374 # foreign servers
3375 my @foreign_server_list;
3377 # add foreign server from cfg file
3378 if ($foreign_server_string ne "") {
3379 my @cfg_foreign_server_list = split(",", $foreign_server_string);
3380 foreach my $foreign_server (@cfg_foreign_server_list) {
3381 push(@foreign_server_list, $foreign_server);
3382 }
3384 daemon_log("0 INFO: found foreign server in config file: ".join(", ", @foreign_server_list), 5);
3385 }
3387 # Perform a DNS lookup for server registration if flag is true
3388 if ($dns_lookup eq "true") {
3389 # Add foreign server from dns
3390 my @tmp_servers;
3391 if (not $server_domain) {
3392 # Try our DNS Searchlist
3393 for my $domain(get_dns_domains()) {
3394 chomp($domain);
3395 my ($tmp_domains, $error_string) = &get_server_addresses($domain);
3396 if(@$tmp_domains) {
3397 for my $tmp_server(@$tmp_domains) {
3398 push @tmp_servers, $tmp_server;
3399 }
3400 }
3401 }
3402 if(@tmp_servers && length(@tmp_servers)==0) {
3403 daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
3404 }
3405 } else {
3406 @tmp_servers = &get_server_addresses($server_domain);
3407 if( 0 == @tmp_servers ) {
3408 daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
3409 }
3410 }
3412 daemon_log("0 INFO: found foreign server via DNS ".join(", ", @tmp_servers), 5);
3414 foreach my $server (@tmp_servers) {
3415 unshift(@foreign_server_list, $server);
3416 }
3417 } else {
3418 daemon_log("0 INFO: DNS lookup for server registration is disabled", 5);
3419 }
3422 # eliminate duplicate entries
3423 @foreign_server_list = &del_doubles(@foreign_server_list);
3424 my $all_foreign_server = join(", ", @foreign_server_list);
3425 daemon_log("0 INFO: found foreign server in config file and DNS: '$all_foreign_server'", 5);
3427 # add all found foreign servers to known_server
3428 my $cur_timestamp = &get_time();
3429 foreach my $foreign_server (@foreign_server_list) {
3431 # do not add myself to known_server_db
3432 if (&is_local($foreign_server)) { next; }
3433 ######################################
3435 my $res = $known_server_db->add_dbentry( {table=>$known_server_tn,
3436 primkey=>['hostname'],
3437 hostname=>$foreign_server,
3438 macaddress=>"",
3439 status=>'not_yet_registered',
3440 hostkey=>"none",
3441 loaded_modules => "none",
3442 timestamp=>$cur_timestamp,
3443 } );
3444 }
3447 # Import all modules
3448 &import_modules;
3450 # Check wether all modules are gosa-si valid passwd check
3451 &password_check;
3453 # Prepare for using Opsi
3454 if ($opsi_enabled eq "true") {
3455 use JSON::RPC::Client;
3456 use XML::Quote qw(:all);
3457 $opsi_url= "https://".$opsi_admin.":".$opsi_password."@".$opsi_server.":4447/rpc";
3458 $opsi_client = new JSON::RPC::Client;
3459 }
3462 POE::Component::Server::TCP->new(
3463 Alias => "TCP_SERVER",
3464 Port => $server_port,
3465 ClientInput => sub {
3466 my ($kernel, $input, $heap, $session) = @_[KERNEL, ARG0, HEAP, SESSION];
3467 my $session_id = $session->ID;
3468 my $remote_ip = $heap->{'remote_ip'};
3469 push(@msgs_to_decrypt, $input);
3470 &daemon_log("$session_id DEBUG: incoming message from '$remote_ip'", 7);
3471 $kernel->yield("msg_to_decrypt");
3472 },
3473 InlineStates => {
3474 msg_to_decrypt => \&msg_to_decrypt,
3475 next_task => \&next_task,
3476 task_result => \&handle_task_result,
3477 task_done => \&handle_task_done,
3478 task_debug => \&handle_task_debug,
3479 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!" },
3480 }
3481 );
3483 daemon_log("0 INFO: start socket for incoming xml messages at port '$server_port' ", 1);
3485 # create session for repeatedly checking the job queue for jobs
3486 POE::Session->create(
3487 inline_states => {
3488 _start => \&session_start,
3489 register_at_foreign_servers => \®ister_at_foreign_servers,
3490 sig_handler => \&sig_handler,
3491 next_task => \&next_task,
3492 task_result => \&handle_task_result,
3493 task_done => \&handle_task_done,
3494 task_debug => \&handle_task_debug,
3495 watch_for_next_tasks => \&watch_for_next_tasks,
3496 watch_for_new_messages => \&watch_for_new_messages,
3497 watch_for_delivery_messages => \&watch_for_delivery_messages,
3498 watch_for_done_messages => \&watch_for_done_messages,
3499 watch_for_new_jobs => \&watch_for_new_jobs,
3500 watch_for_modified_jobs => \&watch_for_modified_jobs,
3501 watch_for_done_jobs => \&watch_for_done_jobs,
3502 watch_for_opsi_jobs => \&watch_for_opsi_jobs,
3503 watch_for_old_known_clients => \&watch_for_old_known_clients,
3504 create_packages_list_db => \&run_create_packages_list_db,
3505 create_fai_server_db => \&run_create_fai_server_db,
3506 create_fai_release_db => \&run_create_fai_release_db,
3507 recreate_packages_db => \&run_recreate_packages_db,
3508 session_run_result => \&session_run_result,
3509 session_run_debug => \&session_run_debug,
3510 session_run_done => \&session_run_done,
3511 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!" },
3512 }
3513 );
3516 POE::Kernel->run();
3517 exit;