13960654cc342a3d42a2cfde0a96748055dd12b8
1 #!/usr/bin/perl
2 #*********************************************************************
3 #
4 # gosa-si-client -- client for the gosa-si-server
5 #
6 # (c) 2007-2009 by Andreas Rettenberger <rettenberger@gonicus.de>
7 # (c) 2008-2010 by Cajus Pollmeier <pollmeier@gonicus.de>
8 # (c) 2008-2009 by Jan Wenzel <wenzel@gonicus.de>
9 # (c) 2010 by Benoit Mortier <benoit.mortier@opensides.be>
10 #
11 #*********************************************************************
12 use strict;
13 use warnings;
15 use Getopt::Long;
16 use Config::IniFiles;
17 use IO::Socket::INET;
18 use IO::Handle;
19 use IO::Select;
20 use Crypt::Rijndael;
21 use MIME::Base64;
22 use Digest::MD5 qw(md5 md5_hex md5_base64);
23 use XML::Simple;
24 use Data::Dumper;
25 use Sys::Syslog qw( :DEFAULT setlogsock);
26 use Time::HiRes qw( usleep clock_gettime );
27 use File::Spec;
28 use File::Basename;
29 use File::Find;
30 use File::Copy;
31 use File::Path;
32 use Net::LDAP;
33 use Net::LDAP::Util qw(:escape);
34 use File::Pid;
35 use GOsaSI::GosaSupportDaemon;
37 use POE qw(Component::Server::TCP Wheel::Run Filter::Reference);
38 use Symbol qw(qualify_to_ref);
39 use Fcntl qw/:flock/;
40 use POSIX;
42 my $server_version = '$HeadURL: https://oss.gonicus.de/repositories/gosa/trunk/gosa-si/gosa-si-server $:$Rev$';
44 # revision number of server and program name
45 my $server_headURL;
46 my $server_revision;
47 my $server_status;
49 my $db_module = "DBsqlite";
50 {
51 no strict "refs";
52 require ("GOsaSI/".$db_module.".pm");
53 ("GOsaSI/".$db_module)->import;
54 }
56 my $modules_path = "/usr/lib/gosa-si/modules";
57 use lib "/usr/lib/gosa-si/modules";
59 my ($foreground, $ping_timeout);
60 my ($server);
61 my ($gosa_server, $job_queue_timeout, $job_queue_loop_delay);
62 my ($messaging_db_loop_delay);
63 my $procid;
64 my $arp_fifo;
65 my $debug_parts = 0;
66 my $debug_parts_bitstring;
67 my ($xml);
68 my $sources_list;
69 my $max_clients;
70 my %repo_files=();
71 my $repo_path;
72 my %repo_dirs=();
74 # Variables declared in config file are always set to 'our'
75 our (%cfg_defaults, $log_file, $pid_file, $pid
76 $server_ip, $server_port, $ClientPackages_key, $dns_lookup,
77 $arp_activ, $gosa_unit_tag,
78 $GosaPackages_key, $gosa_timeout,
79 $serverPackages_enabled, $foreign_server_string, $server_domain, $ServerPackages_key, $foreign_servers_register_delay,
80 $wake_on_lan_passwd, $job_synchronization, $modified_jobs_loop_delay,
81 $arp_enabled, $arp_interface,
82 $opsi_enabled, $opsi_server, $opsi_admin, $opsi_password,
83 $new_systems_ou,
84 $arch,
85 );
87 # additional variable which should be globaly accessable
88 our $server_address;
89 our $server_mac_address;
90 our $gosa_address;
91 our $no_arp;
92 our $forground;
93 our $cfg_file;
94 our ($ldap_uri, $ldap_base, $ldap_admin_dn, $ldap_admin_password, $ldap_server_dn, $ldap_version, $ldap_retry_sec);
95 our ($mysql_username, $mysql_password, $mysql_database, $mysql_host);
96 our $known_modules;
97 our $known_functions;
98 our $root_uid;
99 our $adm_gid;
100 our $verbose= 0;
101 our $global_kernel;
103 # where is the config stored by default and his name
104 our $config = '/etc/gosa-si/server.conf';
106 # by default dumping of config is undefined
107 our $dump_config = undef;
109 # if foreground is not null, script will be not forked to background
110 $foreground = 0 ;
112 # specifies the timeout seconds while checking the online status of a registrating client
113 $ping_timeout = 5;
115 $no_arp = 0;
116 my $packages_list_under_construction = "/tmp/packages_list_creation_in_progress";
117 my @packages_list_statements;
118 my $watch_for_new_jobs_in_progress = 0;
120 # holds all incoming decrypted messages
121 our $incoming_db;
122 our $incoming_tn = 'incoming';
123 my $incoming_file_name;
124 my @incoming_col_names = ("id INTEGER PRIMARY KEY",
125 "timestamp VARCHAR(14) DEFAULT 'none'",
126 "headertag VARCHAR(255) DEFAULT 'none'",
127 "targettag VARCHAR(255) DEFAULT 'none'",
128 "xmlmessage TEXT",
129 "module VARCHAR(255) DEFAULT 'none'",
130 "sessionid VARCHAR(255) DEFAULT '0'",
131 );
133 # holds all gosa jobs
134 our $job_db;
135 our $job_queue_tn = 'jobs';
136 my $job_queue_file_name;
137 my @job_queue_col_names = ("id INTEGER PRIMARY KEY",
138 "timestamp VARCHAR(14) DEFAULT 'none'",
139 "status VARCHAR(255) DEFAULT 'none'",
140 "result TEXT",
141 "progress VARCHAR(255) DEFAULT 'none'",
142 "headertag VARCHAR(255) DEFAULT 'none'",
143 "targettag VARCHAR(255) DEFAULT 'none'",
144 "xmlmessage TEXT",
145 "macaddress VARCHAR(17) DEFAULT 'none'",
146 "plainname VARCHAR(255) DEFAULT 'none'",
147 "siserver VARCHAR(255) DEFAULT 'none'",
148 "modified INTEGER DEFAULT '0'",
149 "periodic VARCHAR(6) DEFAULT 'none'",
150 );
152 # holds all other gosa-si-server
153 our $known_server_db;
154 our $known_server_tn = "known_server";
155 my $known_server_file_name;
156 my @known_server_col_names = ("hostname VARCHAR(255)", "macaddress VARCHAR(17)", "status VARCHAR(255)", "hostkey VARCHAR(255)", "loaded_modules TEXT", "timestamp VARCHAR(14)", "update_time VARCHAR(14)");
158 # holds all registrated clients
159 our $known_clients_db;
160 our $known_clients_tn = "known_clients";
161 my $known_clients_file_name;
162 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)");
164 # holds all registered clients at a foreign server
165 our $foreign_clients_db;
166 our $foreign_clients_tn = "foreign_clients";
167 my $foreign_clients_file_name;
168 my @foreign_clients_col_names = ("hostname VARCHAR(255)", "macaddress VARCHAR(17)", "regserver VARCHAR(255)", "timestamp VARCHAR(14)");
170 # holds all logged in user at each client
171 our $login_users_db;
172 our $login_users_tn = "login_users";
173 my $login_users_file_name;
174 my @login_users_col_names = ("client VARCHAR(255)", "user VARCHAR(255)", "timestamp VARCHAR(14)", "regserver VARCHAR(255) DEFAULT 'localhost'");
176 # holds all fai server, the debian release and tag
177 our $fai_server_db;
178 our $fai_server_tn = "fai_server";
179 my $fai_server_file_name;
180 our @fai_server_col_names = ("timestamp VARCHAR(14)", "server VARCHAR(255)", "fai_release VARCHAR(255)", "sections VARCHAR(255)", "tag VARCHAR(255)");
182 our $fai_release_db;
183 our $fai_release_tn = "fai_release";
184 my $fai_release_file_name;
185 our @fai_release_col_names = ("timestamp VARCHAR(14)", "fai_release VARCHAR(255)", "class VARCHAR(255)", "type VARCHAR(255)", "state VARCHAR(255)");
187 # holds all packages available from different repositories
188 our $packages_list_db;
189 our $packages_list_tn = "packages_list";
190 my $packages_list_file_name;
191 our @packages_list_col_names = ("distribution VARCHAR(255)", "package VARCHAR(255)", "version VARCHAR(255)", "section VARCHAR(255)", "description TEXT", "template LONGBLOB", "timestamp VARCHAR(14)");
192 my $outdir = "/tmp/packages_list_db";
194 # holds all messages which should be delivered to a user
195 our $messaging_db;
196 our $messaging_tn = "messaging";
197 our @messaging_col_names = ("id INTEGER", "subject TEXT", "message_from VARCHAR(255)", "message_to VARCHAR(255)",
198 "flag VARCHAR(255)", "direction VARCHAR(255)", "delivery_time VARCHAR(255)", "message TEXT", "timestamp VARCHAR(14)" );
199 my $messaging_file_name;
201 # path to directory to store client install log files
202 our $client_fai_log_dir = "/var/log/fai";
204 # queue which stores taskes until one of the $max_children children are ready to process the task
205 #my @tasks = qw();
206 my @msgs_to_decrypt = qw();
207 my $max_children = 2;
210 # loop delay for job queue to look for opsi jobs
211 my $job_queue_opsi_delay = 10;
212 our $opsi_client;
213 our $opsi_url;
215 # Lifetime of logged in user information. If no update information comes after n seconds,
216 # the user is expeceted to be no longer logged in or the host is no longer running. Because
217 # of this, the user is deleted from login_users_db
218 our $logged_in_user_date_of_expiry = 600;
220 # List of month names, used in function daemon_log
221 my @monthnames = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
223 # List of accepted periodical xml tags related to cpan modul DateTime
224 our $check_periodic = {"months"=>'', "weeks"=>'', "days"=>'', "hours"=>'', "minutes"=>''};
227 %cfg_defaults = (
228 "General" => {
229 "log-file" => [\$log_file, "/var/log/gosa-si/gosa-si-server.log"],
230 "pid-file" => [\$pid_file, "/var/run/gosa-si/gosa-si-server.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'],
246 "debian-arch" => [\$arch, 'i386'],
247 "ldap-uri" => [\$ldap_uri, ""],
248 "ldap-base" => [\$ldap_base, ""],
249 "ldap-admin-dn" => [\$ldap_admin_dn, ""],
250 "ldap-admin-password" => [\$ldap_admin_password, ""],
251 "ldap-version" => [\$ldap_version, 3],
252 "ldap-retry-sec" => [\$ldap_retry_sec, 10],
253 "gosa-unit-tag" => [\$gosa_unit_tag, ""],
254 "max-clients" => [\$max_clients, 10],
255 "wol-password" => [\$wake_on_lan_passwd, ""],
256 "mysql-username" => [\$mysql_username, "gosa_si"],
257 "mysql-password" => [\$mysql_password, ""],
258 "mysql-database" => [\$mysql_database, "gosa_si"],
259 "mysql-host" => [\$mysql_host, "127.0.0.1"],
260 },
261 "GOsaPackages" => {
262 "job-queue" => [\$job_queue_file_name, '/var/lib/gosa-si/jobs.db'],
263 "job-queue-loop-delay" => [\$job_queue_loop_delay, 3],
264 "messaging-db-loop-delay" => [\$messaging_db_loop_delay, 3],
265 "key" => [\$GosaPackages_key, "none"],
266 "new-systems-ou" => [\$new_systems_ou, 'ou=workstations,ou=systems'],
267 },
268 "ClientPackages" => {
269 "key" => [\$ClientPackages_key, "none"],
270 "user-date-of-expiry" => [\$logged_in_user_date_of_expiry, 600],
271 },
272 "ServerPackages"=> {
273 "enabled" => [\$serverPackages_enabled, "true"],
274 "address" => [\$foreign_server_string, ""],
275 "dns-lookup" => [\$dns_lookup, "true"],
276 "domain" => [\$server_domain, ""],
277 "key" => [\$ServerPackages_key, "none"],
278 "key-lifetime" => [\$foreign_servers_register_delay, 120],
279 "job-synchronization-enabled" => [\$job_synchronization, "true"],
280 "synchronization-loop" => [\$modified_jobs_loop_delay, 5],
281 },
282 "ArpHandler" => {
283 "enabled" => [\$arp_enabled, "false"],
284 "interface" => [\$arp_interface, "all"],
285 },
286 "Opsi" => {
287 "enabled" => [\$opsi_enabled, "false"],
288 "server" => [\$opsi_server, "localhost"],
289 "admin" => [\$opsi_admin, "opsi-admin"],
290 "password" => [\$opsi_password, "secret"],
291 },
293 );
295 #############################
296 #
297 # @brief Display error message and/or help text.
298 #
299 # In correspondence to previous GetOptions
300 #
301 # @param $text - string to print as error message
302 # @param $help - set true, if you want to show usage help
303 #
304 sub usage
305 {
306 my( $text, $help ) = @_;
308 $text = undef if( 'h' eq $text );
309 (defined $text) && print STDERR "\n$text\n";
311 if( (defined $help && $help)
312 || (!defined $help && !defined $text) )
313 {
314 print STDERR << "EOF";
316 usage: $0 [-hvf] [-c config] [-d number]
318 -h : this (help) message
319 -c <file> : config file (default: ${config})
320 -x <cfg> : dump configuration to stdout
321 ( 1 = current, 2 = default )
322 -f : foreground (don't fork)
323 -v : be verbose (multiple to increase verbosity)
324 'v': error logs
325 'vvv': warning plus error logs
326 'vvvvv': info plus warning logs
327 'vvvvvvv': debug plus info logs
328 -no-arp : starts $prg without connection to arp module
329 -d <int> : if verbose level is higher than 7x 'v' specified parts can be debugged
330 1 : report incoming messages
331 2 : report unencrypted outgoing messages
332 4 : report encrypting key for messages
333 8 : report decrypted incoming message and verification if the message complies gosa-si requirements
334 16 : message processing
335 32 : ldap connectivity
336 64 : database status and connectivity
337 128 : main process
338 256 : creation of packages_list_db
339 512 : ARP debug information
340 EOF
341 }
342 print( "\n" );
344 exit( -1 );
345 }
347 #############################
348 #
349 # @brief Manage gosa-si-client configuration.
350 #
351 # Will exit after successfull dump to stdout (type = 1 | 2)
352 #
353 # Dump type can be:
354 # 1: Current gosa-si-client configuration in config file (exit)
355 # 2: Default gosa-si-client configuration (exit)
356 # 3: Dump to logfile (no exit)
357 #
358 # @param int config type
359 #
360 sub dump_configuration {
362 my( $cfg_type ) = @_;
364 return if( ! defined $cfg_type );
366 if(1==$cfg_type ) {
367 print( "# Current gosa-si-server configuration\n" );
368 } elsif (2==$cfg_type) {
369 print( "# Default gosa-si-server configuration\n" );
370 } elsif (3==$cfg_type) {
371 daemon_log( "Dumping gosa-si-server configuration\n", 2 );
372 } else {
373 return;
374 }
376 foreach my $section (keys %cfg_defaults) {
377 if( 3 != $cfg_type ) {
378 print( "\n[${section}]\n" );
379 } else {
380 daemon_log( "\n [${section}]\n", 3 );
381 }
383 foreach my $param (sort( keys %{$cfg_defaults{ $section }})) {
384 my $pinfo = $cfg_defaults{ $section }{ $param };
385 my $value;
386 if (1==$cfg_type) {
387 if( defined( ${@$pinfo[ 0 ]} ) ) {
388 $value = ${@$pinfo[ 0 ]};
389 print( "$param=$value\n" );
390 } else {
391 print( "#${param}=\n" );
392 }
393 } elsif (2==$cfg_type) {
394 $value = @{$pinfo}[ 1 ];
395 if( defined( @$pinfo[ 1 ] ) ) {
396 $value = @{$pinfo}[ 1 ];
397 print( "$param=$value\n" );
398 } else {
399 print( "#${param}=\n" );
400 }
401 } elsif (3==$cfg_type) {
402 if( defined( ${@$pinfo[ 0 ]} ) ) {
403 $value = ${@$pinfo[ 0 ]};
404 daemon_log( " $param=$value\n", 3 )
405 }
406 }
407 }
408 }
411 # We just exit at stdout dump
412 if( 3 == $cfg_type ) {
413 daemon_log( "\n", 3 );
414 } else {
415 exit( 0 );
416 }
417 }
420 #=== FUNCTION ================================================================
421 # NAME: logging
422 # PARAMETERS: level - string - default 'info'
423 # msg - string -
424 # facility - string - default 'LOG_DAEMON'
425 # RETURNS: nothing
426 # DESCRIPTION: function for logging
427 #===============================================================================
428 sub daemon_log {
429 my( $msg, $level ) = @_;
430 if (not defined $msg) { return }
431 if (not defined $level) { $level = 1 }
432 my $to_be_logged = 0;
434 # Write log line if line level is lower than verbosity given in commandline
435 if ($level <= $verbose)
436 {
437 $to_be_logged = 1 ;
438 }
440 # Write if debug flag is set and bitstring matches
441 if ($debug_parts > 0)
442 {
443 my $tmp_level = ($level - 10 >= 0) ? $level - 10 : 0 ;
444 my $tmp_level_bitstring = unpack("B32", pack("N", $tmp_level));
445 if (int($debug_parts_bitstring & $tmp_level_bitstring))
446 {
447 $to_be_logged = 1;
448 }
449 }
451 if ($to_be_logged)
452 {
453 if(defined $log_file){
454 my $open_log_fh = sysopen(LOG_HANDLE, $log_file, O_WRONLY | O_CREAT | O_APPEND , 0440);
455 if(not $open_log_fh) {
456 print STDERR "cannot open $log_file: $!";
457 return;
458 }
459 # Check owner and group of log_file and update settings if necessary
460 my ($dev, $ino, $mode, $nlink, $uid, $gid, $rdev, $size, $atime, $mtime, $ctime, $blksize, $blocks) = stat($log_file);
461 if((not $uid eq $root_uid) || (not $gid eq $adm_gid)) {
462 chown($root_uid, $adm_gid, $log_file);
463 }
465 # Prepare time string for log message
466 my ($seconds,$minutes,$hours,$monthday,$month,$year,$weekday,$yearday,$sommertime) = localtime(time);
467 $hours = $hours < 10 ? $hours = "0".$hours : $hours;
468 $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes;
469 $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds;
470 $month = $monthnames[$month];
471 $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday;
472 $year+=1900;
474 # Microseconds since epoch
475 my $microSeconds = sprintf("%.2f", &Time::HiRes::clock_gettime());
476 $microSeconds =~ s/^\d*(.\d\d)$/$1/;
478 # Build log message and write it to log file and commandline
479 chomp($msg);
480 my $log_msg = "$month $monthday $hours:$minutes:$seconds$microSeconds $prg $msg\n";
481 flock(LOG_HANDLE, LOCK_EX);
482 seek(LOG_HANDLE, 0, 2);
483 print LOG_HANDLE $log_msg;
484 flock(LOG_HANDLE, LOCK_UN);
485 if( $foreground )
486 {
487 print STDERR $log_msg;
488 }
489 close( LOG_HANDLE );
490 }
491 }
492 }
495 #=== FUNCTION ================================================================
496 # NAME: check_cmdline_param
497 # PARAMETERS: nothing
498 # RETURNS: nothing
499 # DESCRIPTION: validates commandline parameter
500 #===============================================================================
501 sub check_cmdline_param () {
502 my $err_counter = 0;
504 # Check configuration file
505 if(not defined($cfg_file)) {
506 $cfg_file = "/etc/gosa-si/server.conf";
507 if(! -r $cfg_file) {
508 print STDERR "Please specify a config file.\n";
509 $err_counter++;
510 }
511 }
513 # Prepare identification which gosa-si parts should be debugged and which not
514 if (defined $debug_parts)
515 {
516 if ($debug_parts =~ /^\d+$/)
517 {
518 $debug_parts_bitstring = unpack("B32", pack("N", $debug_parts));
519 }
520 else
521 {
522 print STDERR "Value '$debug_parts' invalid for option d (number expected)\n";
523 $err_counter++;
524 }
525 }
527 # Exit if an error occour
528 if( $err_counter > 0 ) { &usage( "", 1 ); }
529 }
532 #=== FUNCTION ================================================================
533 # NAME: check_pid
534 # PARAMETERS: nothing
535 # RETURNS: nothing
536 # DESCRIPTION: handels pid processing
537 #===============================================================================
538 sub check_pid {
539 $pid = -1;
540 # Check, if we are already running
541 if( open( my $LOCK_FILE, "<", "$pid_file" ) ) {
542 $pid = <$LOCK_FILE>;
543 if( defined $pid ) {
544 chomp( $pid );
545 if( -f "/proc/$pid/stat" ) {
546 my($stat) = `cat /proc/$pid/stat` =~ m/$pid \((.+)\).*/;
547 if( $stat ) {
548 print STDERR "\nERROR: Already running!\n";
549 close( $LOCK_FILE );
550 exit -1;
551 }
552 }
553 }
554 close( $LOCK_FILE );
555 unlink( $pid_file );
556 }
558 # create a syslog msg if it is not to possible to open PID file
559 if (not sysopen(my $LOCK_FILE, $pid_file, O_WRONLY|O_CREAT|O_EXCL, 0644)) {
560 my($msg) = "Couldn't obtain lockfile '$pid_file' ";
561 if (open(my $LOCK_FILE, '<', $pid_file)
562 && ($pid = <$LOCK_FILE>))
563 {
564 chomp($pid);
565 $msg .= "(PID $pid)\n";
566 } else {
567 $msg .= "(unable to read PID)\n";
568 }
569 if( ! ($foreground) ) {
570 openlog( $0, "cons,pid", "daemon" );
571 syslog( "warning", $msg );
572 closelog();
573 }
574 else {
575 print( STDERR " $msg " );
576 }
577 exit( -1 );
578 }
579 }
581 #=== FUNCTION ================================================================
582 # NAME: import_modules
583 # PARAMETERS: module_path - string - abs. path to the directory the modules
584 # are stored
585 # RETURNS: nothing
586 # DESCRIPTION: each file in module_path which ends with '.pm' and activation
587 # state is on is imported by "require 'file';"
588 #===============================================================================
589 sub import_modules {
590 if (not -e $modules_path) {
591 daemon_log("0 ERROR: cannot find directory or directory is not readable: $modules_path", 1);
592 }
594 opendir (DIR, $modules_path) or die "ERROR while loading modules from directory $modules_path : $!\n";
596 while (defined (my $file = readdir (DIR))) {
597 if (not $file =~ /(\S*?).pm$/) {
598 next;
599 }
600 my $mod_name = $1;
602 # ArpHandler switch
603 if( $file =~ /ArpHandler.pm/ ) {
604 if( $arp_enabled eq "false" ) { next; }
605 }
607 # ServerPackages switch
608 if ($file eq "ServerPackages.pm" && $serverPackages_enabled eq "false")
609 {
610 $dns_lookup = "false";
611 next;
612 }
614 eval { require $file; };
615 if ($@) {
616 daemon_log("0 ERROR: gosa-si-server could not load module $file", 1);
617 daemon_log("$@", 1);
618 exit;
619 } else {
620 my $info = eval($mod_name.'::get_module_info()');
621 # Only load module if get_module_info() returns a non-null object
622 if( $info ) {
623 my ($input_address, $input_key, $event_hash) = @{$info};
624 $known_modules->{$mod_name} = $info;
625 daemon_log("0 INFO: module $mod_name loaded", 5);
626 }
627 }
628 }
629 close (DIR);
630 }
632 #=== FUNCTION ================================================================
633 # NAME: password_check
634 # PARAMETERS: nothing
635 # RETURNS: nothing
636 # DESCRIPTION: escalates an critical error if two modules exist which are avaialable by
637 # the same password
638 #===============================================================================
639 sub password_check {
640 my $passwd_hash = {};
641 while (my ($mod_name, $mod_info) = each %$known_modules) {
642 my $mod_passwd = @$mod_info[1];
643 if (not defined $mod_passwd) { next; }
644 if (not exists $passwd_hash->{$mod_passwd}) {
645 $passwd_hash->{$mod_passwd} = $mod_name;
647 # escalates critical error
648 } else {
649 &daemon_log("0 ERROR: two loaded modules do have the same password. Please modify the 'key'-parameter in config file");
650 &daemon_log("0 ERROR: module='$mod_name' and module='".$passwd_hash->{$mod_passwd}."'");
651 exit( -1 );
652 }
653 }
655 }
658 #=== FUNCTION ================================================================
659 # NAME: sig_int_handler
660 # PARAMETERS: signal - string - signal came from system
661 # RETURNS: nothing
662 # DESCRIPTION: handle tasks to be done before signal becomes active
663 #===============================================================================
664 sub sig_int_handler {
665 my ($signal) = @_;
667 # if (defined($ldap_handle)) {
668 # $ldap_handle->disconnect;
669 # }
670 # TODO alle verbliebenden ldap verbindungen aus allen heaps beenden
672 daemon_log("shutting down gosa-si-server", 1);
674 $global_kernel->yield('shutdown');
676 # to be removed rather crude !!
677 #system("kill `ps -C gosa-si-server -o pid=`");
679 $pid->remove or warn "Could not remove $pidfile\n";
681 exit(0);
682 }
683 $SIG{INT} = \&sig_int_handler;
686 sub check_key_and_xml_validity {
687 my ($crypted_msg, $module_key, $session_id) = @_;
688 my $msg;
689 my $msg_hash;
690 my $error_string;
691 eval{
692 $msg = &decrypt_msg($crypted_msg, $module_key);
694 if ($msg =~ /<xml>/i){
695 $msg =~ s/\s+/ /g; # just for better daemon_log
696 daemon_log("$session_id DEBUG: decrypted_msg: \n$msg", 18);
697 $msg_hash = $xml->XMLin($msg, ForceArray=>1);
699 ##############
700 # check header
701 if( not exists $msg_hash->{'header'} ) { die "no header specified"; }
702 my $header_l = $msg_hash->{'header'};
703 if( (1 > @{$header_l}) || ( ( 'HASH' eq ref @{$header_l}[0]) && (1 > keys %{@{$header_l}[0]}) ) ) { die 'empty header tag'; }
704 if( 1 < @{$header_l} ) { die 'more than one header specified'; }
705 my $header = @{$header_l}[0];
706 if( 0 == length $header) { die 'empty string in header tag'; }
708 ##############
709 # check source
710 if( not exists $msg_hash->{'source'} ) { die "no source specified"; }
711 my $source_l = $msg_hash->{'source'};
712 if( (1 > @{$source_l}) || ( ( 'HASH' eq ref @{$source_l}[0]) && (1 > keys %{@{$source_l}[0]}) ) ) { die 'empty source tag'; }
713 if( 1 < @{$source_l} ) { die 'more than one source specified'; }
714 my $source = @{$source_l}[0];
715 if( 0 == length $source) { die 'source error'; }
717 ##############
718 # check target
719 if( not exists $msg_hash->{'target'} ) { die "no target specified"; }
720 my $target_l = $msg_hash->{'target'};
721 if( (1 > @{$target_l}) || ( ('HASH' eq ref @{$target_l}[0]) && (1 > keys %{@{$target_l}[0]}) ) ) { die 'empty target tag'; }
722 }
723 };
724 if($@) {
725 daemon_log("$session_id ERROR: do not understand the message: $@", 1);
726 $msg = undef;
727 $msg_hash = undef;
728 }
730 return ($msg, $msg_hash);
731 }
734 sub check_outgoing_xml_validity {
735 my ($msg, $session_id) = @_;
737 my $msg_hash;
738 eval{
739 $msg_hash = $xml->XMLin($msg, ForceArray=>1);
741 ##############
742 # check header
743 my $header_l = $msg_hash->{'header'};
744 if( 1 != @{$header_l} ) {
745 die 'no or more than one headers specified';
746 }
747 my $header = @{$header_l}[0];
748 if( 0 == length $header) {
749 die 'header has length 0';
750 }
752 ##############
753 # check source
754 my $source_l = $msg_hash->{'source'};
755 if( 1 != @{$source_l} ) {
756 die 'no or more than 1 sources specified';
757 }
758 my $source = @{$source_l}[0];
759 if( 0 == length $source) {
760 die 'source has length 0';
761 }
763 # Check if source contains hostname instead of ip address
764 if($source =~ /^[a-z][\w\-\.]+:\d+$/i) {
765 my ($hostname,$port) = split(/:/, $source);
766 my $ip_address = inet_ntoa(scalar gethostbyname($hostname));
767 if(defined($ip_address) && $ip_address =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/ && $port =~ /^\d+$/) {
768 # Write ip address to $source variable
769 $source = "$ip_address:$port";
770 $msg_hash->{source}[0] = $source ;
771 $msg =~ s/<source>.*<\/source>/<source>$source<\/source>/;
772 }
773 }
774 unless( $source =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
775 $source =~ /^GOSA$/i) {
776 die "source '$source' is neither a complete ip-address with port nor 'GOSA'";
777 }
779 ##############
780 # check target
781 my $target_l = $msg_hash->{'target'};
782 if( 0 == @{$target_l} ) {
783 die "no targets specified";
784 }
785 foreach my $target (@$target_l) {
786 if( 0 == length $target) {
787 die "target has length 0";
788 }
789 unless( $target =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
790 $target =~ /^GOSA$/i ||
791 $target =~ /^\*$/ ||
792 $target =~ /KNOWN_SERVER/i ||
793 $target =~ /JOBDB/i ||
794 $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 ){
795 die "target '$target' is not a complete ip-address with port or a valid target name or a mac-address";
796 }
797 }
798 };
799 if($@) {
800 daemon_log("$session_id ERROR: outgoing msg is not gosa-si envelope conform: $@", 1);
801 daemon_log("$@ ".(defined($msg) && length($msg)>0)?$msg:"Empty Message", 1);
802 $msg_hash = undef;
803 }
805 return ($msg, $msg_hash);
806 }
809 sub input_from_known_server {
810 my ($input, $remote_ip, $session_id) = @_ ;
811 my ($msg, $msg_hash, $module);
813 my $sql_statement= "SELECT * FROM known_server";
814 my $query_res = $known_server_db->select_dbentry( $sql_statement );
816 while( my ($hit_num, $hit) = each %{ $query_res } ) {
817 my $host_name = $hit->{hostname};
818 if( not $host_name =~ "^$remote_ip") {
819 next;
820 }
821 my $host_key = $hit->{hostkey};
822 daemon_log("$session_id DEBUG: input_from_known_server: host_name: $host_name", 14);
823 daemon_log("$session_id DEBUG: input_from_known_server: host_key: $host_key", 14);
825 # check if module can open msg envelope with module key
826 my ($tmp_msg, $tmp_msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
827 if( (!$tmp_msg) || (!$tmp_msg_hash) ) {
828 daemon_log("$session_id DEBUG: input_from_known_server: deciphering raise error", 14);
829 daemon_log("$@", 14);
830 next;
831 }
832 else {
833 $msg = $tmp_msg;
834 $msg_hash = $tmp_msg_hash;
835 $module = "ServerPackages";
836 daemon_log("$session_id DEBUG: check_key_and_xml_validity... ok", 14);
837 last;
838 }
839 }
841 if( (!$msg) || (!$msg_hash) || (!$module) ) {
842 daemon_log("$session_id DEBUG: Incoming message is not from a known server", 14);
843 }
845 return ($msg, $msg_hash, $module);
846 }
849 sub input_from_known_client {
850 my ($input, $remote_ip, $session_id) = @_ ;
851 my ($msg, $msg_hash, $module);
853 my $sql_statement= "SELECT * FROM known_clients";
854 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
855 while( my ($hit_num, $hit) = each %{ $query_res } ) {
856 my $host_name = $hit->{hostname};
857 if( not $host_name =~ /^$remote_ip/) {
858 next;
859 }
860 my $host_key = $hit->{hostkey};
861 &daemon_log("$session_id DEBUG: input_from_known_client: host_name: $host_name", 14);
862 &daemon_log("$session_id DEBUG: input_from_known_client: host_key: $host_key", 14);
864 # check if module can open msg envelope with module key
865 ($msg, $msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
867 if( (!$msg) || (!$msg_hash) ) {
868 &daemon_log("$session_id DEGUG: input_from_known_client: deciphering raise error", 14);
869 next;
870 }
871 else {
872 $module = "ClientPackages";
873 daemon_log("$session_id DEBUG: check_key_and_xml_validity... ok", 14);
874 last;
875 }
876 }
878 if( (!$msg) || (!$msg_hash) || (!$module) ) {
879 &daemon_log("$session_id DEBUG: Incoming message is not from a known client", 14);
880 }
882 return ($msg, $msg_hash, $module);
883 }
886 sub input_from_unknown_host {
887 no strict "refs";
888 my ($input, $session_id) = @_ ;
889 my ($msg, $msg_hash, $module);
890 my $error_string;
892 my %act_modules = %$known_modules;
894 while( my ($mod, $info) = each(%act_modules)) {
896 # check a key exists for this module
897 my $module_key = ${$mod."_key"};
898 if( not defined $module_key ) {
899 if( $mod eq 'ArpHandler' ) {
900 next;
901 }
902 daemon_log("$session_id ERROR: no key specified in config file for $mod", 1);
903 next;
904 }
905 daemon_log("$session_id DEBUG: $mod: $module_key", 14);
907 # check if module can open msg envelope with module key
908 ($msg, $msg_hash) = &check_key_and_xml_validity($input, $module_key, $session_id);
909 if( (not defined $msg) || (not defined $msg_hash) ) {
910 next;
911 } else {
912 $module = $mod;
913 daemon_log("$session_id DEBUG: check_key_and_xml_validity... ok", 18);
914 last;
915 }
916 }
918 if( (!$msg) || (!$msg_hash) || (!$module)) {
919 daemon_log("$session_id DEBUG: Incoming message is not from an unknown host", 14);
920 }
922 return ($msg, $msg_hash, $module);
923 }
926 sub create_ciphering {
927 my ($passwd) = @_;
928 if((!defined($passwd)) || length($passwd)==0) {
929 $passwd = "";
930 }
931 $passwd = substr(md5_hex("$passwd") x 32, 0, 32);
932 my $iv = substr(md5_hex('GONICUS GmbH'),0, 16);
933 my $my_cipher = Crypt::Rijndael->new($passwd , Crypt::Rijndael::MODE_CBC());
934 $my_cipher->set_iv($iv);
935 return $my_cipher;
936 }
939 sub encrypt_msg {
940 my ($msg, $key) = @_;
941 my $my_cipher = &create_ciphering($key);
942 my $len;
943 {
944 use bytes;
945 $len= 16-length($msg)%16;
946 }
947 $msg = "\0"x($len).$msg;
948 $msg = $my_cipher->encrypt($msg);
949 chomp($msg = &encode_base64($msg));
950 # there are no newlines allowed inside msg
951 $msg=~ s/\n//g;
952 return $msg;
953 }
956 sub decrypt_msg {
958 my ($msg, $key) = @_ ;
959 $msg = &decode_base64($msg);
960 my $my_cipher = &create_ciphering($key);
961 $msg = $my_cipher->decrypt($msg);
962 $msg =~ s/\0*//g;
963 return $msg;
964 }
967 sub get_encrypt_key {
968 my ($target) = @_ ;
969 my $encrypt_key;
970 my $error = 0;
972 # target can be in known_server
973 if( not defined $encrypt_key ) {
974 my $sql_statement= "SELECT * FROM known_server WHERE hostname='$target'";
975 my $query_res = $known_server_db->select_dbentry( $sql_statement );
976 while( my ($hit_num, $hit) = each %{ $query_res } ) {
977 my $host_name = $hit->{hostname};
978 if( $host_name ne $target ) {
979 next;
980 }
981 $encrypt_key = $hit->{hostkey};
982 last;
983 }
984 }
986 # target can be in known_client
987 if( not defined $encrypt_key ) {
988 my $sql_statement= "SELECT * FROM known_clients WHERE hostname='$target'";
989 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
990 while( my ($hit_num, $hit) = each %{ $query_res } ) {
991 my $host_name = $hit->{hostname};
992 if( $host_name ne $target ) {
993 next;
994 }
995 $encrypt_key = $hit->{hostkey};
996 last;
997 }
998 }
1000 return $encrypt_key;
1001 }
1004 #=== FUNCTION ================================================================
1005 # NAME: open_socket
1006 # PARAMETERS: PeerAddr string something like 192.168.1.1 or 192.168.1.1:10000
1007 # [PeerPort] string necessary if port not appended by PeerAddr
1008 # RETURNS: socket IO::Socket::INET
1009 # DESCRIPTION: open a socket to PeerAddr
1010 #===============================================================================
1011 sub open_socket {
1012 my ($PeerAddr, $PeerPort) = @_ ;
1013 if(defined($PeerPort)){
1014 $PeerAddr = $PeerAddr.":".$PeerPort;
1015 }
1016 my $socket;
1017 $socket = new IO::Socket::INET(PeerAddr => $PeerAddr,
1018 Porto => "tcp",
1019 Type => SOCK_STREAM,
1020 Timeout => 5,
1021 );
1022 if(not defined $socket) {
1023 return;
1024 }
1025 # &daemon_log("DEBUG: open_socket: $PeerAddr", 7);
1026 return $socket;
1027 }
1030 sub send_msg_to_target {
1031 my ($msg, $address, $encrypt_key, $msg_header, $session_id) = @_ ;
1032 my $error = 0;
1033 my $header;
1034 my $timestamp = &get_time();
1035 my $new_status;
1036 my $act_status;
1037 my ($sql_statement, $res);
1039 if( $msg_header ) {
1040 $header = "'$msg_header'-";
1041 } else {
1042 $header = "";
1043 }
1045 # Memorize own source address
1046 my $own_source_address = &get_local_ip_for_remote_ip(sprintf("%s", $address =~ /^([0-9\.]*?):.*$/));
1047 $own_source_address .= ":".$server_port;
1049 # Patch 0.0.0.0 source to real address
1050 $msg =~ s/<source>(0\.0\.0\.0):(\d*?)<\/source>/<source>$own_source_address<\/source>/s;
1051 # Patch GOSA source to real address and add forward_to_gosa tag
1052 $msg =~ s/<source>GOSA<\/source>/<source>$own_source_address<\/source> <forward_to_gosa>$own_source_address,$session_id<\/forward_to_gosa>/ ;
1054 # encrypt xml msg
1055 my $crypted_msg = &encrypt_msg($msg, $encrypt_key);
1057 # opensocket
1058 my $socket = &open_socket($address);
1059 if( !$socket ) {
1060 daemon_log("$session_id ERROR: Cannot open socket to host '$address'. Message processing aborted!", 1);
1061 $error++;
1062 }
1064 if( $error == 0 ) {
1065 # send xml msg
1066 print $socket $crypted_msg.";$own_source_address\n";
1067 daemon_log("$session_id INFO: send ".$header."msg to $address", 5);
1068 daemon_log("$session_id DEBUG: message:\n$msg", 12);
1070 }
1072 # close socket in any case
1073 if( $socket ) {
1074 close $socket;
1075 }
1077 if( $error > 0 ) { $new_status = "down"; }
1078 else { $new_status = $msg_header; }
1081 # known_clients
1082 $sql_statement = "SELECT * FROM $known_clients_tn WHERE hostname='$address'";
1083 $res = $known_clients_db->select_dbentry($sql_statement);
1084 if( keys(%$res) == 1) {
1085 $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
1086 if ($act_status eq "down" && $new_status eq "down") {
1087 $sql_statement = "DELETE FROM known_clients WHERE hostname='$address'";
1088 $res = $known_clients_db->del_dbentry($sql_statement);
1089 daemon_log("$session_id WARNING: failed 2x to send msg to host '$address', delete host from known_clients", 3);
1090 } else {
1091 $sql_statement = "UPDATE known_clients SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
1092 $res = $known_clients_db->update_dbentry($sql_statement);
1093 if($new_status eq "down"){
1094 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
1095 } else {
1096 daemon_log("$session_id DEBUG: set '$address' from status '$act_status' to '$new_status'", 138);
1097 }
1098 }
1099 }
1101 # known_server
1102 $sql_statement = "SELECT * FROM $known_server_tn WHERE hostname='$address'";
1103 $res = $known_server_db->select_dbentry($sql_statement);
1104 if( keys(%$res) == 1) {
1105 $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
1106 if ($act_status eq "down" && $new_status eq "down") {
1107 $sql_statement = "DELETE FROM known_server WHERE hostname='$address'";
1108 $res = $known_server_db->del_dbentry($sql_statement);
1109 daemon_log("$session_id WARNING: failed 2x to send a message to host '$address', delete host from known_server", 3);
1110 }
1111 else {
1112 $sql_statement = "UPDATE known_server SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
1113 $res = $known_server_db->update_dbentry($sql_statement);
1114 if($new_status eq "down"){
1115 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
1116 } else {
1117 daemon_log("$session_id DEBUG: set '$address' from status '$act_status' to '$new_status'", 138);
1118 }
1119 }
1120 }
1121 return $error;
1122 }
1125 sub update_jobdb_status_for_send_msgs {
1126 my ($session_id, $answer, $error) = @_;
1127 if( $answer =~ /<jobdb_id>(\d+)<\/jobdb_id>/ ) {
1128 &daemon_log("$session_id DEBUG: try to update job status", 138);
1129 my $jobdb_id = $1;
1131 $answer =~ /<header>(.*)<\/header>/;
1132 my $job_header = $1;
1134 $answer =~ /<target>(.*)<\/target>/;
1135 my $job_target = $1;
1137 # Sending msg failed
1138 if( $error ) {
1140 # Set jobs to done, jobs do not need to deliver their message in any case
1141 if (($job_header eq "trigger_action_localboot")
1142 ||($job_header eq "trigger_action_lock")
1143 ||($job_header eq "trigger_action_halt")
1144 ) {
1145 my $sql_statement = "UPDATE $job_queue_tn SET status='done' WHERE id=$jobdb_id";
1146 my $res = $job_db->update_dbentry($sql_statement);
1148 # Reactivate jobs, jobs need to deliver their message
1149 } elsif (($job_header eq "trigger_action_activate")
1150 ||($job_header eq "trigger_action_update")
1151 ||($job_header eq "trigger_action_reinstall")
1152 ||($job_header eq "trigger_activate_new")
1153 ) {
1154 if ($job_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) {
1155 &reactivate_job_with_delay($session_id, $job_target, $job_header, 30 );
1156 } else {
1157 # If we don't have the mac adress at this time, we use the plainname
1158 my $plainname_result = $job_db->select_dbentry("SELECT plainname from jobs where id=$jobdb_id");
1159 my $plainname = $job_target;
1160 if ((keys(%$plainname_result) > 0) ) {
1161 $plainname = $plainname_result->{1}->{$job_target};
1162 }
1163 &reactivate_job_with_delay($session_id, $plainname, $job_header, 30 );
1164 }
1165 # For all other messages
1166 } else {
1167 my $sql_statement = "UPDATE $job_queue_tn ".
1168 "SET status='error', result='can not deliver msg, please consult log file' ".
1169 "WHERE id=$jobdb_id";
1170 my $res = $job_db->update_dbentry($sql_statement);
1171 }
1173 # Sending msg was successful
1174 } else {
1175 # Set jobs localboot, lock, activate, halt, reboot and wake to done
1176 # jobs reinstall, update, inst_update do themself setting to done
1177 if (($job_header eq "trigger_action_localboot")
1178 ||($job_header eq "trigger_action_lock")
1179 ||($job_header eq "trigger_action_activate")
1180 ||($job_header eq "trigger_action_halt")
1181 ||($job_header eq "trigger_action_reboot")
1182 ||($job_header eq "trigger_action_wake")
1183 ||($job_header eq "trigger_wake")
1184 ) {
1186 my $sql_statement = "UPDATE $job_queue_tn ".
1187 "SET status='done' ".
1188 "WHERE id=$jobdb_id AND status='processed'";
1189 my $res = $job_db->update_dbentry($sql_statement);
1190 } else {
1191 &daemon_log("$session_id DEBUG: sending message succeed but cannot update job status.", 138);
1192 }
1193 }
1194 } else {
1195 &daemon_log("$session_id DEBUG: cannot update job status, msg has no jobdb_id-tag.", 138);
1196 }
1197 }
1199 sub reactivate_job_with_delay {
1200 my ($session_id, $target, $header, $delay) = @_ ;
1201 # 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
1203 if (not defined $delay) { $delay = 30 } ;
1204 my $delay_timestamp = &calc_timestamp(&get_time(), "plus", $delay);
1206 my $sql = "UPDATE $job_queue_tn Set timestamp='$delay_timestamp', status='waiting' WHERE (macaddress LIKE '$target' OR plainname LIKE '$target') AND headertag='$header'";
1207 my $res = $job_db->update_dbentry($sql);
1208 daemon_log("$session_id INFO: '$header'-job will be reactivated at '$delay_timestamp' ".
1209 "cause client '$target' is currently not available", 5);
1210 return;
1211 }
1214 sub sig_handler {
1215 my ($kernel, $signal) = @_[KERNEL, ARG0] ;
1216 daemon_log("0 INFO got signal '$signal'", 1);
1217 $kernel->sig_handled();
1218 return;
1219 }
1222 sub msg_to_decrypt {
1223 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
1224 my $session_id = $session->ID;
1225 my ($msg, $msg_hash, $module);
1226 my $error = 0;
1228 # fetch new msg out of @msgs_to_decrypt
1229 my $tmp_next_msg = shift @msgs_to_decrypt;
1230 my ($next_msg, $msg_source) = split(/;/, $tmp_next_msg);
1232 # msg is from a new client or gosa
1233 ($msg, $msg_hash, $module) = &input_from_unknown_host($next_msg, $session_id);
1235 # msg is from a gosa-si-server
1236 if(((!$msg) || (!$msg_hash) || (!$module)) && ($serverPackages_enabled eq "true")){
1237 if (not defined $msg_source)
1238 {
1239 # Only needed, to be compatible with older gosa-si-server versions
1240 ($msg, $msg_hash, $module) = &input_from_known_server($next_msg, $heap->{'remote_ip'}, $session_id);
1241 }
1242 else
1243 {
1244 ($msg, $msg_hash, $module) = &input_from_known_server($next_msg, $msg_source, $session_id);
1245 }
1246 }
1247 # msg is from a gosa-si-client
1248 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1249 if (not defined $msg_source)
1250 {
1251 # Only needed, to be compatible with older gosa-si-server versions
1252 ($msg, $msg_hash, $module) = &input_from_known_client($next_msg, $heap->{'remote_ip'}, $session_id);
1253 }
1254 else
1255 {
1256 ($msg, $msg_hash, $module) = &input_from_known_client($next_msg, $msg_source, $session_id);
1257 }
1258 }
1259 # an error occurred
1260 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1261 # If an incoming msg could not be decrypted (maybe a wrong key), decide if msg comes from a client
1262 # or a server. In case of a client, send a ping. If the client could not understand a msg from its
1263 # server the client cause a re-registering process. In case of a server, decrease update_time in kown_server_db
1264 # and trigger a re-registering process for servers
1265 if (defined $msg_source && $msg_source =~ /:$server_port$/ && $serverPackages_enabled eq "true")
1266 {
1267 daemon_log("$session_id WARNING: Cannot understand incoming msg from server '$msg_source'. Cause re-registration process for servers.", 3);
1268 my $update_statement = "UPDATE $known_server_tn SET update_time='19700101000000' WHERE hostname='$msg_source'";
1269 daemon_log("$session_id DEBUG: $update_statement", 7);
1270 my $upadte_res = $known_server_db->exec_statement($update_statement);
1271 $kernel->yield("register_at_foreign_servers");
1272 }
1273 elsif ((defined $msg_source) && (not $msg_source =~ /:$server_port$/))
1274 {
1275 daemon_log("$session_id WARNING: Cannot understand incoming msg from client '$msg_source'. Send ping-msg to cause a re-registering of the client if necessary", 3);
1276 #my $remote_ip = $heap->{'remote_ip'};
1277 #my $remote_port = $heap->{'remote_port'};
1278 my $ping_msg = "<xml> <header>gosa_ping</header> <source>$server_address</source><target>$msg_source</target></xml>";
1279 my ($test_error, $test_error_string) = &send_msg_to_target($ping_msg, "$msg_source", "dummy-key", "gosa_ping", $session_id);
1280 daemon_log("$session_id WARNING: Sending msg to cause re-registering: $ping_msg", 3);
1281 }
1282 else
1283 {
1284 my $foreign_host = defined $msg_source ? $msg_source : $heap->{'remote_ip'};
1285 daemon_log("$session_id ERROR: Incoming message from host '$foreign_host' cannot be understood. Processing aborted!", 1);
1286 daemon_log("$session_id DEBUG: Aborted message: $tmp_next_msg", 11);
1287 }
1289 $error++
1290 }
1293 my $header;
1294 my $target;
1295 my $source;
1296 my $done = 0;
1297 my $sql;
1298 my $res;
1300 # check whether this message should be processed here
1301 if ($error == 0) {
1302 $header = @{$msg_hash->{'header'}}[0];
1303 $target = @{$msg_hash->{'target'}}[0];
1304 $source = @{$msg_hash->{'source'}}[0];
1305 my $not_found_in_known_clients_db = 0;
1306 my $not_found_in_known_server_db = 0;
1307 my $not_found_in_foreign_clients_db = 0;
1308 my $local_address;
1309 my $local_mac;
1310 my ($target_ip, $target_port) = split(':', $target);
1312 # Determine the local ip address if target is an ip address
1313 if ($target =~ /^\d+\.\d+\.\d+\.\d+:\d+$/) {
1314 $local_address = &get_local_ip_for_remote_ip($target_ip).":$server_port";
1315 } else {
1316 $local_address = $server_address;
1317 }
1319 # Determine the local mac address if target is a mac address
1320 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) {
1321 my $loc_ip = &get_local_ip_for_remote_ip($heap->{'remote_ip'});
1322 my $network_interface= &get_interface_for_ip($loc_ip);
1323 $local_mac = &get_mac_for_interface($network_interface);
1324 } else {
1325 $local_mac = $server_mac_address;
1326 }
1328 # target and source is equal to GOSA -> process here
1329 if (not $done) {
1330 if ($target eq "GOSA" && $source eq "GOSA") {
1331 $done = 1;
1332 &daemon_log("$session_id DEBUG: target and source is 'GOSA' -> process '$header' here", 11);
1333 }
1334 }
1336 # target is own address without forward_to_gosa-tag -> process here
1337 if (not $done) {
1338 #if ((($target eq $local_address) || ($target eq $local_mac) ) && (not exists $msg_hash->{'forward_to_gosa'})) {
1339 if (($target eq $local_address) && (not exists $msg_hash->{'forward_to_gosa'})) {
1340 $done = 1;
1341 if ($source eq "GOSA") {
1342 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1343 }
1344 &daemon_log("$session_id DEBUG: target is own address without forward_to_gosa-tag -> process '$header' here", 11);
1345 }
1346 }
1348 # target is own address with forward_to_gosa-tag not pointing to myself -> process here
1349 if (not $done) {
1350 my $forward_to_gosa = @{$msg_hash->{'forward_to_gosa'}}[0];
1351 my $gosa_at;
1352 my $gosa_session_id;
1353 if (($target eq $local_address) && (defined $forward_to_gosa)){
1354 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1355 if ($gosa_at ne $local_address) {
1356 $done = 1;
1357 &daemon_log("$session_id DEBUG: target is own address with forward_to_gosa-tag not pointing to myself -> process '$header' here", 11);
1358 }
1359 }
1360 }
1362 # Target is a client address and there is a processing function within a plugin -> process loaclly
1363 if (not $done)
1364 {
1365 # Check if target is a client address
1366 $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1367 $res = $known_clients_db->select_dbentry($sql);
1368 if ((keys(%$res) > 0) )
1369 {
1370 my $hostname = $res->{1}->{'hostname'};
1371 my $reduced_header = $header;
1372 $reduced_header =~ s/gosa_//;
1373 # Check if there is a processing function within a plugin
1374 if (exists $known_functions->{$reduced_header})
1375 {
1376 $msg =~ s/<target>\S*<\/target>/<target>$hostname<\/target>/;
1377 $done = 1;
1378 &daemon_log("$session_id DEBUG: Target is client address with processing function within a plugin -> process '$header' here", 11);
1379 }
1380 }
1381 }
1383 # If header has a 'job_' prefix, do always process message locally
1384 # which means put it into job queue
1385 if ((not $done) && ($header =~ /job_/))
1386 {
1387 $done = 1;
1388 &daemon_log("$session_id DEBUG: Header has a 'job_' prefix. Put it into job queue. -> process '$header' here", 11);
1389 }
1391 # if message should be processed here -> add message to incoming_db
1392 if ($done) {
1393 # if a 'job_' or a 'gosa_' message comes from a foreign server, fake module from
1394 # ServerPackages to GosaPackages so gosa-si-server knows how to process this kind of messages
1395 if ($header =~ /^gosa_/ || $header =~ /^job_/) {
1396 $module = "GosaPackages";
1397 }
1399 my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1400 primkey=>[],
1401 headertag=>$header,
1402 targettag=>$target,
1403 xmlmessage=>&encode_base64($msg),
1404 timestamp=>&get_time,
1405 module=>$module,
1406 sessionid=>$session_id,
1407 } );
1408 $kernel->yield('watch_for_next_tasks');
1409 }
1411 # target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa
1412 if (not $done) {
1413 my $forward_to_gosa = @{$msg_hash->{'forward_to_gosa'}}[0];
1414 my $gosa_at;
1415 my $gosa_session_id;
1416 if (($target eq $local_address) && (defined $forward_to_gosa)){
1417 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1418 if ($gosa_at eq $local_address) {
1419 my $session_reference = $kernel->ID_id_to_session($gosa_session_id);
1420 if( defined $session_reference ) {
1421 $heap = $session_reference->get_heap();
1422 }
1423 if(exists $heap->{'client'}) {
1424 $msg = &encrypt_msg($msg, $GosaPackages_key);
1425 $heap->{'client'}->put($msg);
1426 &daemon_log("$session_id DEBUG: incoming '$header' message forwarded to GOsa", 11);
1427 }
1428 $done = 1;
1429 &daemon_log("$session_id DEBUG: target is own address with forward_to_gosa-tag pointing at myself -> forward '$header' to gosa", 11);
1430 }
1431 }
1433 }
1435 # target is a client address in known_clients -> forward to client
1436 if (not $done) {
1437 $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1438 $res = $known_clients_db->select_dbentry($sql);
1439 if (keys(%$res) > 0)
1440 {
1441 $done = 1;
1442 &daemon_log("$session_id DEBUG: target is a client address in known_clients -> forward '$header' to client", 11);
1443 my $hostkey = $res->{1}->{'hostkey'};
1444 my $hostname = $res->{1}->{'hostname'};
1445 $msg =~ s/<target>\S*<\/target>/<target>$hostname<\/target>/;
1446 $msg =~ s/<header>gosa_/<header>/;
1447 my $error= &send_msg_to_target($msg, $hostname, $hostkey, $header, $session_id);
1448 if ($error) {
1449 &daemon_log("$session_id ERROR: Some problems occurred while trying to send msg to client '$hostname': $msg", 1);
1450 }
1451 }
1452 else
1453 {
1454 $not_found_in_known_clients_db = 1;
1455 }
1456 }
1458 # target is a client address in foreign_clients -> forward to registration server
1459 if (not $done) {
1460 $sql = "SELECT * FROM $foreign_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1461 $res = $foreign_clients_db->select_dbentry($sql);
1462 if (keys(%$res) > 0) {
1463 my $hostname = $res->{1}->{'hostname'};
1464 my ($host_ip, $host_port) = split(/:/, $hostname);
1465 my $local_address = &get_local_ip_for_remote_ip($host_ip).":$server_port";
1466 my $regserver = $res->{1}->{'regserver'};
1467 my $sql = "SELECT * FROM $known_server_tn WHERE hostname='$regserver'";
1468 my $res = $known_server_db->select_dbentry($sql);
1469 if (keys(%$res) > 0) {
1470 my $regserver_key = $res->{1}->{'hostkey'};
1471 $msg =~ s/<source>GOSA<\/source>/<source>$local_address<\/source>/;
1472 $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1473 if ($source eq "GOSA") {
1474 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1475 }
1476 my $error= &send_msg_to_target($msg, $regserver, $regserver_key, $header, $session_id);
1477 if ($error) {
1478 &daemon_log("$session_id ERROR: some problems occurred while trying to send msg to registration server: $msg", 1);
1479 }
1480 }
1481 $done = 1;
1482 &daemon_log("$session_id DEBUG: target is a client address in foreign_clients -> forward '$header' to registration server", 11);
1483 } else {
1484 $not_found_in_foreign_clients_db = 1;
1485 }
1486 }
1488 # target is a server address -> forward to server
1489 if (not $done) {
1490 $sql = "SELECT * FROM $known_server_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1491 $res = $known_server_db->select_dbentry($sql);
1492 if (keys(%$res) > 0) {
1493 my $hostkey = $res->{1}->{'hostkey'};
1495 if ($source eq "GOSA") {
1496 $msg =~ s/<source>GOSA<\/source>/<source>$local_address<\/source>/;
1497 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1499 }
1501 &send_msg_to_target($msg, $target, $hostkey, $header, $session_id);
1502 $done = 1;
1503 &daemon_log("$session_id DEBUG: target is a server address -> forward '$header' to server", 11);
1504 } else {
1505 $not_found_in_known_server_db = 1;
1506 }
1507 }
1510 # target is not in foreign_clients_db, known_server_db or known_clients_db, maybe it is a complete new one -> process here
1511 if ( $not_found_in_foreign_clients_db
1512 && $not_found_in_known_server_db
1513 && $not_found_in_known_clients_db) {
1514 &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 '$header' here", 11);
1515 if ($header =~ /^gosa_/ || $header =~ /^job_/) {
1516 $module = "GosaPackages";
1517 }
1518 my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1519 primkey=>[],
1520 headertag=>$header,
1521 targettag=>$target,
1522 xmlmessage=>&encode_base64($msg),
1523 timestamp=>&get_time,
1524 module=>$module,
1525 sessionid=>$session_id,
1526 } );
1527 $done = 1;
1528 $kernel->yield('watch_for_next_tasks');
1529 }
1532 if (not $done) {
1533 daemon_log("$session_id ERROR: do not know what to do with this message: $msg", 1);
1534 if ($source eq "GOSA") {
1535 my %data = ('error_msg' => &encode_base64($msg), 'error_string' => "Do not know what to do with this message!");
1536 my $error_msg = &build_msg("error", $local_address, "GOSA", \%data );
1538 my $session_reference = $kernel->ID_id_to_session($session_id);
1539 if( defined $session_reference ) {
1540 $heap = $session_reference->get_heap();
1541 }
1542 if(exists $heap->{'client'}) {
1543 $error_msg = &encrypt_msg($error_msg, $GosaPackages_key);
1544 $heap->{'client'}->put($error_msg);
1545 }
1546 }
1547 }
1549 }
1551 return;
1552 }
1555 sub next_task {
1556 my ($session, $heap, $task) = @_[SESSION, HEAP, ARG0, ARG1];
1557 my $running_task = POE::Wheel::Run->new(
1558 Program => sub { process_task($session, $heap, $task) },
1559 StdioFilter => POE::Filter::Reference->new(),
1560 StdoutEvent => "task_result",
1561 StderrEvent => "task_debug",
1562 CloseEvent => "task_done",
1563 );
1564 $heap->{task}->{ $running_task->ID } = $running_task;
1565 }
1567 sub handle_task_result {
1568 my ($kernel, $heap, $result) = @_[KERNEL, HEAP, ARG0];
1569 my $client_answer = $result->{'answer'};
1570 if( $client_answer =~ s/session_id=(\d+)$// ) {
1571 my $session_id = $1;
1572 if( defined $session_id ) {
1573 my $session_reference = $kernel->ID_id_to_session($session_id);
1574 if( defined $session_reference ) {
1575 $heap = $session_reference->get_heap();
1576 }
1577 }
1579 if(exists $heap->{'client'}) {
1580 $heap->{'client'}->put($client_answer);
1581 }
1582 }
1583 $kernel->sig(CHLD => "child_reap");
1584 }
1586 sub handle_task_debug {
1587 my $result = $_[ARG0];
1588 print STDERR "$result\n";
1589 }
1591 sub handle_task_done {
1592 my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1593 delete $heap->{task}->{$task_id};
1594 if (exists $heap->{ldap_handle}->{$task_id}) {
1595 &release_ldap_handle($heap->{ldap_handle}->{$task_id});
1596 }
1597 }
1599 sub process_task {
1600 no strict "refs";
1601 #CHECK: Not @_[...]?
1602 my ($session, $heap, $task) = @_;
1603 my $error = 0;
1604 my $answer_l;
1605 my ($answer_header, @answer_target_l, $answer_source);
1606 my $client_answer = "";
1608 # prepare all variables needed to process message
1609 #my $msg = $task->{'xmlmessage'};
1610 my $msg = &decode_base64($task->{'xmlmessage'});
1611 my $incoming_id = $task->{'id'};
1612 my $module = $task->{'module'};
1613 my $header = $task->{'headertag'};
1614 my $session_id = $task->{'sessionid'};
1615 my $msg_hash;
1616 eval {
1617 $msg_hash = $xml->XMLin($msg, ForceArray=>1);
1618 };
1619 daemon_log("ERROR: XML failure '$@'") if ($@);
1620 my $source = @{$msg_hash->{'source'}}[0];
1622 # set timestamp of incoming client uptodate, so client will not
1623 # be deleted from known_clients because of expiration
1624 my $cur_time = &get_time();
1625 my $sql = "UPDATE $known_clients_tn SET timestamp='$cur_time' WHERE hostname='$source'";
1626 my $res = $known_clients_db->exec_statement($sql);
1628 ######################
1629 # process incoming msg
1630 if( $error == 0) {
1631 daemon_log("$session_id INFO: Incoming msg (session_id=$session_id) with header '".@{$msg_hash->{'header'}}[0]."'", 5);
1632 daemon_log("$session_id DEBUG: Processing module ".$module, 26);
1633 $answer_l = &{ $module."::process_incoming_msg" }($msg, $msg_hash, $session_id);
1635 if ( 0 < @{$answer_l} ) {
1636 my $answer_str = join("\n", @{$answer_l});
1637 my @headers;
1638 while ($answer_str =~ /<header>(\w+)<\/header>/g) {
1639 push(@headers, $1);
1640 }
1641 daemon_log("$session_id DEBUG: got answer message(s) with header: '".join("', '", @headers)."'", 26);
1642 } else {
1643 daemon_log("$session_id DEBUG: $module: got no answer from module!" ,26);
1644 }
1646 }
1647 if( !$answer_l ) { $error++ };
1649 ########
1650 # answer
1651 if( $error == 0 ) {
1653 foreach my $answer ( @{$answer_l} ) {
1654 # check outgoing msg to xml validity
1655 my ($answer, $answer_hash) = &check_outgoing_xml_validity($answer, $session_id);
1656 if( not defined $answer_hash ) { next; }
1658 $answer_header = @{$answer_hash->{'header'}}[0];
1659 @answer_target_l = @{$answer_hash->{'target'}};
1660 $answer_source = @{$answer_hash->{'source'}}[0];
1662 # deliver msg to all targets
1663 foreach my $answer_target ( @answer_target_l ) {
1665 # targets of msg are all gosa-si-clients in known_clients_db
1666 if( $answer_target eq "*" ) {
1667 # answer is for all clients
1668 my $sql_statement= "SELECT * FROM known_clients";
1669 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1670 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1671 my $host_name = $hit->{hostname};
1672 my $host_key = $hit->{hostkey};
1673 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1674 &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1675 }
1676 }
1678 # targets of msg are all gosa-si-server in known_server_db
1679 elsif( $answer_target eq "KNOWN_SERVER" ) {
1680 # answer is for all server in known_server
1681 my $sql_statement= "SELECT * FROM $known_server_tn";
1682 my $query_res = $known_server_db->select_dbentry( $sql_statement );
1683 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1684 my $host_name = $hit->{hostname};
1685 my $host_key = $hit->{hostkey};
1686 $answer =~ s/<target>\S+<\/target>/<target>$host_name<\/target>/g;
1687 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1688 &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1689 }
1690 }
1692 # target of msg is GOsa
1693 elsif( $answer_target eq "GOSA" ) {
1694 my $session_id = ($1) if $answer =~ /<session_id>(\d+?)<\/session_id>/;
1695 my $add_on = "";
1696 if( defined $session_id ) {
1697 $add_on = ".session_id=$session_id";
1698 }
1699 my $header = ($1) if $answer =~ /<header>(\S*)<\/header>/;
1700 daemon_log("$session_id INFO: send ".$header." message to GOsa", 5);
1701 daemon_log("$session_id DEBUG: message:\n$answer", 12);
1702 # answer is for GOSA and has to returned to connected client
1703 my $gosa_answer = &encrypt_msg($answer, $GosaPackages_key);
1704 $client_answer = $gosa_answer.$add_on;
1705 }
1707 # target of msg is job queue at this host
1708 elsif( $answer_target eq "JOBDB") {
1709 $answer =~ /<header>(\S+)<\/header>/;
1710 my $header;
1711 if( defined $1 ) { $header = $1; }
1712 my $error = &send_msg_to_target($answer, $server_address, $GosaPackages_key, $header, $session_id);
1713 &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1714 }
1716 # Target of msg is a mac address
1717 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 ) {
1718 daemon_log("$session_id DEBUG: target is mac address '$answer_target', looking for host in known_clients and foreign_clients", 138);
1720 # Looking for macaddress in known_clients
1721 my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$answer_target'";
1722 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1723 my $found_ip_flag = 0;
1724 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1725 my $host_name = $hit->{hostname};
1726 my $host_key = $hit->{hostkey};
1727 $answer =~ s/$answer_target/$host_name/g;
1728 daemon_log("$session_id INFO: found host '$host_name', associated to '$answer_target'", 5);
1729 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1730 &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1731 $found_ip_flag++ ;
1732 }
1734 # Looking for macaddress in foreign_clients
1735 if ($found_ip_flag == 0) {
1736 my $sql = "SELECT * FROM $foreign_clients_tn WHERE macaddress LIKE '$answer_target'";
1737 my $res = $foreign_clients_db->select_dbentry($sql);
1738 while( my ($hit_num, $hit) = each %{ $res } ) {
1739 my $host_name = $hit->{hostname};
1740 my $reg_server = $hit->{regserver};
1741 daemon_log("$session_id INFO: found host '$host_name' with mac '$answer_target', registered at '$reg_server'", 5);
1743 # Fetch key for reg_server
1744 my $reg_server_key;
1745 my $sql = "SELECT * FROM $known_server_tn WHERE hostname='$reg_server'";
1746 my $res = $known_server_db->select_dbentry($sql);
1747 if (exists $res->{1}) {
1748 $reg_server_key = $res->{1}->{'hostkey'};
1749 } else {
1750 daemon_log("$session_id ERROR: cannot find hostkey for '$host_name' in '$known_server_tn'", 1);
1751 daemon_log("$session_id ERROR: unable to forward answer to correct registration server, processing is aborted!", 1);
1752 $reg_server_key = undef;
1753 }
1755 # Send answer to server where client is registered
1756 if (defined $reg_server_key) {
1757 $answer =~ s/$answer_target/$host_name/g;
1758 my $error = &send_msg_to_target($answer, $reg_server, $reg_server_key, $answer_header, $session_id);
1759 &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1760 $found_ip_flag++ ;
1761 }
1762 }
1763 }
1765 # No mac to ip matching found
1766 if( $found_ip_flag == 0) {
1767 daemon_log("$session_id WARNING: no host found in known_clients or foreign_clients with mac address '$answer_target'", 3);
1768 &reactivate_job_with_delay($session_id, $answer_target, $answer_header, 30);
1769 }
1771 # Answer is for one specific host
1772 } else {
1773 # get encrypt_key
1774 my $encrypt_key = &get_encrypt_key($answer_target);
1775 if( not defined $encrypt_key ) {
1776 # unknown target
1777 daemon_log("$session_id WARNING: unknown target '$answer_target'", 3);
1778 next;
1779 }
1780 my $error = &send_msg_to_target($answer, $answer_target, $encrypt_key, $answer_header,$session_id);
1781 &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1782 }
1783 }
1784 }
1785 }
1787 my $filter = POE::Filter::Reference->new();
1788 my %result = (
1789 status => "seems ok to me",
1790 answer => $client_answer,
1791 );
1793 my $output = $filter->put( [ \%result ] );
1794 print @$output;
1797 }
1799 sub session_start {
1800 my ($kernel) = $_[KERNEL];
1801 $global_kernel = $kernel;
1802 $kernel->yield('register_at_foreign_servers');
1803 $kernel->yield('create_fai_server_db', $fai_server_tn );
1804 $kernel->yield('create_fai_release_db', $fai_release_tn );
1805 $kernel->sig(USR1 => "sig_handler");
1806 $kernel->sig(USR2 => "recreate_packages_db");
1807 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1808 $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay);
1809 $kernel->delay_set('watch_for_modified_jobs', $modified_jobs_loop_delay);
1810 $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1811 $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1812 $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1813 $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1815 # Start opsi check
1816 if ($opsi_enabled eq "true") {
1817 $kernel->delay_set('watch_for_opsi_jobs', $job_queue_opsi_delay);
1818 }
1820 }
1823 sub watch_for_done_jobs {
1824 my $kernel = $_[KERNEL];
1826 my $sql_statement = "SELECT * FROM ".$job_queue_tn." WHERE ((status='done') AND (modified='0'))";
1827 my $res = $job_db->select_dbentry( $sql_statement );
1829 while( my ($number, $hit) = each %{$res} )
1830 {
1831 # Non periodical jobs can be deleted.
1832 if ($hit->{periodic} eq "none")
1833 {
1834 my $jobdb_id = $hit->{id};
1835 my $sql_statement = "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1836 my $res = $job_db->del_dbentry($sql_statement);
1837 }
1839 # Periodical jobs should not be deleted but reactivated with new timestamp instead.
1840 else
1841 {
1842 my ($p_time, $periodic) = split("_", $hit->{periodic});
1843 my $reactivated_ts = $hit->{timestamp};
1844 my $act_ts = int(&get_time());
1845 while ($act_ts > int($reactivated_ts)) # Redo calculation to avoid multiple jobs in the past
1846 {
1847 $reactivated_ts = &calc_timestamp($reactivated_ts, "plus", $p_time, $periodic);
1848 }
1849 my $sql = "UPDATE $job_queue_tn SET status='waiting', timestamp='$reactivated_ts' WHERE id='".$hit->{id}."'";
1850 my $res = $job_db->exec_statement($sql);
1851 &daemon_log("J INFO: Update periodical job '".$hit->{headertag}."' for client '".$hit->{targettag}."'. New execution time '$reactivated_ts'.", 5);
1852 }
1853 }
1855 $kernel->delay_set('watch_for_done_jobs',$job_queue_loop_delay);
1856 }
1859 sub watch_for_opsi_jobs {
1860 my ($kernel) = $_[KERNEL];
1861 # 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
1862 # opsi install job is to parse the xml message. There is still the correct header.
1863 my $sql_statement = "SELECT * FROM ".$job_queue_tn." WHERE ((xmlmessage LIKE '%opsi_install_client</header>%') AND (status='processing') AND (siserver='localhost'))";
1864 my $res = $job_db->select_dbentry( $sql_statement );
1866 # Ask OPSI for an update of the running jobs
1867 while (my ($id, $hit) = each %$res ) {
1868 # Determine current parameters of the job
1869 my $hostId = $hit->{'plainname'};
1870 my $macaddress = $hit->{'macaddress'};
1871 my $progress = $hit->{'progress'};
1873 my $result= {};
1875 # For hosts, only return the products that are or get installed
1876 my $callobj;
1877 $callobj = {
1878 method => 'getProductStates_hash',
1879 params => [ $hostId ],
1880 id => 1,
1881 };
1883 my $hres = $opsi_client->call($opsi_url, $callobj);
1884 # TODO : move all opsi relevant statments to opsi plugin
1885 # The following 3 lines must be tested befor they can replace the rubbish above and below !!!
1886 #my ($res, $err) = &opsi_com::_getProductStates_hash(hostId=>$hostId)
1887 #if (not $err) {
1888 # my $htmp = $res->{$hostId};
1889 #
1890 if (not &check_opsi_res($hres)) {
1891 my $htmp= $hres->result->{$hostId};
1892 # Check state != not_installed or action == setup -> load and add
1893 my $products= 0;
1894 my $installed= 0;
1895 my $installing = 0;
1896 my $error= 0;
1897 my @installed_list;
1898 my @error_list;
1899 my $act_status = "none";
1900 foreach my $product (@{$htmp}){
1902 if ($product->{'installationStatus'} ne "not_installed" or
1903 $product->{'actionRequest'} eq "setup"){
1905 # Increase number of products for this host
1906 $products++;
1908 if ($product->{'installationStatus'} eq "failed"){
1909 $result->{$product->{'productId'}}= "error";
1910 unshift(@error_list, $product->{'productId'});
1911 $error++;
1912 }
1913 if ($product->{'installationStatus'} eq "installed" && $product->{'actionRequest'} eq "none"){
1914 $result->{$product->{'productId'}}= "installed";
1915 unshift(@installed_list, $product->{'productId'});
1916 $installed++;
1917 }
1918 if ($product->{'installationStatus'} eq "installing"){
1919 $result->{$product->{'productId'}}= "installing";
1920 $installing++;
1921 $act_status = "installing - ".$product->{'productId'};
1922 }
1923 }
1924 }
1926 # Estimate "rough" progress, avoid division by zero
1927 if ($products == 0) {
1928 $result->{'progress'}= 0;
1929 } else {
1930 $result->{'progress'}= int($installed * 100 / $products);
1931 }
1933 # Set updates in job queue
1934 if ((not $error) && (not $installing) && ($installed)) {
1935 $act_status = "installed - ".join(", ", @installed_list);
1936 }
1937 if ($error) {
1938 $act_status = "error - ".join(", ", @error_list);
1939 }
1940 if ($progress ne $result->{'progress'} ) {
1941 # Updating progress and result
1942 my $update_statement = "UPDATE $job_queue_tn SET modified='1', progress='".$result->{'progress'}."', result='$act_status' WHERE macaddress LIKE '$macaddress' AND siserver='localhost'";
1943 my $update_res = $job_db->update_dbentry($update_statement);
1944 }
1945 if ($progress eq 100) {
1946 # Updateing status
1947 my $done_statement = "UPDATE $job_queue_tn SET modified='1', ";
1948 if ($error) {
1949 $done_statement .= "status='error'";
1950 } else {
1951 $done_statement .= "status='done'";
1952 }
1953 $done_statement .= " WHERE macaddress LIKE '$macaddress' AND siserver='localhost'";
1954 my $done_res = $job_db->update_dbentry($done_statement);
1955 }
1958 }
1959 }
1961 $kernel->delay_set('watch_for_opsi_jobs', $job_queue_opsi_delay);
1962 }
1965 # If a job got an update or was modified anyway, send to all other si-server an update message of this jobs.
1966 sub watch_for_modified_jobs {
1967 my ($kernel,$heap) = @_[KERNEL, HEAP];
1969 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE (modified='1')";
1970 my $res = $job_db->select_dbentry( $sql_statement );
1972 # if db contains no jobs which should be update, do nothing
1973 if (keys %$res != 0) {
1975 if ($job_synchronization eq "true") {
1976 # make out of the db result a gosa-si message
1977 my $update_msg = &db_res2si_msg ($res, "foreign_job_updates", "KNOWN_SERVER", "MY_LOCAL_ADDRESS");
1979 # update all other SI-server
1980 &inform_all_other_si_server($update_msg);
1981 }
1983 # set jobs all jobs to modified = 0, wait until the next modification for updates of other si-server
1984 $sql_statement = "UPDATE $job_queue_tn SET modified='0' ";
1985 $res = $job_db->update_dbentry($sql_statement);
1986 }
1988 $kernel->delay_set('watch_for_modified_jobs', $modified_jobs_loop_delay);
1989 }
1992 sub watch_for_new_jobs {
1993 if($watch_for_new_jobs_in_progress == 0) {
1994 $watch_for_new_jobs_in_progress = 1;
1995 my ($kernel,$heap) = @_[KERNEL, HEAP];
1997 # check gosa job queue for jobs with executable timestamp
1998 my $timestamp = &get_time();
1999 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE siserver='localhost' AND status='waiting' AND (CAST(timestamp AS UNSIGNED)) < $timestamp ORDER BY timestamp";
2000 my $res = $job_db->exec_statement( $sql_statement );
2002 # Merge all new jobs that would do the same actions
2003 my @drops;
2004 my $hits;
2005 foreach my $hit (reverse @{$res} ) {
2006 my $macaddress= lc @{$hit}[8];
2007 my $headertag= @{$hit}[5];
2008 if(
2009 defined($hits->{$macaddress}) &&
2010 defined($hits->{$macaddress}->{$headertag}) &&
2011 defined($hits->{$macaddress}->{$headertag}[0])
2012 ) {
2013 push @drops, "DELETE FROM $job_queue_tn WHERE id = $hits->{$macaddress}->{$headertag}[0]";
2014 }
2015 $hits->{$macaddress}->{$headertag}= $hit;
2016 }
2018 # Delete new jobs with a matching job in state 'processing'
2019 foreach my $macaddress (keys %{$hits}) {
2020 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
2021 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
2022 if(defined($jobdb_id)) {
2023 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND headertag='$jobdb_headertag' AND status='processing'";
2024 my $res = $job_db->exec_statement( $sql_statement );
2025 foreach my $hit (@{$res}) {
2026 push @drops, "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
2027 }
2028 } else {
2029 daemon_log("J ERROR: Job without id exists for macaddress $macaddress!", 1);
2030 }
2031 }
2032 }
2034 # Commit deletion
2035 $job_db->exec_statementlist(\@drops);
2037 # Look for new jobs that could be executed
2038 foreach my $macaddress (keys %{$hits}) {
2040 # Look if there is an executing job
2041 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='processing'";
2042 my $res = $job_db->exec_statement( $sql_statement );
2044 # Skip new jobs for host if there is a processing job
2045 if(defined($res) and defined @{$res}[0]) {
2046 # Prevent race condition if there is a trigger_activate job waiting and a goto-activation job processing
2047 my $row = @{$res}[0] if (ref $res eq 'ARRAY');
2048 if(@{$row}[5] eq 'trigger_action_reinstall') {
2049 my $sql_statement_2 = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='waiting' AND headertag = 'trigger_activate_new'";
2050 my $res_2 = $job_db->exec_statement( $sql_statement_2 );
2051 if(defined($res_2) and defined @{$res_2}[0]) {
2052 # Set status from goto-activation to 'waiting' and update timestamp
2053 $job_db->exec_statement("UPDATE $job_queue_tn SET status='waiting', timestamp='".&calc_timestamp(&get_time(), 'plus', 30)."' WHERE macaddress LIKE '$macaddress' AND headertag = 'trigger_action_reinstall'");
2054 }
2055 }
2056 next;
2057 }
2059 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
2060 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
2061 if(defined($jobdb_id)) {
2062 my $job_msg = @{$hits->{$macaddress}->{$jobdb_headertag}}[7];
2064 daemon_log("J DEBUG: its time to execute $job_msg", 7);
2065 my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$macaddress'";
2066 my $res_hash = $known_clients_db->select_dbentry( $sql_statement );
2068 # expect macaddress is unique!!!!!!
2069 my $target = $res_hash->{1}->{hostname};
2071 # change header
2072 $job_msg =~ s/<header>job_/<header>gosa_/;
2074 # add sqlite_id
2075 $job_msg =~ s/<\/xml>$/<jobdb_id>$jobdb_id<\/jobdb_id><\/xml>/;
2077 $job_msg =~ /<header>(\S+)<\/header>/;
2078 my $header = $1 ;
2079 my $func_error = &send_msg_to_target($job_msg, $server_address, $GosaPackages_key, $header, "J");
2081 # update status in job queue to ...
2082 # ... 'processing', for jobs: 'reinstall', 'update', activate_new
2083 if (($header =~ /gosa_trigger_action_reinstall/)
2084 || ($header =~ /gosa_trigger_activate_new/)
2085 || ($header =~ /gosa_trigger_action_update/)) {
2086 my $sql_statement = "UPDATE $job_queue_tn SET status='processing' WHERE id=$jobdb_id";
2087 my $dbres = $job_db->update_dbentry($sql_statement);
2088 }
2090 # ... 'done', for all other jobs, they are no longer needed in the jobqueue
2091 else {
2092 my $sql_statement = "UPDATE $job_queue_tn SET status='done' WHERE id=$jobdb_id";
2093 my $dbres = $job_db->exec_statement($sql_statement);
2094 }
2097 # We don't want parallel processing
2098 last;
2099 }
2100 }
2101 }
2103 $watch_for_new_jobs_in_progress = 0;
2104 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
2105 }
2106 }
2109 sub watch_for_new_messages {
2110 my ($kernel,$heap) = @_[KERNEL, HEAP];
2111 my @coll_user_msg; # collection list of outgoing messages
2113 # check messaging_db for new incoming messages with executable timestamp
2114 my $timestamp = &get_time();
2115 my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( (CAST(timestamp AS UNSIGNED))<$timestamp AND flag='n' AND direction='in' )";
2116 my $res = $messaging_db->exec_statement( $sql_statement );
2117 foreach my $hit (@{$res}) {
2119 # create outgoing messages
2120 my $message_to = @{$hit}[3];
2121 # translate message_to to plain login name
2122 my @message_to_l = split(/,/, $message_to);
2123 my %receiver_h;
2124 foreach my $receiver (@message_to_l) {
2125 if ($receiver =~ /^u_([\s\S]*)$/) {
2126 $receiver_h{$1} = 0;
2127 } elsif ($receiver =~ /^g_([\s\S]*)$/) {
2128 my $group_name = $1;
2129 # fetch all group members from ldap and add them to receiver hash
2130 my $ldap_handle = &get_ldap_handle();
2131 if (defined $ldap_handle) {
2132 my $mesg = $ldap_handle->search(
2133 base => $ldap_base,
2134 scope => 'sub',
2135 attrs => ['memberUid'],
2136 filter => "cn=$group_name",
2137 );
2138 if ($mesg->count) {
2139 my @entries = $mesg->entries;
2140 foreach my $entry (@entries) {
2141 my @receivers= $entry->get_value("memberUid");
2142 foreach my $receiver (@receivers) {
2143 $receiver_h{$receiver} = 0;
2144 }
2145 }
2146 }
2147 # translating errors ?
2148 if ($mesg->code) {
2149 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: $mesg->error", 1);
2150 }
2151 &release_ldap_handle($ldap_handle);
2152 # ldap handle error ?
2153 } else {
2154 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: no ldap handle available", 1);
2155 }
2156 } else {
2157 my $sbjct = &encode_base64(@{$hit}[1]);
2158 my $msg = &encode_base64(@{$hit}[7]);
2159 &daemon_log("M WARNING: unknown receiver '$receiver' for a user-message '$sbjct - $msg'", 3);
2160 }
2161 }
2162 my @receiver_l = keys(%receiver_h);
2164 my $message_id = @{$hit}[0];
2166 #add each outgoing msg to messaging_db
2167 my $receiver;
2168 foreach $receiver (@receiver_l) {
2169 my $sql_statement = "INSERT INTO $messaging_tn (id, subject, message_from, message_to, flag, direction, delivery_time, message, timestamp) ".
2170 "VALUES ('".
2171 $message_id."', '". # id
2172 @{$hit}[1]."', '". # subject
2173 @{$hit}[2]."', '". # message_from
2174 $receiver."', '". # message_to
2175 "none"."', '". # flag
2176 "out"."', '". # direction
2177 @{$hit}[6]."', '". # delivery_time
2178 @{$hit}[7]."', '". # message
2179 $timestamp."'". # timestamp
2180 ")";
2181 &daemon_log("M DEBUG: $sql_statement", 1);
2182 my $res = $messaging_db->exec_statement($sql_statement);
2183 &daemon_log("M INFO: message '".@{$hit}[0]."' is prepared for delivery to receiver '$receiver'", 5);
2184 }
2186 # set incoming message to flag d=deliverd
2187 $sql_statement = "UPDATE $messaging_tn SET flag='p' WHERE id='$message_id'";
2188 &daemon_log("M DEBUG: $sql_statement", 7);
2189 $res = $messaging_db->update_dbentry($sql_statement);
2190 &daemon_log("M INFO: message '$message_id' is set to flag 'p' (processed)", 5);
2191 }
2193 $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
2194 return;
2195 }
2197 sub watch_for_delivery_messages {
2198 my ($kernel, $heap) = @_[KERNEL, HEAP];
2200 # select outgoing messages
2201 my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( flag='p' AND direction='out' )";
2202 my $res = $messaging_db->exec_statement( $sql_statement );
2204 # build out msg for each usr
2205 foreach my $hit (@{$res}) {
2206 my $receiver = @{$hit}[3];
2207 my $msg_id = @{$hit}[0];
2208 my $subject = @{$hit}[1];
2209 my $message = @{$hit}[7];
2211 # resolve usr -> host where usr is logged in
2212 my $sql = "SELECT * FROM $login_users_tn WHERE (user='$receiver')";
2213 my $res = $login_users_db->exec_statement($sql);
2215 # receiver is logged in nowhere
2216 if (not ref(@$res[0]) eq "ARRAY") { next; }
2218 # receiver ist logged in at a client registered at local server
2219 my $send_succeed = 0;
2220 foreach my $hit (@$res) {
2221 my $receiver_host = @$hit[0];
2222 my $delivered2host = 0;
2223 &daemon_log("M DEBUG: user '$receiver' is logged in at host '$receiver_host'", 7);
2225 # Looking for host in know_clients_db
2226 my $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$receiver_host')";
2227 my $res = $known_clients_db->exec_statement($sql);
2229 # Host is known in known_clients_db
2230 if (ref(@$res[0]) eq "ARRAY") {
2231 my $receiver_key = @{@{$res}[0]}[2];
2232 my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
2233 my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data );
2234 my $error = &send_msg_to_target($out_msg, $receiver_host, $receiver_key, "usr_msg", 0);
2235 if ($error == 0 ) {
2236 $send_succeed++ ;
2237 $delivered2host++ ;
2238 &daemon_log("M DEBUG: send message for user '$receiver' to host '$receiver_host'", 7);
2239 } else {
2240 &daemon_log("M DEBUG: cannot send message for user '$receiver' to host '$receiver_host'", 7);
2241 }
2242 }
2244 # Message already send, do not need to do anything more, otherwise ...
2245 if ($delivered2host) { next;}
2247 # ...looking for host in foreign_clients_db
2248 $sql = "SELECT * FROM $foreign_clients_tn WHERE (hostname='$receiver_host')";
2249 $res = $foreign_clients_db->exec_statement($sql);
2251 # Host is known in foreign_clients_db
2252 if (ref(@$res[0]) eq "ARRAY") {
2253 my $registration_server = @{@{$res}[0]}[2];
2255 # Fetch encryption key for registration server
2256 my $sql = "SELECT * FROM $known_server_tn WHERE (hostname='$registration_server')";
2257 my $res = $known_server_db->exec_statement($sql);
2258 if (ref(@$res[0]) eq "ARRAY") {
2259 my $registration_server_key = @{@{$res}[0]}[3];
2260 my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
2261 my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data );
2262 my $error = &send_msg_to_target($out_msg, $registration_server, $registration_server_key, "usr_msg", 0);
2263 if ($error == 0 ) {
2264 $send_succeed++ ;
2265 $delivered2host++ ;
2266 &daemon_log("M DEBUG: send message for user '$receiver' to server '$registration_server'", 7);
2267 } else {
2268 &daemon_log("M ERROR: cannot send message for user '$receiver' to server '$registration_server'", 1);
2269 }
2271 } else {
2272 &daemon_log("M ERROR: host '$receiver_host' is reported to be ".
2273 "registrated at server '$registration_server', ".
2274 "but no data available in known_server_db ", 1);
2275 }
2276 }
2278 if (not $delivered2host) {
2279 &daemon_log("M ERROR: unable to send user message to host '$receiver_host'", 1);
2280 }
2281 }
2283 if ($send_succeed) {
2284 # set outgoing msg at db to deliverd
2285 my $sql = "UPDATE $messaging_tn SET flag='d' WHERE (id='$msg_id' AND direction='out' AND message_to='$receiver')";
2286 my $res = $messaging_db->exec_statement($sql);
2287 &daemon_log("M INFO: send message for user '$receiver' to logged in hosts", 5);
2288 } else {
2289 &daemon_log("M WARNING: failed to deliver message for user '$receiver'", 3);
2290 }
2291 }
2293 $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
2294 return;
2295 }
2298 sub watch_for_done_messages {
2299 my ($kernel,$heap) = @_[KERNEL, HEAP];
2301 my $sql = "SELECT * FROM $messaging_tn WHERE (flag='p' AND direction='in')";
2302 my $res = $messaging_db->exec_statement($sql);
2304 foreach my $hit (@{$res}) {
2305 my $msg_id = @{$hit}[0];
2307 my $sql = "SELECT * FROM $messaging_tn WHERE (id='$msg_id' AND direction='out' AND (NOT flag='s'))";
2308 my $res = $messaging_db->exec_statement($sql);
2310 # not all usr msgs have been seen till now
2311 if ( ref(@$res[0]) eq "ARRAY") { next; }
2313 $sql = "DELETE FROM $messaging_tn WHERE (id='$msg_id')";
2314 $res = $messaging_db->exec_statement($sql);
2316 }
2318 $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
2319 return;
2320 }
2323 sub watch_for_old_known_clients {
2324 my ($kernel,$heap) = @_[KERNEL, HEAP];
2326 my $sql_statement = "SELECT * FROM $known_clients_tn";
2327 my $res = $known_clients_db->select_dbentry( $sql_statement );
2329 my $cur_time = int(&get_time());
2331 while ( my ($hit_num, $hit) = each %$res) {
2332 my $expired_timestamp = int($hit->{'timestamp'});
2333 $expired_timestamp =~ /(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)/;
2334 my $dt = DateTime->new( year => $1,
2335 month => $2,
2336 day => $3,
2337 hour => $4,
2338 minute => $5,
2339 second => $6,
2340 );
2342 $dt->add( seconds => 2 * int($hit->{'keylifetime'}) );
2343 $expired_timestamp = $dt->ymd('').$dt->hms('');
2344 if ($cur_time > $expired_timestamp) {
2345 my $hostname = $hit->{'hostname'};
2346 my $del_sql = "DELETE FROM $known_clients_tn WHERE hostname='$hostname'";
2347 my $del_res = $known_clients_db->exec_statement($del_sql);
2349 &main::daemon_log("0 INFO: timestamp '".$hit->{'timestamp'}."' of client '$hostname' is expired('$expired_timestamp'), client will be deleted from known_clients_db", 5);
2350 }
2352 }
2354 $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
2355 }
2358 sub watch_for_next_tasks {
2359 my ($kernel,$heap) = @_[KERNEL, HEAP];
2361 my $sql = "SELECT * FROM $incoming_tn";
2362 my $res = $incoming_db->select_dbentry($sql);
2364 while ( my ($hit_num, $hit) = each %$res) {
2365 my $headertag = $hit->{'headertag'};
2366 if ($headertag =~ /^answer_(\d+)/) {
2367 # do not start processing, this message is for a still running POE::Wheel
2368 next;
2369 }
2370 my $message_id = $hit->{'id'};
2371 my $session_id = $hit->{'sessionid'};
2372 &daemon_log("$session_id DEBUG: start processing for message with incoming id: '$message_id'", 11);
2374 $kernel->yield('next_task', $hit);
2376 my $sql = "DELETE FROM $incoming_tn WHERE id=$message_id";
2377 my $res = $incoming_db->exec_statement($sql);
2378 }
2379 }
2382 sub get_ldap_handle {
2383 my ($session_id) = @_;
2384 my $heap;
2386 if (not defined $session_id ) { $session_id = 0 };
2387 if ($session_id =~ /[^0-9]*/) { $session_id = 0 };
2389 my ($package, $file, $row, $subroutine, $hasArgs, $wantArray, $evalText, $isRequire) = caller(1);
2390 my $caller_text = "subroutine $subroutine";
2391 if ($subroutine eq "(eval)") {
2392 $caller_text = "eval block within file '$file' for '$evalText'";
2393 }
2394 daemon_log("$session_id DEBUG: new ldap handle for '$caller_text' required!", 42);
2396 get_handle:
2397 my $ldap_handle = Net::LDAP->new( $ldap_uri );
2398 if (not ref $ldap_handle) {
2399 daemon_log("$session_id ERROR: Connection to LDAP URI '$ldap_uri' failed! Retrying in $ldap_retry_sec seconds.", 1);
2400 sleep($ldap_retry_sec);
2401 goto get_handle;
2402 } else {
2403 daemon_log("$session_id DEBUG: Connection to LDAP URI '$ldap_uri' established.", 42);
2404 }
2406 $ldap_handle->bind($ldap_admin_dn, password => $ldap_admin_password) or &daemon_log("$session_id ERROR: Could not bind as '$ldap_admin_dn' to LDAP URI '$ldap_uri'!", 1);
2407 return $ldap_handle;
2408 }
2411 sub release_ldap_handle {
2412 my ($ldap_handle, $session_id) = @_ ;
2413 if (not defined $session_id ) { $session_id = 0 };
2415 if(ref $ldap_handle) {
2416 $ldap_handle->disconnect();
2417 }
2418 &main::daemon_log("$session_id DEBUG: Released a ldap handle!", 42);
2419 return;
2420 }
2423 sub change_fai_state {
2424 my ($st, $targets, $session_id) = @_;
2425 $session_id = 0 if not defined $session_id;
2426 # Set FAI state to localboot
2427 my %mapActions= (
2428 reboot => '',
2429 update => 'softupdate',
2430 localboot => 'localboot',
2431 reinstall => 'install',
2432 rescan => '',
2433 wake => '',
2434 memcheck => 'memcheck',
2435 sysinfo => 'sysinfo',
2436 install => 'install',
2437 );
2439 # Return if this is unknown
2440 if (!exists $mapActions{ $st }){
2441 daemon_log("$session_id ERROR: unknown action '$st', can not translate ot FAIstate", 1);
2442 return;
2443 }
2445 my $state= $mapActions{ $st };
2447 # Build search filter for hosts
2448 my $search= "(&(objectClass=GOhard)";
2449 foreach (@{$targets}){
2450 $search.= "(macAddress=$_)";
2451 }
2452 $search.= ")";
2454 # If there's any host inside of the search string, procress them
2455 if (!($search =~ /macAddress/)){
2456 daemon_log("$session_id ERROR: no macAddress found in filter statement for LDAP search: '$search'", 1);
2457 return;
2458 }
2460 my $ldap_handle = &get_ldap_handle($session_id);
2461 # Perform search for Unit Tag
2462 my $mesg = $ldap_handle->search(
2463 base => $ldap_base,
2464 scope => 'sub',
2465 attrs => ['dn', 'FAIstate', 'objectClass'],
2466 filter => "$search"
2467 );
2469 if ($mesg->count) {
2470 my @entries = $mesg->entries;
2471 if (0 == @entries) {
2472 daemon_log("$session_id ERROR: ldap search failed: ldap_base=$ldap_base, filter=$search", 1);
2473 }
2475 foreach my $entry (@entries) {
2476 # Only modify entry if it is not set to '$state'
2477 if ($entry->get_value("FAIstate") ne "$state"){
2478 daemon_log("$session_id INFO: Setting FAIstate to '$state' for ".$entry->dn, 5);
2479 my $result;
2480 my %tmp = map { $_ => 1 } $entry->get_value("objectClass");
2481 if (exists $tmp{'FAIobject'}){
2482 if ($state eq ''){
2483 $result= $ldap_handle->modify($entry->dn, changes => [ delete => [ FAIstate => [] ] ]);
2484 } else {
2485 $result= $ldap_handle->modify($entry->dn, changes => [ replace => [ FAIstate => $state ] ]);
2486 }
2487 } elsif ($state ne ''){
2488 $result= $ldap_handle->modify($entry->dn, changes => [ add => [ objectClass => 'FAIobject' ], add => [ FAIstate => $state ] ]);
2489 }
2491 # Errors?
2492 if ($result->code){
2493 daemon_log("$session_id Error: Setting FAIstate to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
2494 }
2495 } else {
2496 daemon_log("$session_id DEBUG FAIstate at host '".$entry->dn."' already at state '$st'", 42);
2497 }
2498 }
2499 } else {
2500 daemon_log("$session_id ERROR: LDAP search failed: ldap_base=$ldap_base, filter=$search", 1);
2501 }
2502 &release_ldap_handle($ldap_handle, $session_id);
2504 return;
2505 }
2508 sub change_goto_state {
2509 my ($st, $targets, $session_id) = @_;
2510 $session_id = 0 if not defined $session_id;
2512 # Switch on or off?
2513 my $state= $st eq 'active' ? 'active': 'locked';
2515 my $ldap_handle = &get_ldap_handle($session_id);
2516 if( defined($ldap_handle) ) {
2518 # Build search filter for hosts
2519 my $search= "(&(objectClass=GOhard)";
2520 foreach (@{$targets}){
2521 $search.= "(macAddress=$_)";
2522 }
2523 $search.= ")";
2525 # If there's any host inside of the search string, procress them
2526 if (!($search =~ /macAddress/)){
2527 &release_ldap_handle($ldap_handle);
2528 return;
2529 }
2531 # Perform search for Unit Tag
2532 my $mesg = $ldap_handle->search(
2533 base => $ldap_base,
2534 scope => 'sub',
2535 attrs => ['dn', 'gotoMode'],
2536 filter => "$search"
2537 );
2539 if ($mesg->count) {
2540 my @entries = $mesg->entries;
2541 foreach my $entry (@entries) {
2543 # Only modify entry if it is not set to '$state'
2544 if ($entry->get_value("gotoMode") ne $state){
2546 daemon_log("$session_id INFO: Setting gotoMode to '$state' for ".$entry->dn, 5);
2547 my $result;
2548 $result= $ldap_handle->modify($entry->dn, changes => [replace => [ gotoMode => $state ] ]);
2550 # Errors?
2551 if ($result->code){
2552 &daemon_log("$session_id Error: Setting gotoMode to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
2553 }
2555 }
2556 }
2557 } else {
2558 daemon_log("$session_id ERROR: LDAP search failed in function change_goto_state: ldap_base=$ldap_base, filter=$search", 1);
2559 }
2561 }
2562 &release_ldap_handle($ldap_handle, $session_id);
2563 return;
2564 }
2567 sub run_recreate_packages_db {
2568 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2569 my $session_id = $session->ID;
2570 &main::daemon_log("$session_id INFO: Recreating FAI Packages DB ('$fai_release_tn', '$fai_server_tn', '$packages_list_tn')", 5);
2571 $kernel->yield('create_fai_release_db', $fai_release_tn);
2572 $kernel->yield('create_fai_server_db', $fai_server_tn);
2573 return;
2574 }
2577 sub run_create_fai_server_db {
2578 my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
2579 my $session_id = $session->ID;
2580 my $task = POE::Wheel::Run->new(
2581 Program => sub { &create_fai_server_db($table_name,$kernel, undef, $session_id) },
2582 StdoutEvent => "session_run_result",
2583 StderrEvent => "session_run_debug",
2584 CloseEvent => "session_run_done",
2585 );
2587 $heap->{task}->{ $task->ID } = $task;
2588 return;
2589 }
2592 sub create_fai_server_db {
2593 my ($table_name, $kernel, $dont_create_packages_list, $session_id) = @_;
2594 my $result;
2596 if (not defined $session_id) { $session_id = 0; }
2597 my $ldap_handle = &get_ldap_handle($session_id);
2598 if(defined($ldap_handle)) {
2599 daemon_log("$session_id INFO: create_fai_server_db: start", 5);
2600 my $mesg= $ldap_handle->search(
2601 base => $ldap_base,
2602 scope => 'sub',
2603 attrs => ['FAIrepository', 'gosaUnitTag'],
2604 filter => "(&(FAIrepository=*)(objectClass=FAIrepositoryServer))",
2605 );
2606 if($mesg->{'resultCode'} == 0 &&
2607 $mesg->count != 0) {
2608 foreach my $entry (@{$mesg->{entries}}) {
2609 if($entry->exists('FAIrepository')) {
2610 # Add an entry for each Repository configured for server
2611 foreach my $repo(@{$entry->get_value('FAIrepository', asref => 1)}) {
2612 my($tmp_url,$tmp_server,$tmp_release,$tmp_sections) = split(/\|/, $repo);
2613 my $tmp_tag= $entry->get_value('gosaUnitTag') || "";
2614 $result= $fai_server_db->add_dbentry( {
2615 table => $table_name,
2616 primkey => ['server', 'fai_release', 'tag'],
2617 server => $tmp_url,
2618 fai_release => $tmp_release,
2619 sections => $tmp_sections,
2620 tag => (length($tmp_tag)>0)?$tmp_tag:"",
2621 } );
2622 }
2623 }
2624 }
2625 }
2626 daemon_log("$session_id INFO: create_fai_server_db: finished", 5);
2627 &release_ldap_handle($ldap_handle);
2629 # TODO: Find a way to post the 'create_packages_list_db' event
2630 if(not defined($dont_create_packages_list)) {
2631 &create_packages_list_db(undef, $session_id);
2632 }
2633 }
2635 return $result;
2636 }
2639 sub run_create_fai_release_db {
2640 my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
2641 my $session_id = $session->ID;
2642 my $task = POE::Wheel::Run->new(
2643 Program => sub { &create_fai_release_db($table_name, $session_id) },
2644 StdoutEvent => "session_run_result",
2645 StderrEvent => "session_run_debug",
2646 CloseEvent => "session_run_done",
2647 );
2649 $heap->{task}->{ $task->ID } = $task;
2650 return;
2651 }
2654 sub create_fai_release_db {
2655 my ($table_name, $session_id) = @_;
2656 my $result;
2658 # used for logging
2659 if (not defined $session_id) { $session_id = 0; }
2661 my $ldap_handle = &get_ldap_handle($session_id);
2662 if(defined($ldap_handle)) {
2663 daemon_log("$session_id INFO: create_fai_release_db: start",5);
2664 my $mesg= $ldap_handle->search(
2665 base => $ldap_base,
2666 scope => 'sub',
2667 attrs => [],
2668 filter => "(&(objectClass=organizationalUnit)(ou=fai))",
2669 );
2670 if(($mesg->code == 0) && ($mesg->count != 0))
2671 {
2672 daemon_log("$session_id DEBUG: create_fai_release_db: count " . $mesg->count,138);
2674 # Walk through all possible FAI container ou's
2675 my @sql_list;
2676 my $timestamp= &get_time();
2677 foreach my $ou (@{$mesg->{entries}}) {
2678 my $tmp_classes= resolve_fai_classes($ou->dn, $ldap_handle, $session_id);
2679 if(defined($tmp_classes) && ref($tmp_classes) eq 'HASH') {
2680 my @tmp_array=get_fai_release_entries($tmp_classes);
2681 if(@tmp_array) {
2682 foreach my $entry (@tmp_array) {
2683 if(defined($entry) && ref($entry) eq 'HASH') {
2684 my $sql=
2685 "INSERT INTO $table_name "
2686 ."(timestamp, fai_release, class, type, state) VALUES ("
2687 .$timestamp.","
2688 ."'".$entry->{'release'}."',"
2689 ."'".$entry->{'class'}."',"
2690 ."'".$entry->{'type'}."',"
2691 ."'".$entry->{'state'}."')";
2692 push @sql_list, $sql;
2693 }
2694 }
2695 }
2696 }
2697 }
2699 daemon_log("$session_id DEBUG: create_fai_release_db: Inserting ".scalar @sql_list." entries to DB",138);
2700 &release_ldap_handle($ldap_handle);
2701 if(@sql_list) {
2702 unshift @sql_list, "VACUUM";
2703 unshift @sql_list, "DELETE FROM $table_name";
2704 $fai_release_db->exec_statementlist(\@sql_list);
2705 }
2706 daemon_log("$session_id DEBUG: create_fai_release_db: Done with inserting",138);
2707 } else {
2708 daemon_log("$session_id INFO: create_fai_release_db: error: " . $mesg->code, 5);
2709 }
2710 daemon_log("$session_id INFO: create_fai_release_db: finished",5);
2711 }
2712 return $result;
2713 }
2715 sub get_fai_types {
2716 my $tmp_classes = shift || return undef;
2717 my @result;
2719 foreach my $type(keys %{$tmp_classes}) {
2720 if(defined($tmp_classes->{$type}[0]) && (!($tmp_classes->{$type}[0] =~ /^.*?removed.*?$/))) {
2721 my $entry = {
2722 type => $type,
2723 state => $tmp_classes->{$type}[0],
2724 };
2725 push @result, $entry;
2726 }
2727 }
2729 return @result;
2730 }
2732 sub get_fai_state {
2733 my $result = "";
2734 my $tmp_classes = shift || return $result;
2736 foreach my $type(keys %{$tmp_classes}) {
2737 if(defined($tmp_classes->{$type}[0])) {
2738 $result = $tmp_classes->{$type}[0];
2740 # State is equal for all types in class
2741 last;
2742 }
2743 }
2745 return $result;
2746 }
2748 sub resolve_fai_classes {
2749 my ($fai_base, $ldap_handle, $session_id) = @_;
2750 if (not defined $session_id) { $session_id = 0; }
2751 my $result;
2752 my @possible_fai_classes= ("FAIscript", "FAIhook", "FAIpartitionTable", "FAItemplate", "FAIvariable", "FAIprofile", "FAIpackageList");
2753 my $fai_filter= "(|(&(objectClass=FAIclass)(|(objectClass=".join(")(objectClass=", @possible_fai_classes).")))(objectClass=FAIbranch))";
2754 my $fai_classes;
2756 daemon_log("$session_id DEBUG: Searching for FAI entries in base $fai_base", 138);
2757 my $mesg= $ldap_handle->search(
2758 base => $fai_base,
2759 scope => 'sub',
2760 attrs => ['cn','objectClass','FAIstate'],
2761 filter => $fai_filter,
2762 );
2763 daemon_log("$session_id DEBUG: Found ".$mesg->count()." FAI entries", 138);
2765 if($mesg->{'resultCode'} == 0 &&
2766 $mesg->count != 0) {
2767 foreach my $entry (@{$mesg->{entries}}) {
2768 if($entry->exists('cn')) {
2769 my $tmp_dn= $entry->dn();
2770 $tmp_dn= substr( $tmp_dn, 0, length($tmp_dn)
2771 - length($fai_base) - 1 );
2773 # Skip classname and ou dn parts for class
2774 my $tmp_release = ($1) if $tmp_dn =~ /^[^,]+,[^,]+,(.*?)$/;
2776 # Skip classes without releases
2777 if((!defined($tmp_release)) || length($tmp_release)==0) {
2778 next;
2779 }
2781 my $tmp_cn= $entry->get_value('cn');
2782 my $tmp_state= $entry->get_value('FAIstate');
2784 my $tmp_type;
2785 # Get FAI type
2786 for my $oclass(@{$entry->get_value('objectClass', asref => 1)}) {
2787 if(grep $_ eq $oclass, @possible_fai_classes) {
2788 $tmp_type= $oclass;
2789 last;
2790 }
2791 }
2793 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2794 # A Subrelease
2795 my @sub_releases = split(/,/, $tmp_release);
2797 # Walk through subreleases and build hash tree
2798 my $hash;
2799 while(my $tmp_sub_release = pop @sub_releases) {
2800 $hash .= "\{'$tmp_sub_release'\}->";
2801 }
2802 eval('push @{$fai_classes->'.$hash.'{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";');
2803 } else {
2804 # A branch, no subrelease
2805 push @{$fai_classes->{$tmp_release}->{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";
2806 }
2807 } elsif (!$entry->exists('cn')) {
2808 my $tmp_dn= $entry->dn();
2809 $tmp_dn= substr( $tmp_dn, 0, length($tmp_dn)
2810 - length($fai_base) - 1 );
2811 my $tmp_release = ($1) if $tmp_dn =~ /^(.*?)$/;
2813 # Skip classes without releases
2814 if((!defined($tmp_release)) || length($tmp_release)==0) {
2815 next;
2816 }
2818 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2819 # A Subrelease
2820 my @sub_releases= split(/,/, $tmp_release);
2822 # Walk through subreleases and build hash tree
2823 my $hash;
2824 while(my $tmp_sub_release = pop @sub_releases) {
2825 $hash .= "\{'$tmp_sub_release'\}->";
2826 }
2827 # Remove the last two characters
2828 chop($hash);
2829 chop($hash);
2831 eval('$fai_classes->'.$hash.'= {}');
2832 } else {
2833 # A branch, no subrelease
2834 if(!exists($fai_classes->{$tmp_release})) {
2835 $fai_classes->{$tmp_release} = {};
2836 }
2837 }
2838 }
2839 }
2841 # The hash is complete, now we can honor the copy-on-write based missing entries
2842 foreach my $release (keys %$fai_classes) {
2843 $result->{$release}= deep_copy(apply_fai_inheritance($fai_classes->{$release}));
2844 }
2845 }
2846 return $result;
2847 }
2849 sub apply_fai_inheritance {
2850 my $fai_classes = shift || return {};
2851 my $tmp_classes;
2853 # Get the classes from the branch
2854 foreach my $class (keys %{$fai_classes}) {
2855 # Skip subreleases
2856 if($class =~ /^ou=.*$/) {
2857 next;
2858 } else {
2859 $tmp_classes->{$class}= deep_copy($fai_classes->{$class});
2860 }
2861 }
2863 # Apply to each subrelease
2864 foreach my $subrelease (keys %{$fai_classes}) {
2865 if($subrelease =~ /ou=/) {
2866 foreach my $tmp_class (keys %{$tmp_classes}) {
2867 if(!exists($fai_classes->{$subrelease}->{$tmp_class})) {
2868 $fai_classes->{$subrelease}->{$tmp_class} =
2869 deep_copy($tmp_classes->{$tmp_class});
2870 } else {
2871 foreach my $type (keys %{$tmp_classes->{$tmp_class}}) {
2872 if(!exists($fai_classes->{$subrelease}->{$tmp_class}->{$type})) {
2873 $fai_classes->{$subrelease}->{$tmp_class}->{$type}=
2874 deep_copy($tmp_classes->{$tmp_class}->{$type});
2875 }
2876 }
2877 }
2878 }
2879 }
2880 }
2882 # Find subreleases in deeper levels
2883 foreach my $subrelease (keys %{$fai_classes}) {
2884 if($subrelease =~ /ou=/) {
2885 foreach my $subsubrelease (keys %{$fai_classes->{$subrelease}}) {
2886 if($subsubrelease =~ /ou=/) {
2887 apply_fai_inheritance($fai_classes->{$subrelease});
2888 }
2889 }
2890 }
2891 }
2893 return $fai_classes;
2894 }
2896 sub get_fai_release_entries {
2897 my $tmp_classes = shift || return;
2898 my $parent = shift || "";
2899 my @result = shift || ();
2901 foreach my $entry (keys %{$tmp_classes}) {
2902 if(defined($entry)) {
2903 if($entry =~ /^ou=.*$/) {
2904 my $release_name = $entry;
2905 $release_name =~ s/ou=//g;
2906 if(length($parent)>0) {
2907 $release_name = $parent."/".$release_name;
2908 }
2909 my @bufentries = get_fai_release_entries($tmp_classes->{$entry}, $release_name, @result);
2910 foreach my $bufentry(@bufentries) {
2911 push @result, $bufentry;
2912 }
2913 } else {
2914 my @types = get_fai_types($tmp_classes->{$entry});
2915 foreach my $type (@types) {
2916 push @result,
2917 {
2918 'class' => $entry,
2919 'type' => $type->{'type'},
2920 'release' => $parent,
2921 'state' => $type->{'state'},
2922 };
2923 }
2924 }
2925 }
2926 }
2928 return @result;
2929 }
2931 sub deep_copy {
2932 my $this = shift;
2933 if (not ref $this) {
2934 $this;
2935 } elsif (ref $this eq "ARRAY") {
2936 [map deep_copy($_), @$this];
2937 } elsif (ref $this eq "HASH") {
2938 +{map { $_ => deep_copy($this->{$_}) } keys %$this};
2939 } else { die "what type is $_?" }
2940 }
2943 sub session_run_result {
2944 my ($kernel, $heap, $client_answer) = @_[KERNEL, HEAP, ARG0];
2945 $kernel->sig(CHLD => "child_reap");
2946 }
2948 sub session_run_debug {
2949 my $result = $_[ARG0];
2950 print STDERR "$result\n";
2951 }
2953 sub session_run_done {
2954 my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
2955 delete $heap->{task}->{$task_id};
2956 if (exists $heap->{ldap_handle}->{$task_id}) {
2957 &release_ldap_handle($heap->{ldap_handle}->{$task_id});
2958 }
2959 delete $heap->{ldap_handle}->{$task_id};
2960 }
2963 sub create_sources_list {
2964 my $session_id = shift || 0;
2965 my $result="/tmp/gosa_si_tmp_sources_list";
2967 # Remove old file
2968 if(stat($result)) {
2969 unlink($result);
2970 &main::daemon_log("$session_id DEBUG: remove an old version of '$result'", 7);
2971 }
2973 open(my $fh, ">", "$result");
2974 if (not defined $fh) {
2975 &main::daemon_log("$session_id DEBUG: cannot open '$result' for writing", 7);
2976 return undef;
2977 }
2978 if(defined($main::ldap_server_dn) and length($main::ldap_server_dn) > 0) {
2979 my $ldap_handle = &get_ldap_handle($session_id);
2980 my $mesg=$ldap_handle->search(
2981 base => $main::ldap_server_dn,
2982 scope => 'base',
2983 attrs => 'FAIrepository',
2984 filter => 'objectClass=FAIrepositoryServer'
2985 );
2986 if($mesg->count) {
2987 foreach my $entry(@{$mesg->{'entries'}}) {
2988 foreach my $value(@{$entry->get_value('FAIrepository', asref => 1)}) {
2989 my ($server, $tag, $release, $sections)= split /\|/, $value;
2990 my $line = "deb $server $release";
2991 $sections =~ s/,/ /g;
2992 $line.= " $sections";
2993 print $fh $line."\n";
2994 }
2995 }
2996 }
2997 &release_ldap_handle($ldap_handle);
2998 } else {
2999 if (defined $main::ldap_server_dn){
3000 &main::daemon_log("$session_id ERROR: something wrong with ldap_server_dn '$main::ldap_server_dn', abort create_sources_list", 1);
3001 } else {
3002 &main::daemon_log("$session_id ERROR: no ldap_server_dn found, abort create_sources_list", 1);
3003 }
3004 }
3005 close($fh);
3007 return $result;
3008 }
3011 sub run_create_packages_list_db {
3012 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
3013 my $session_id = $session->ID;
3014 my $task = POE::Wheel::Run->new(
3015 Priority => +20,
3016 Program => sub {&create_packages_list_db(undef, $session_id)},
3017 StdoutEvent => "session_run_result",
3018 StderrEvent => "session_run_debug",
3019 CloseEvent => "session_run_done",
3020 );
3021 $heap->{task}->{ $task->ID } = $task;
3022 }
3025 sub create_packages_list_db {
3026 my ($sources_file, $session_id) = @_;
3028 # it should not be possible to trigger a recreation of packages_list_db
3029 # while packages_list_db is under construction, so set flag packages_list_under_construction
3030 # which is tested befor recreation can be started
3031 if (-r $packages_list_under_construction) {
3032 daemon_log("$session_id WARNING: packages_list_db is right now under construction, please wait until this process is finished", 3);
3033 return;
3034 } else {
3035 daemon_log("$session_id INFO: create_packages_list_db: start", 5);
3036 # set packages_list_under_construction to true
3037 system("touch $packages_list_under_construction");
3038 @packages_list_statements=();
3039 }
3041 if (not defined $session_id) { $session_id = 0; }
3043 if (not defined $sources_file) {
3044 &main::daemon_log("$session_id INFO: no sources_file given for creating packages list so trigger creation of it", 5);
3045 $sources_file = &create_sources_list($session_id);
3046 }
3048 if (not defined $sources_file) {
3049 &main::daemon_log("$session_id ERROR: no sources_file given under '$sources_file', skip create_packages_list_db", 1);
3050 unlink($packages_list_under_construction);
3051 return;
3052 }
3054 my $line;
3056 open(my $CONFIG, "<", "$sources_file") or do {
3057 daemon_log( "$session_id ERROR: create_packages_list_db: Failed to open '$sources_file'", 1);
3058 unlink($packages_list_under_construction);
3059 return;
3060 };
3062 # Read lines
3063 while ($line = <$CONFIG>){
3064 # Unify
3065 chop($line);
3066 $line =~ s/^\s+//;
3067 $line =~ s/^\s+/ /;
3069 # Strip comments
3070 $line =~ s/#.*$//g;
3072 # Skip empty lines
3073 if ($line =~ /^\s*$/){
3074 next;
3075 }
3077 # Interpret deb line
3078 if ($line =~ /^deb [^\s]+\s[^\s]+\s[^\s]+/){
3079 my( $baseurl, $dist, $sections ) = ($line =~ /^deb\s([^\s]+)\s+([^\s]+)\s+(.*)$/);
3080 my $section;
3081 foreach $section (split(' ', $sections)){
3082 &parse_package_info( $baseurl, $dist, $section, $session_id );
3083 }
3084 }
3085 else {
3086 daemon_log("$session_id ERROR: cannot parse line '$line'", 1);
3087 }
3088 }
3090 close ($CONFIG);
3092 if(keys(%repo_dirs)) {
3093 find(\&cleanup_and_extract, keys( %repo_dirs ));
3094 &main::strip_packages_list_statements();
3095 $packages_list_db->exec_statementlist(\@packages_list_statements);
3096 }
3097 unlink($packages_list_under_construction);
3098 daemon_log("$session_id INFO: create_packages_list_db: finished", 5);
3099 return;
3100 }
3102 # This function should do some intensive task to minimize the db-traffic
3103 sub strip_packages_list_statements {
3104 my @existing_entries= @{$packages_list_db->exec_statement("SELECT * FROM $main::packages_list_tn")};
3105 my @new_statement_list=();
3106 my $hash;
3107 my $insert_hash;
3108 my $update_hash;
3109 my $delete_hash;
3110 my $known_packages_hash;
3111 my $local_timestamp=get_time();
3113 foreach my $existing_entry (@existing_entries) {
3114 $hash->{@{$existing_entry}[0]}->{@{$existing_entry}[1]}->{@{$existing_entry}[2]}= $existing_entry;
3115 }
3117 foreach my $statement (@packages_list_statements) {
3118 if($statement =~ /^INSERT/i) {
3119 # Assign the values from the insert statement
3120 my ($distribution,$package,$version,$section,$description,$template,$timestamp) = ($1,$2,$3,$4,$5,$6,$7) if $statement =~
3121 /^INSERT\s+?INTO\s+?$main::packages_list_tn\s+?VALUES\s*?\('(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)'\s*?\)$/si;
3122 if(exists($hash->{$distribution}->{$package}->{$version})) {
3123 # If section or description has changed, update the DB
3124 if(
3125 (! (@{$hash->{$distribution}->{$package}->{$version}}[3] eq $section)) or
3126 (! (@{$hash->{$distribution}->{$package}->{$version}}[4] eq $description))
3127 ) {
3128 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,undef);
3129 } else {
3130 # package is already present in database. cache this knowledge for later use
3131 @{$known_packages_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
3132 }
3133 } else {
3134 # Insert a non-existing entry to db
3135 @{$insert_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
3136 }
3137 } elsif ($statement =~ /^UPDATE/i) {
3138 my ($template,$package,$version) = ($1,$2,$3) if $statement =~
3139 /^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;
3140 foreach my $distribution (keys %{$hash}) {
3141 if(exists($insert_hash->{$distribution}->{$package}->{$version})) {
3142 # update the insertion hash to execute only one query per package (insert instead insert+update)
3143 @{$insert_hash->{$distribution}->{$package}->{$version}}[5]= $template;
3144 } elsif(exists($hash->{$distribution}->{$package}->{$version})) {
3145 if( ! (@{$hash->{$distribution}->{$package}->{$version}}[5] eq $template)) {
3146 my $section;
3147 my $description;
3148 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) and
3149 length(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) > 0 ) {
3150 $section= @{$update_hash->{$distribution}->{$package}->{$version}}[3];
3151 }
3152 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
3153 $description= @{$update_hash->{$distribution}->{$package}->{$version}}[4];
3154 }
3155 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
3156 }
3157 }
3158 }
3159 }
3160 }
3162 # Check for orphaned entries
3163 foreach my $existing_entry (@existing_entries) {
3164 my $distribution= @{$existing_entry}[0];
3165 my $package= @{$existing_entry}[1];
3166 my $version= @{$existing_entry}[2];
3167 my $section= @{$existing_entry}[3];
3169 if(
3170 exists($insert_hash->{$distribution}->{$package}->{$version}) ||
3171 exists($update_hash->{$distribution}->{$package}->{$version}) ||
3172 exists($known_packages_hash->{$distribution}->{$package}->{$version})
3173 ) {
3174 next;
3175 } else {
3176 # Insert entry to delete hash
3177 @{$delete_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section);
3178 }
3179 }
3181 # unroll the insert hash
3182 foreach my $distribution (keys %{$insert_hash}) {
3183 foreach my $package (keys %{$insert_hash->{$distribution}}) {
3184 foreach my $version (keys %{$insert_hash->{$distribution}->{$package}}) {
3185 push @new_statement_list, "INSERT INTO $main::packages_list_tn VALUES ('$distribution','$package','$version',"
3186 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[3]',"
3187 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[4]',"
3188 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[5]',"
3189 ."'$local_timestamp')";
3190 }
3191 }
3192 }
3194 # unroll the update hash
3195 foreach my $distribution (keys %{$update_hash}) {
3196 foreach my $package (keys %{$update_hash->{$distribution}}) {
3197 foreach my $version (keys %{$update_hash->{$distribution}->{$package}}) {
3198 my $set = "";
3199 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3])) {
3200 $set .= "section = '@{$update_hash->{$distribution}->{$package}->{$version}}[3]', ";
3201 }
3202 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
3203 $set .= "description = '@{$update_hash->{$distribution}->{$package}->{$version}}[4]', ";
3204 }
3205 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[5])) {
3206 $set .= "template = '@{$update_hash->{$distribution}->{$package}->{$version}}[5]', ";
3207 }
3208 if(defined($set) and length($set) > 0) {
3209 $set .= "timestamp = '$local_timestamp'";
3210 } else {
3211 next;
3212 }
3213 push @new_statement_list,
3214 "UPDATE $main::packages_list_tn SET $set WHERE"
3215 ." distribution = '$distribution'"
3216 ." AND package = '$package'"
3217 ." AND version = '$version'";
3218 }
3219 }
3220 }
3222 # unroll the delete hash
3223 foreach my $distribution (keys %{$delete_hash}) {
3224 foreach my $package (keys %{$delete_hash->{$distribution}}) {
3225 foreach my $version (keys %{$delete_hash->{$distribution}->{$package}}) {
3226 my $section = @{$delete_hash->{$distribution}->{$package}->{$version}}[3];
3227 push @new_statement_list, "DELETE FROM $main::packages_list_tn WHERE distribution='$distribution' AND package='$package' AND version='$version' AND section='$section'";
3228 }
3229 }
3230 }
3232 unshift(@new_statement_list, "VACUUM");
3234 @packages_list_statements = @new_statement_list;
3235 }
3238 sub parse_package_info {
3239 my ($baseurl, $dist, $section, $session_id)= @_;
3240 my ($package);
3241 if (not defined $session_id) { $session_id = 0; }
3242 my ($path) = ($baseurl =~ m%://[^/]*(.*)$%);
3243 $repo_dirs{ "${repo_path}/pool" } = 1;
3245 foreach $package ("Packages.gz"){
3246 daemon_log("$session_id DEBUG: create_packages_list: fetch $baseurl, $dist, $section", 266);
3247 get_package( "$baseurl/dists/$dist/$section/binary-$arch/$package", "$outdir/$dist/$section", $session_id );
3248 parse_package( "$outdir/$dist/$section", $dist, $path, $session_id );
3249 }
3251 }
3254 sub get_package {
3255 my ($url, $dest, $session_id)= @_;
3256 if (not defined $session_id) { $session_id = 0; }
3258 my $tpath = dirname($dest);
3259 -d "$tpath" || mkpath "$tpath";
3261 # This is ugly, but I've no time to take a look at "how it works in perl"
3262 if(0 == system("wget '$url' -O '$dest' 2>/dev/null") ) {
3263 system("gunzip -cd '$dest' > '$dest.in'");
3264 daemon_log("$session_id DEBUG: run command: gunzip -cd '$dest' > '$dest.in'", 266);
3265 unlink($dest);
3266 daemon_log("$session_id DEBUG: delete file '$dest'", 266);
3267 } else {
3268 daemon_log("$session_id ERROR: create_packages_list_db: get_packages: fetching '$url' into '$dest' failed!", 1);
3269 }
3270 return 0;
3271 }
3274 sub parse_package {
3275 my ($path, $dist, $srv_path, $session_id)= @_;
3276 if (not defined $session_id) { $session_id = 0;}
3277 my ($package, $version, $section, $description);
3278 my $timestamp = &get_time();
3280 if(not stat("$path.in")) {
3281 daemon_log("$session_id ERROR: create_packages_list: parse_package: file '$path.in' is not readable",1);
3282 return;
3283 }
3285 open(my $PACKAGES, "<", "$path.in");
3286 if(not defined($PACKAGES)) {
3287 daemon_log("$session_id ERROR: create_packages_list_db: parse_package: cannot open '$path.in'",1);
3288 return;
3289 }
3291 # Read lines
3292 while (<$PACKAGES>){
3293 my $line = $_;
3294 # Unify
3295 chop($line);
3297 # Use empty lines as a trigger
3298 if ($line =~ /^\s*$/){
3299 my $sql = "INSERT INTO packages_list VALUES ('$dist', '$package', '$version', '$section', '$description', '', '$timestamp')";
3300 push(@packages_list_statements, $sql);
3301 $package = "none";
3302 $version = "none";
3303 $section = "none";
3304 $description = "none";
3305 next;
3306 }
3308 # Trigger for package name
3309 if ($line =~ /^Package:\s/){
3310 ($package)= ($line =~ /^Package: (.*)$/);
3311 next;
3312 }
3314 # Trigger for version
3315 if ($line =~ /^Version:\s/){
3316 ($version)= ($line =~ /^Version: (.*)$/);
3317 next;
3318 }
3320 # Trigger for description
3321 if ($line =~ /^Description:\s/){
3322 ($description)= &encode_base64(($line =~ /^Description: (.*)$/));
3323 next;
3324 }
3326 # Trigger for section
3327 if ($line =~ /^Section:\s/){
3328 ($section)= ($line =~ /^Section: (.*)$/);
3329 next;
3330 }
3332 # Trigger for filename
3333 if ($line =~ /^Filename:\s/){
3334 my ($filename) = ($line =~ /^Filename: (.*)$/);
3335 store_fileinfo( $package, $filename, $dist, $srv_path, $version, $repo_path );
3336 next;
3337 }
3338 }
3340 close( $PACKAGES );
3341 unlink( "$path.in" );
3342 }
3345 sub store_fileinfo {
3346 my( $package, $file, $dist, $path, $vers, $srvdir) = @_;
3348 my %fileinfo = (
3349 'package' => $package,
3350 'dist' => $dist,
3351 'version' => $vers,
3352 );
3354 $repo_files{ "${srvdir}/$file" } = \%fileinfo;
3355 }
3358 sub cleanup_and_extract {
3359 my $fileinfo = $repo_files{ $File::Find::name };
3361 if( defined $fileinfo ) {
3362 my $dir = "$outdir/$fileinfo->{ 'dist' }/debconf.d";
3363 my $sql;
3364 my $package = $fileinfo->{ 'package' };
3365 my $newver = $fileinfo->{ 'version' };
3367 mkpath($dir);
3368 system( "dpkg -e '$File::Find::name' '$dir/DEBIAN'" );
3370 if( -f "$dir/DEBIAN/templates" ) {
3372 daemon_log("0 DEBUG: Found debconf templates in '$package' - $newver", 266);
3374 my $tmpl= ""; {
3375 local $/=undef;
3376 open(my $FILE, "$dir/DEBIAN/templates");
3377 $tmpl = &encode_base64(<$FILE>);
3378 close($FILE);
3379 }
3380 rmtree("$dir/DEBIAN/templates");
3382 $sql= "update $main::packages_list_tn set template = '$tmpl' where package = '$package' and version = '$newver';";
3383 push @packages_list_statements, $sql;
3384 }
3385 }
3387 return;
3388 }
3391 sub prepare_server_registration
3392 {
3393 # Add foreign server from cfg file
3394 my @foreign_server_list;
3395 if ($foreign_server_string ne "") {
3396 my @cfg_foreign_server_list = split(",", $foreign_server_string);
3397 foreach my $foreign_server (@cfg_foreign_server_list) {
3398 push(@foreign_server_list, $foreign_server);
3399 }
3401 daemon_log("0 INFO: found foreign server in config file: ".join(", ", @foreign_server_list), 5);
3402 }
3404 # Perform a DNS lookup for server registration if flag is true
3405 if ($dns_lookup eq "true") {
3406 # Add foreign server from dns
3407 my @tmp_servers;
3408 if (not $server_domain) {
3409 # Try our DNS Searchlist
3410 for my $domain(get_dns_domains()) {
3411 chomp($domain);
3412 my ($tmp_domains, $error_string) = &get_server_addresses($domain);
3413 if(@$tmp_domains) {
3414 for my $tmp_server(@$tmp_domains) {
3415 push @tmp_servers, $tmp_server;
3416 }
3417 }
3418 }
3419 if(@tmp_servers && length(@tmp_servers)==0) {
3420 daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
3421 }
3422 } else {
3423 @tmp_servers = &get_server_addresses($server_domain);
3424 if( 0 == @tmp_servers ) {
3425 daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
3426 }
3427 }
3429 daemon_log("0 INFO: found foreign server via DNS ".join(", ", @tmp_servers), 5);
3431 foreach my $server (@tmp_servers) {
3432 unshift(@foreign_server_list, $server);
3433 }
3434 } else {
3435 daemon_log("0 INFO: DNS lookup for server registration is disabled", 5);
3436 }
3438 # eliminate duplicate entries
3439 @foreign_server_list = &del_doubles(@foreign_server_list);
3440 my $all_foreign_server = join(", ", @foreign_server_list);
3441 daemon_log("0 INFO: found foreign server in config file and DNS: '$all_foreign_server'", 5);
3443 # add all found foreign servers to known_server
3444 my $cur_timestamp = &get_time();
3445 foreach my $foreign_server (@foreign_server_list) {
3447 # do not add myself to known_server_db
3448 if (&is_local($foreign_server)) { next; }
3449 ######################################
3451 my $res = $known_server_db->add_dbentry( {table=>$known_server_tn,
3452 primkey=>['hostname'],
3453 hostname=>$foreign_server,
3454 macaddress=>"",
3455 status=>'not_yet_registered',
3456 hostkey=>"none",
3457 loaded_modules => "none",
3458 timestamp=>$cur_timestamp,
3459 update_time=>'19700101000000',
3460 } );
3461 }
3462 }
3464 sub register_at_foreign_servers {
3465 my ($kernel) = $_[KERNEL];
3467 # Update status and update-time of all si-server with expired update_time and
3468 # block them for race conditional registration processes of other si-servers.
3469 my $act_time = &get_time();
3470 my $block_statement = "UPDATE $known_server_tn SET status='new_server',update_time='19700101000000' WHERE (CAST(update_time AS UNSIGNED))<$act_time ";
3471 my $block_res = $known_server_db->exec_statement($block_statement);
3473 # Fetch all si-server from db where update_time is younger than act_time
3474 my $fetch_statement = "SELECT * FROM $known_server_tn WHERE update_time='19700101000000'";
3475 my $fetch_res = $known_server_db->exec_statement($fetch_statement);
3477 # Detect already connected clients. Will be added to registration msg later.
3478 my $client_sql = "SELECT * FROM $known_clients_tn";
3479 my $client_res = $known_clients_db->exec_statement($client_sql);
3481 # Send registration messag to all fetched si-server
3482 foreach my $hit (@$fetch_res) {
3483 my $hostname = @$hit[0];
3484 my $hostkey = &create_passwd;
3486 # Add already connected clients to registration message
3487 my $myhash = &create_xml_hash('new_server', $server_address, $hostname);
3488 &add_content2xml_hash($myhash, 'key', $hostkey);
3489 map(&add_content2xml_hash($myhash, 'client', @{$_}[0].",".@{$_}[4]), @$client_res);
3491 # Add locally loaded gosa-si modules to registration message
3492 my $loaded_modules = {};
3493 while (my ($package, $pck_info) = each %$known_modules) {
3494 next if ((!defined(@$pck_info[2])) || (!(ref (@$pck_info[2]) eq 'HASH')));
3495 foreach my $act_module (keys(%{@$pck_info[2]})) {
3496 $loaded_modules->{$act_module} = "";
3497 }
3498 }
3499 map(&add_content2xml_hash($myhash, "loaded_modules", $_), keys(%$loaded_modules));
3501 # Add macaddress to registration message
3502 my ($host_ip, $host_port) = split(/:/, $hostname);
3503 my $local_ip = &get_local_ip_for_remote_ip($host_ip);
3504 my $network_interface= &get_interface_for_ip($local_ip);
3505 my $host_mac = &get_mac_for_interface($network_interface);
3506 &add_content2xml_hash($myhash, 'macaddress', $host_mac);
3508 # Build registration message and send it
3509 my $foreign_server_msg = &create_xml_string($myhash);
3510 my $error = &send_msg_to_target($foreign_server_msg, $hostname, $ServerPackages_key, "new_server", 0);
3511 }
3514 # After n sec perform a check of all server registration processes
3515 $kernel->delay_set("control_server_registration", 2);
3517 return;
3518 }
3521 sub control_server_registration {
3522 my ($kernel) = $_[KERNEL];
3524 # Check if all registration processes succeed or not
3525 my $select_statement = "SELECT * FROM $known_server_tn WHERE status='new_server'";
3526 my $select_res = $known_server_db->exec_statement($select_statement);
3528 # If at least one registration process failed, maybe in case of a race condition
3529 # with a foreign registration process
3530 if (@$select_res > 0)
3531 {
3532 # Release block statement 'new_server' to make the server accessible
3533 # for foreign registration processes
3534 my $update_statement = "UPDATE $known_server_tn SET status='waiting' WHERE status='new_server'";
3535 my $update_res = $known_server_db->exec_statement($update_statement);
3537 # Set a random delay to avoid the registration race condition
3538 my $new_foreign_servers_register_delay = int(rand(4))+1;
3539 $kernel->delay_set("register_at_foreign_servers", $new_foreign_servers_register_delay);
3540 }
3541 # If all registration processes succeed
3542 else
3543 {
3544 $kernel->delay_set("register_at_foreign_servers", $foreign_servers_register_delay);
3545 }
3547 return;
3548 }
3550 #
3551 #==== MAIN = main ==============================================================
3552 #
3553 # Parse options and allow '-vvv'
3554 Getopt::Long::Configure( 'bundling' );
3555 GetOptions( 'v|verbose+' => \$verbose,
3556 'h|help' => \&usage,
3557 'c|config=s' => \$config,
3558 'x|dump-config=i' => \$dump_config,
3559 'f|foreground' => \$foreground)
3560 'd=s' => \$debug_parts,
3561 or usage( '', 1 );
3563 # We may want to dump the default configuration
3564 if( defined $dump_config ) {
3565 if($dump_config==1) {
3566 } elsif ($dump_config==2) {
3567 dump_configuration( $dump_config );
3568 } else {
3569 usage( "Dump configuration value has to be 1 or 2" );
3570 }
3571 }
3573 # read and set config parameters
3574 #&check_cmdline_param ;
3575 #&read_configfile($cfg_file, %cfg_defaults);
3576 &read_configfile($config, %cfg_defaults);
3577 #&check_pid;
3579 # not sure but seems to be for igonring phantom
3580 $SIG{CHLD} = 'IGNORE';
3582 # Create the PID object
3583 # Ensure you put a name that won't clobber
3584 # another program's PID file
3585 my $pid = File::Pid->new({
3586 file => $pidfile,
3587 });
3589 # Write the PID file
3590 $pid->write;
3592 # forward error messages to logfile
3593 if( ! $foreground ) {
3594 open( STDIN, '+>/dev/null' );
3595 open( STDOUT, '+>&STDIN' );
3596 open( STDERR, '+>&STDIN' );
3597 }
3599 # Just fork, if we are not in foreground mode
3600 if( ! $foreground ) {
3601 chdir '/' or die "Can't chdir to /: $!";
3602 $pid = fork;
3603 setsid or die "Can't start a new session: $!";
3604 umask 0;
3605 #} else {
3606 # $pid = $$;
3607 #}
3609 # Do something useful - put our PID into the pid_file
3610 #if( 0 != $pid ) {
3611 # open( my $LOCK_FILE, ">", "$pid_file" );
3612 # print $LOCK_FILE "$pid\n";
3613 # close( $LOCK_FILE );
3614 # if( !$foreground ) {
3615 # exit( 0 )
3616 # };
3617 #}
3619 # parse head url and revision from svn
3620 my $server_status_hash = { 'developmental'=>'revision', 'stable'=>'release'};
3621 $server_version =~ /^\$HeadURL: (\S+) \$:\$Rev: (\d+) \$$/;
3622 $server_headURL = defined $1 ? $1 : 'unknown' ;
3623 $server_revision = defined $2 ? $2 : 'unknown' ;
3624 if ($server_headURL =~ /\/tag\// ||
3625 $server_headURL =~ /\/branches\// ) {
3626 $server_status = "stable";
3627 } else {
3628 $server_status = "developmental" ;
3629 }
3630 # Prepare log file and set permissions
3631 $root_uid = getpwnam('root');
3632 $adm_gid = getgrnam('adm');
3633 open(my $FH, ">>", "$log_file");
3634 close($FH);
3635 chmod(0440, $log_file);
3636 chown($root_uid, $adm_gid, $log_file);
3637 chown($root_uid, $adm_gid, "/var/lib/gosa-si");
3639 daemon_log(" ", 1);
3640 daemon_log("$0 started!", 1);
3641 daemon_log("status: $server_status", 1);
3642 daemon_log($server_status_hash->{$server_status}.": $server_revision", 1);
3644 # Buildup data bases
3645 {
3646 no strict "refs";
3648 if ($db_module eq "DBmysql") {
3650 daemon_log("0 INFO: importing database module '$db_module'", 1);
3652 # connect to incoming_db
3653 $incoming_db = ("GOsaSI::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3655 # connect to gosa-si job queue
3656 $job_db = ("GOsaSI::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3658 # connect to known_clients_db
3659 $known_clients_db = ("GOsaSI::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3661 # connect to foreign_clients_db
3662 $foreign_clients_db = ("GOsaSI::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3664 # connect to known_server_db
3665 $known_server_db = ("GOsaSI::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3667 # connect to login_usr_db
3668 $login_users_db = ("GOsaSI::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3670 # connect to fai_server_db
3671 $fai_server_db = ("GOsaSI::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3673 # connect to fai_release_db
3674 $fai_release_db = ("GOsaSI::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3676 # connect to packages_list_db
3677 $packages_list_db = ("GOsaSI::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3679 # connect to messaging_db
3680 $messaging_db = ("GOsaSI::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3682 } elsif ($db_module eq "DBsqlite") {
3684 daemon_log("0 INFO: importing database module '$db_module'", 1);
3686 # connect to incoming_db
3687 unlink($incoming_file_name);
3688 $incoming_db = ("GOsaSI::".$db_module)->new($incoming_file_name);
3689 chmod(0640, $incoming_file_name);
3690 chown($root_uid, $adm_gid, $incoming_file_name);
3692 # connect to gosa-si job queue
3693 $job_db = ("GOsaSI::".$db_module)->new($job_queue_file_name);
3694 chmod(0640, $job_queue_file_name);
3695 chown($root_uid, $adm_gid, $job_queue_file_name);
3697 # connect to known_clients_db
3698 #unlink($known_clients_file_name);
3699 $known_clients_db = ("GOsaSI::".$db_module)->new($known_clients_file_name);
3700 chmod(0640, $known_clients_file_name);
3701 chown($root_uid, $adm_gid, $known_clients_file_name);
3703 # connect to foreign_clients_db
3704 #unlink($foreign_clients_file_name);
3705 $foreign_clients_db = ("GOsaSI::".$db_module)->new($foreign_clients_file_name);
3706 chmod(0640, $foreign_clients_file_name);
3707 chown($root_uid, $adm_gid, $foreign_clients_file_name);
3709 # connect to known_server_db
3710 unlink($known_server_file_name); # do not delete, gosa-si-server should be forced to check config file and dns at each start
3711 $known_server_db = ("GOsaSI::".$db_module)->new($known_server_file_name);
3712 chmod(0640, $known_server_file_name);
3713 chown($root_uid, $adm_gid, $known_server_file_name);
3715 # connect to login_usr_db
3716 #unlink($login_users_file_name);
3717 $login_users_db = ("GOsaSI::".$db_module)->new($login_users_file_name);
3718 chmod(0640, $login_users_file_name);
3719 chown($root_uid, $adm_gid, $login_users_file_name);
3721 # connect to fai_server_db
3722 unlink($fai_server_file_name);
3723 $fai_server_db = ("GOsaSI::".$db_module)->new($fai_server_file_name);
3724 chmod(0640, $fai_server_file_name);
3725 chown($root_uid, $adm_gid, $fai_server_file_name);
3727 # connect to fai_release_db
3728 unlink($fai_release_file_name);
3729 $fai_release_db = ("GOsaSI::".$db_module)->new($fai_release_file_name);
3730 chmod(0640, $fai_release_file_name);
3731 chown($root_uid, $adm_gid, $fai_release_file_name);
3733 # connect to packages_list_db
3734 unlink($packages_list_under_construction);
3735 $packages_list_db = ("GOsaSI::".$db_module)->new($packages_list_file_name);
3736 chmod(0640, $packages_list_file_name);
3737 chown($root_uid, $adm_gid, $packages_list_file_name);
3739 # connect to messaging_db
3740 #unlink($messaging_file_name);
3741 $messaging_db = ("GOsaSI::".$db_module)->new($messaging_file_name);
3742 chmod(0640, $messaging_file_name);
3743 chown($root_uid, $adm_gid, $messaging_file_name);
3744 }
3745 }
3747 # Creating tables
3749 daemon_log("0 INFO: creating tables in database with '$db_module'", 1);
3751 $messaging_db->create_table($messaging_tn, \@messaging_col_names);
3752 $packages_list_db->create_table($packages_list_tn, \@packages_list_col_names);
3753 $fai_release_db->create_table($fai_release_tn, \@fai_release_col_names);
3754 $fai_server_db->create_table($fai_server_tn, \@fai_server_col_names);
3755 $login_users_db->create_table($login_users_tn, \@login_users_col_names);
3756 $known_server_db->create_table($known_server_tn, \@known_server_col_names);
3757 $foreign_clients_db->create_table($foreign_clients_tn, \@foreign_clients_col_names);
3758 $known_clients_db->create_table($known_clients_tn, \@known_clients_col_names);
3759 $incoming_db->create_table($incoming_tn, \@incoming_col_names);
3760 $job_db->create_table($job_queue_tn, \@job_queue_col_names);
3762 # create xml object used for en/decrypting
3763 $xml = new XML::Simple();
3765 # Import all modules
3766 &import_modules;
3768 # Check wether all modules are gosa-si valid passwd check
3769 &password_check;
3771 # Check DNS and config file for server registration
3772 if ($serverPackages_enabled eq "true") { &prepare_server_registration; }
3774 # Create functions hash
3775 while (my ($module, @mod_info) = each %$known_modules)
3776 {
3777 while (my ($plugin, $functions) = each %{$mod_info[0][2]})
3778 {
3779 while (my ($function, $nothing) = each %$functions )
3780 {
3781 $known_functions->{$function} = $nothing;
3782 }
3783 }
3784 }
3786 # Prepare for using Opsi
3787 if ($opsi_enabled eq "true") {
3788 use JSON::RPC::Client;
3789 use XML::Quote qw(:all);
3790 $opsi_url= "https://".$opsi_admin.":".$opsi_password."@".$opsi_server.":4447/rpc";
3791 $opsi_client = new JSON::RPC::Client;
3792 }
3795 POE::Component::Server::TCP->new(
3796 Alias => "TCP_SERVER",
3797 Port => $server_port,
3798 ClientInput => sub {
3799 my ($kernel, $input, $heap, $session) = @_[KERNEL, ARG0, HEAP, SESSION];
3800 my $session_id = $session->ID;
3801 if ($input =~ /;([\d\.]+):([\d]+)$/)
3802 {
3803 # Messages from other servers should be blocked if config option is set
3804 if (($2 eq $server_port) && ($serverPackages_enabled eq "false"))
3805 {
3806 return;
3807 }
3808 &daemon_log("$session_id DEBUG: incoming message from '$1:$2'", 11);
3809 }
3810 else
3811 {
3812 my $remote_ip = $heap->{'remote_ip'};
3813 &daemon_log("$session_id DEBUG: incoming message from '$remote_ip'", 11);
3814 }
3815 push(@msgs_to_decrypt, $input);
3816 $kernel->yield("msg_to_decrypt");
3817 },
3818 InlineStates => {
3819 msg_to_decrypt => \&msg_to_decrypt,
3820 watch_for_next_tasks => \&watch_for_next_tasks,
3821 next_task => \&next_task,
3822 task_result => \&handle_task_result,
3823 task_done => \&handle_task_done,
3824 task_debug => \&handle_task_debug,
3825 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!" },
3826 }
3827 );
3829 daemon_log("0 INFO: start socket for incoming xml messages at port '$server_port' ", 1);
3831 # create session for repeatedly checking the job queue for jobs
3832 POE::Session->create(
3833 inline_states => {
3834 _start => \&session_start,
3835 register_at_foreign_servers => \®ister_at_foreign_servers,
3836 control_server_registration => \&control_server_registration,
3837 sig_handler => \&sig_handler,
3838 next_task => \&next_task,
3839 task_result => \&handle_task_result,
3840 task_done => \&handle_task_done,
3841 task_debug => \&handle_task_debug,
3842 watch_for_new_messages => \&watch_for_new_messages,
3843 watch_for_delivery_messages => \&watch_for_delivery_messages,
3844 watch_for_done_messages => \&watch_for_done_messages,
3845 watch_for_new_jobs => \&watch_for_new_jobs,
3846 watch_for_modified_jobs => \&watch_for_modified_jobs,
3847 watch_for_done_jobs => \&watch_for_done_jobs,
3848 watch_for_opsi_jobs => \&watch_for_opsi_jobs,
3849 watch_for_old_known_clients => \&watch_for_old_known_clients,
3850 create_packages_list_db => \&run_create_packages_list_db,
3851 create_fai_server_db => \&run_create_fai_server_db,
3852 create_fai_release_db => \&run_create_fai_release_db,
3853 recreate_packages_db => \&run_recreate_packages_db,
3854 session_run_result => \&session_run_result,
3855 session_run_debug => \&session_run_debug,
3856 session_run_done => \&session_run_done,
3857 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!" },
3858 }
3859 );
3862 POE::Kernel->run();
3863 exit;