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 #*********************************************************************
14 =head1 NAME
16 gosa-si-server -Support infrastructure for GOsa
18 =head1 SYNOPSIS
20 gosa-si-server [-hvf] [-c config] [-x dump ]
22 =head1 OPTIONS
24 B<-h>, B<--help>
25 print out this help message
27 B<-v>, B<--verbose>
28 be verbose (multiple v's will increase verbosity)
29 -v ERROR level
30 -vvv WARNING + ERROR level
31 -vvvvv INFO + WARNING level
32 -vvvvvvv DEBUG + INFO level
33 -vvvvvvvvv in and out going xml messages will be displayed
35 B<-f>, B<--foreground>
36 foregroud, process will not be forked to background
38 B<-c> I<file>, B<--config=>I<file>
39 configuration file, default F</etc/gosa-si/server.conf>
41 B<--no-arp>
42 starts script without connection to arp module
44 B<-d> <int>
45 if verbose level is higher than 7 'v' specified parts can be debugged
47 1 : receiving messages
48 2 : sending messages
49 4 : encrypting/decrypting messages
50 8 : verification if a message complies gosa-si requirements
51 16 : message processing
52 32 : ldap connectivity
53 64 : database status and connectivity
54 128 : main process
55 256 : creation of packages_list_db
56 512 : ARP debug information
58 B<-x> <dump>
59 dump configuration to stdout
60 ( 1 = current, 2 = default )
62 =head1 DESCRIPTION
64 gosa-si-server belongs to the support infrastructure of GOsa. Several gosa-si-clients can connect to one gosa-si-server. The server take care of the message forwarding from GOsa to si-clients. At the client site each message is related to a working instruction which will be executed there. Depending of the message an answer from the client to GOsa via the server is possible. Additional to answers clients reporting events or information to the server. The server registers himself at other servers in network and shares his knowledge with them. So messages to clients which are no registrated locally will be forward to the client corresponding server. The communication within the complete SI nework is realised by XML messages.
67 =head1 BUGS
69 Please report any bugs, or post any suggestions, to the GOsa mailing list <gosa-devel@oss.gonicus.de> or to <https://oss.gonicus.de/labs/gosa>
72 =head1 LICENCE AND COPYRIGHT
74 This code is part of GOsa (L<http://www.gosa-project.org>)
76 Copyright (C) 2003-2010 GONICUS GmbH
78 This program is distributed in the hope that it will be useful,
79 but WITHOUT ANY WARRANTY; without even the implied warranty of
80 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
81 GNU General Public License for more details.
83 =cut
85 use strict;
86 use warnings;
88 use Getopt::Long;
89 use Config::IniFiles;
90 use IO::Socket::INET;
91 use IO::Handle;
92 use IO::Select;
93 use Crypt::Rijndael;
94 use MIME::Base64;
95 use Digest::MD5 qw(md5 md5_hex md5_base64);
96 use XML::Simple;
97 use Data::Dumper;
98 use Sys::Syslog qw( :DEFAULT setlogsock);
99 use Time::HiRes qw( usleep clock_gettime );
100 use File::Spec;
101 use File::Basename;
102 use File::Find;
103 use File::Copy;
104 use File::Path;
105 use Net::LDAP;
106 use Net::LDAP::Util qw(:escape);
107 use File::Pid;
108 use GOsaSI::GosaSupportDaemon;
110 use POE qw(Component::Server::TCP Wheel::Run Filter::Reference);
111 use Symbol qw(qualify_to_ref);
112 use Fcntl qw/:flock/;
113 use POSIX;
115 my $server_version = '$HeadURL: https://oss.gonicus.de/repositories/gosa/trunk/gosa-si/gosa-si-server $:$Rev$';
117 # revision number of server and program name
118 my $server_headURL;
119 my $server_revision;
120 my $server_status;
122 my $db_module = "DBsqlite";
123 {
124 no strict "refs";
125 require ("GOsaSI/".$db_module.".pm");
126 ("GOsaSI/".$db_module)->import;
127 }
129 my $modules_path = "/usr/lib/gosa-si/modules";
130 use lib "/usr/lib/gosa-si/modules";
132 my ($foreground, $ping_timeout);
133 my ($server);
134 my ($gosa_server, $job_queue_timeout, $job_queue_loop_delay);
135 my ($messaging_db_loop_delay);
136 my $procid;
137 my $arp_fifo;
138 my $debug_parts = 0;
139 my $debug_parts_bitstring;
140 my ($xml);
141 my $sources_list;
142 my $max_clients;
143 my %repo_files=();
144 my $repo_path;
145 my %repo_dirs=();
147 my $server_status_hash = { 'developmental'=>'revision', 'stable'=>'release'};
149 # Variables declared in config file are always set to 'our'
150 our (%cfg_defaults, $log_file, $pid_file, $pid,
151 $server_ip, $server_port, $ClientPackages_key, $dns_lookup,
152 $arp_activ, $gosa_unit_tag,
153 $GosaPackages_key, $gosa_timeout,
154 $serverPackages_enabled, $foreign_server_string, $server_domain, $ServerPackages_key, $foreign_servers_register_delay,
155 $wake_on_lan_passwd, $job_synchronization, $modified_jobs_loop_delay,
156 $arp_enabled, $arp_interface,
157 $opsi_enabled, $opsi_server, $opsi_admin, $opsi_password,
158 $new_systems_ou,
159 $arch,
160 );
162 # additional variable which should be globaly accessable
163 our $server_address;
164 our $server_mac_address;
165 our $gosa_address;
166 our $no_arp;
167 our $forground;
168 our $cfg_file;
169 our ($ldap_uri, $ldap_base, $ldap_admin_dn, $ldap_admin_password, $ldap_server_dn, $ldap_version, $ldap_retry_sec);
170 our ($mysql_username, $mysql_password, $mysql_database, $mysql_host);
171 our $known_modules;
172 our $known_functions;
173 our $root_uid;
174 our $adm_gid;
175 our $verbose= 0;
176 our $global_kernel;
178 # where is the config stored by default and his name
179 our $config = '/etc/gosa-si/server.conf';
181 # by default dumping of config is undefined
182 our $dump_config = undef;
184 # if foreground is not null, script will be not forked to background
185 $foreground = 0 ;
187 # specifies the timeout seconds while checking the online status of a registrating client
188 $ping_timeout = 5;
190 $no_arp = 0;
191 my $packages_list_under_construction = "/tmp/packages_list_creation_in_progress";
192 my @packages_list_statements;
193 my $watch_for_new_jobs_in_progress = 0;
195 # holds all incoming decrypted messages
196 our $incoming_db;
197 our $incoming_tn = 'incoming';
198 my $incoming_file_name;
199 my @incoming_col_names = ("id INTEGER PRIMARY KEY",
200 "timestamp VARCHAR(14) DEFAULT 'none'",
201 "headertag VARCHAR(255) DEFAULT 'none'",
202 "targettag VARCHAR(255) DEFAULT 'none'",
203 "xmlmessage TEXT",
204 "module VARCHAR(255) DEFAULT 'none'",
205 "sessionid VARCHAR(255) DEFAULT '0'",
206 );
208 # holds all gosa jobs
209 our $job_db;
210 our $job_queue_tn = 'jobs';
211 my $job_queue_file_name;
212 my @job_queue_col_names = ("id INTEGER PRIMARY KEY",
213 "timestamp VARCHAR(14) DEFAULT 'none'",
214 "status VARCHAR(255) DEFAULT 'none'",
215 "result TEXT",
216 "progress VARCHAR(255) DEFAULT 'none'",
217 "headertag VARCHAR(255) DEFAULT 'none'",
218 "targettag VARCHAR(255) DEFAULT 'none'",
219 "xmlmessage TEXT",
220 "macaddress VARCHAR(17) DEFAULT 'none'",
221 "plainname VARCHAR(255) DEFAULT 'none'",
222 "siserver VARCHAR(255) DEFAULT 'none'",
223 "modified INTEGER DEFAULT '0'",
224 "periodic VARCHAR(6) DEFAULT 'none'",
225 );
227 # holds all other gosa-si-server
228 our $known_server_db;
229 our $known_server_tn = "known_server";
230 my $known_server_file_name;
231 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)");
233 # holds all registrated clients
234 our $known_clients_db;
235 our $known_clients_tn = "known_clients";
236 my $known_clients_file_name;
237 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)");
239 # holds all registered clients at a foreign server
240 our $foreign_clients_db;
241 our $foreign_clients_tn = "foreign_clients";
242 my $foreign_clients_file_name;
243 my @foreign_clients_col_names = ("hostname VARCHAR(255)", "macaddress VARCHAR(17)", "regserver VARCHAR(255)", "timestamp VARCHAR(14)");
245 # holds all logged in user at each client
246 our $login_users_db;
247 our $login_users_tn = "login_users";
248 my $login_users_file_name;
249 my @login_users_col_names = ("client VARCHAR(255)", "user VARCHAR(255)", "timestamp VARCHAR(14)", "regserver VARCHAR(255) DEFAULT 'localhost'");
251 # holds all fai server, the debian release and tag
252 our $fai_server_db;
253 our $fai_server_tn = "fai_server";
254 my $fai_server_file_name;
255 our @fai_server_col_names = ("timestamp VARCHAR(14)", "server VARCHAR(255)", "fai_release VARCHAR(255)", "sections VARCHAR(255)", "tag VARCHAR(255)");
257 our $fai_release_db;
258 our $fai_release_tn = "fai_release";
259 my $fai_release_file_name;
260 our @fai_release_col_names = ("timestamp VARCHAR(14)", "fai_release VARCHAR(255)", "class VARCHAR(255)", "type VARCHAR(255)", "state VARCHAR(255)");
262 # holds all packages available from different repositories
263 our $packages_list_db;
264 our $packages_list_tn = "packages_list";
265 my $packages_list_file_name;
266 our @packages_list_col_names = ("distribution VARCHAR(255)", "package VARCHAR(255)", "version VARCHAR(255)", "section VARCHAR(255)", "description TEXT", "template LONGBLOB", "timestamp VARCHAR(14)");
267 my $outdir = "/tmp/packages_list_db";
269 # holds all messages which should be delivered to a user
270 our $messaging_db;
271 our $messaging_tn = "messaging";
272 our @messaging_col_names = ("id INTEGER", "subject TEXT", "message_from VARCHAR(255)", "message_to VARCHAR(255)",
273 "flag VARCHAR(255)", "direction VARCHAR(255)", "delivery_time VARCHAR(255)", "message TEXT", "timestamp VARCHAR(14)" );
274 my $messaging_file_name;
276 # path to directory to store client install log files
277 our $client_fai_log_dir = "/var/log/fai";
279 # queue which stores taskes until one of the $max_children children are ready to process the task
280 my @msgs_to_decrypt = qw();
281 my $max_children = 2;
284 # loop delay for job queue to look for opsi jobs
285 my $job_queue_opsi_delay = 10;
286 our $opsi_client;
287 our $opsi_url;
289 # Lifetime of logged in user information. If no update information comes after n seconds,
290 # the user is expeceted to be no longer logged in or the host is no longer running. Because
291 # of this, the user is deleted from login_users_db
292 our $logged_in_user_date_of_expiry = 600;
294 # List of month names, used in function daemon_log
295 my @monthnames = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
297 # List of accepted periodical xml tags related to cpan modul DateTime
298 our $check_periodic = {"months"=>'', "weeks"=>'', "days"=>'', "hours"=>'', "minutes"=>''};
301 %cfg_defaults = (
302 "General" => {
303 "log-file" => [\$log_file, "/var/log/gosa-si/gosa-si-server.log"],
304 "pid-file" => [\$pid_file, "/var/run/gosa-si/gosa-si-server.pid"],
305 },
306 "Server" => {
307 "ip" => [\$server_ip, "0.0.0.0"],
308 "port" => [\$server_port, "20081"],
309 "known-clients" => [\$known_clients_file_name, '/var/lib/gosa-si/clients.db' ],
310 "known-servers" => [\$known_server_file_name, '/var/lib/gosa-si/servers.db'],
311 "incoming" => [\$incoming_file_name, '/var/lib/gosa-si/incoming.db'],
312 "login-users" => [\$login_users_file_name, '/var/lib/gosa-si/users.db'],
313 "fai-server" => [\$fai_server_file_name, '/var/lib/gosa-si/fai_server.db'],
314 "fai-release" => [\$fai_release_file_name, '/var/lib/gosa-si/fai_release.db'],
315 "packages-list" => [\$packages_list_file_name, '/var/lib/gosa-si/packages.db'],
316 "messaging" => [\$messaging_file_name, '/var/lib/gosa-si/messaging.db'],
317 "foreign-clients" => [\$foreign_clients_file_name, '/var/lib/gosa-si/foreign_clients.db'],
318 "source-list" => [\$sources_list, '/etc/apt/sources.list'],
319 "repo-path" => [\$repo_path, '/srv/www/debian'],
320 "debian-arch" => [\$arch, 'i386'],
321 "ldap-uri" => [\$ldap_uri, ""],
322 "ldap-base" => [\$ldap_base, ""],
323 "ldap-admin-dn" => [\$ldap_admin_dn, ""],
324 "ldap-admin-password" => [\$ldap_admin_password, ""],
325 "ldap-version" => [\$ldap_version, 3],
326 "ldap-retry-sec" => [\$ldap_retry_sec, 10],
327 "gosa-unit-tag" => [\$gosa_unit_tag, ""],
328 "max-clients" => [\$max_clients, 10],
329 "wol-password" => [\$wake_on_lan_passwd, ""],
330 "mysql-username" => [\$mysql_username, "gosa_si"],
331 "mysql-password" => [\$mysql_password, ""],
332 "mysql-database" => [\$mysql_database, "gosa_si"],
333 "mysql-host" => [\$mysql_host, "127.0.0.1"],
334 },
335 "GOsaPackages" => {
336 "job-queue" => [\$job_queue_file_name, '/var/lib/gosa-si/jobs.db'],
337 "job-queue-loop-delay" => [\$job_queue_loop_delay, 3],
338 "messaging-db-loop-delay" => [\$messaging_db_loop_delay, 3],
339 "key" => [\$GosaPackages_key, "none"],
340 "new-systems-ou" => [\$new_systems_ou, 'ou=workstations,ou=systems'],
341 },
342 "ClientPackages" => {
343 "key" => [\$ClientPackages_key, "none"],
344 "user-date-of-expiry" => [\$logged_in_user_date_of_expiry, 600],
345 },
346 "ServerPackages"=> {
347 "enabled" => [\$serverPackages_enabled, "true"],
348 "address" => [\$foreign_server_string, ""],
349 "dns-lookup" => [\$dns_lookup, "true"],
350 "domain" => [\$server_domain, ""],
351 "key" => [\$ServerPackages_key, "none"],
352 "key-lifetime" => [\$foreign_servers_register_delay, 120],
353 "job-synchronization-enabled" => [\$job_synchronization, "true"],
354 "synchronization-loop" => [\$modified_jobs_loop_delay, 5],
355 },
356 "ArpHandler" => {
357 "enabled" => [\$arp_enabled, "false"],
358 "interface" => [\$arp_interface, "all"],
359 },
360 "Opsi" => {
361 "enabled" => [\$opsi_enabled, "false"],
362 "server" => [\$opsi_server, "localhost"],
363 "admin" => [\$opsi_admin, "opsi-admin"],
364 "password" => [\$opsi_password, "secret"],
365 },
367 );
369 #############################
370 #
371 # @brief Display error message and/or help text.
372 #
373 # In correspondence to previous GetOptions
374 #
375 # @param $text - string to print as error message
376 # @param $help - set true, if you want to show usage help
377 #
378 sub usage
379 {
380 my( $text, $help ) = @_;
382 $text = undef if( 'h' eq $text );
383 (defined $text) && print STDERR "\n$text\n";
385 if( (defined $help && $help)
386 || (!defined $help && !defined $text) )
387 {
388 print STDERR << "EOF";
390 usage: $0 [-hvf] [-c config] [-d number]
392 -h : this (help) message
393 -c <file> : config file (default: ${config})
394 -x <cfg> : dump configuration to stdout
395 ( 1 = current, 2 = default )
396 -f : foreground (don't fork)
397 -v : be verbose (multiple to increase verbosity)
398 'v': error logs
399 'vvv': warning plus error logs
400 'vvvvv': info plus warning logs
401 'vvvvvvv': debug plus info logs
402 -no-arp : starts gosa-si-server without connection to arp module
403 -d <int> : if verbose level is higher than 7x 'v' specified parts can be debugged
404 1 : report incoming messages
405 2 : report unencrypted outgoing messages
406 4 : report encrypting key for messages
407 8 : report decrypted incoming message and verification if the message complies gosa-si requirements
408 16 : message processing
409 32 : ldap connectivity
410 64 : database status and connectivity
411 128 : main process
412 256 : creation of packages_list_db
413 512 : ARP debug information
414 EOF
415 }
416 print( "\n" );
418 exit( -1 );
419 }
421 #############################
422 #
423 # @brief Manage gosa-si-client configuration.
424 #
425 # Will exit after successfull dump to stdout (type = 1 | 2)
426 #
427 # Dump type can be:
428 # 1: Current gosa-si-client configuration in config file (exit)
429 # 2: Default gosa-si-client configuration (exit)
430 # 3: Dump to logfile (no exit)
431 #
432 # @param int config type
433 #
434 sub dump_configuration {
436 my( $cfg_type ) = @_;
438 return if( ! defined $cfg_type );
440 if(1==$cfg_type ) {
441 print( "# Current gosa-si-server configuration\n" );
442 } elsif (2==$cfg_type) {
443 print( "# Default gosa-si-server configuration\n" );
444 } elsif (3==$cfg_type) {
445 daemon_log( "Dumping gosa-si-server configuration\n", 2 );
446 } else {
447 return;
448 }
450 foreach my $section (keys %cfg_defaults) {
451 if( 3 != $cfg_type ) {
452 print( "\n[${section}]\n" );
453 } else {
454 daemon_log( "\n [${section}]\n", 3 );
455 }
457 foreach my $param (sort( keys %{$cfg_defaults{ $section }})) {
458 my $pinfo = $cfg_defaults{ $section }{ $param };
459 my $value;
460 if (1==$cfg_type) {
461 if( defined( ${@$pinfo[ 0 ]} ) ) {
462 $value = ${@$pinfo[ 0 ]};
463 print( "$param=$value\n" );
464 } else {
465 print( "#${param}=\n" );
466 }
467 } elsif (2==$cfg_type) {
468 $value = @{$pinfo}[ 1 ];
469 if( defined( @$pinfo[ 1 ] ) ) {
470 $value = @{$pinfo}[ 1 ];
471 print( "$param=$value\n" );
472 } else {
473 print( "#${param}=\n" );
474 }
475 } elsif (3==$cfg_type) {
476 if( defined( ${@$pinfo[ 0 ]} ) ) {
477 $value = ${@$pinfo[ 0 ]};
478 daemon_log( " $param=$value\n", 3 )
479 }
480 }
481 }
482 }
485 # We just exit at stdout dump
486 if( 3 == $cfg_type ) {
487 daemon_log( "\n", 3 );
488 } else {
489 exit( 0 );
490 }
491 }
494 #=== FUNCTION ================================================================
495 # NAME: logging
496 # PARAMETERS: level - string - default 'info'
497 # msg - string -
498 # facility - string - default 'LOG_DAEMON'
499 # RETURNS: nothing
500 # DESCRIPTION: function for logging
501 #===============================================================================
502 sub daemon_log {
503 my( $msg, $level ) = @_;
504 if (not defined $msg) { return }
505 if (not defined $level) { $level = 1 }
506 my $to_be_logged = 0;
508 # Write log line if line level is lower than verbosity given in commandline
509 if ($level <= $verbose)
510 {
511 $to_be_logged = 1 ;
512 }
514 # Write if debug flag is set and bitstring matches
515 if ($debug_parts > 0)
516 {
517 my $tmp_level = ($level - 10 >= 0) ? $level - 10 : 0 ;
518 my $tmp_level_bitstring = unpack("B32", pack("N", $tmp_level));
519 if (int($debug_parts_bitstring & $tmp_level_bitstring))
520 {
521 $to_be_logged = 1;
522 }
523 }
525 if ($to_be_logged)
526 {
527 if(defined $log_file){
528 my $open_log_fh = sysopen(LOG_HANDLE, $log_file, O_WRONLY | O_CREAT | O_APPEND , 0440);
529 if(not $open_log_fh) {
530 print STDERR "cannot open $log_file: $!";
531 return;
532 }
533 # Check owner and group of log_file and update settings if necessary
534 my ($dev, $ino, $mode, $nlink, $uid, $gid, $rdev, $size, $atime, $mtime, $ctime, $blksize, $blocks) = stat($log_file);
535 if((not $uid eq $root_uid) || (not $gid eq $adm_gid)) {
536 chown($root_uid, $adm_gid, $log_file);
537 }
539 # Prepare time string for log message
540 my ($seconds,$minutes,$hours,$monthday,$month,$year,$weekday,$yearday,$sommertime) = localtime(time);
541 $hours = $hours < 10 ? $hours = "0".$hours : $hours;
542 $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes;
543 $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds;
544 $month = $monthnames[$month];
545 $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday;
546 $year+=1900;
548 # Microseconds since epoch
549 my $microSeconds = sprintf("%.2f", &Time::HiRes::clock_gettime());
550 $microSeconds =~ s/^\d*(.\d\d)$/$1/;
552 # Build log message and write it to log file and commandline
553 chomp($msg);
554 my $log_msg = "$month $monthday $hours:$minutes:$seconds$microSeconds $0 $msg\n";
555 flock(LOG_HANDLE, LOCK_EX);
556 seek(LOG_HANDLE, 0, 2);
557 print LOG_HANDLE $log_msg;
558 flock(LOG_HANDLE, LOCK_UN);
559 if( $foreground )
560 {
561 print STDERR $log_msg;
562 }
563 close( LOG_HANDLE );
564 }
565 }
566 }
568 #=== FUNCTION ================================================================
569 # NAME: import_modules
570 # PARAMETERS: module_path - string - abs. path to the directory the modules
571 # are stored
572 # RETURNS: nothing
573 # DESCRIPTION: each file in module_path which ends with '.pm' and activation
574 # state is on is imported by "require 'file';"
575 #===============================================================================
576 sub import_modules {
577 if (not -e $modules_path) {
578 daemon_log("0 ERROR: cannot find directory or directory is not readable: $modules_path", 1);
579 }
581 opendir (DIR, $modules_path) or die "ERROR while loading modules from directory $modules_path : $!\n";
583 while (defined (my $file = readdir (DIR))) {
584 if (not $file =~ /(\S*?).pm$/) {
585 next;
586 }
587 my $mod_name = $1;
589 # ArpHandler switch
590 if( $file =~ /ArpHandler.pm/ ) {
591 if( $arp_enabled eq "false" ) { next; }
592 }
594 # ServerPackages switch
595 if ($file eq "ServerPackages.pm" && $serverPackages_enabled eq "false")
596 {
597 $dns_lookup = "false";
598 next;
599 }
601 eval { require $file; };
602 if ($@) {
603 daemon_log("0 ERROR: gosa-si-server could not load module $file", 1);
604 daemon_log("$@", 1);
605 exit;
606 } else {
607 my $info = eval($mod_name.'::get_module_info()');
608 # Only load module if get_module_info() returns a non-null object
609 if( $info ) {
610 my ($input_address, $input_key, $event_hash) = @{$info};
611 $known_modules->{$mod_name} = $info;
612 daemon_log("0 INFO: module $mod_name loaded", 5);
613 }
614 }
615 }
616 close (DIR);
617 }
619 #=== FUNCTION ================================================================
620 # NAME: password_check
621 # PARAMETERS: nothing
622 # RETURNS: nothing
623 # DESCRIPTION: escalates an critical error if two modules exist which are avaialable by
624 # the same password
625 #===============================================================================
626 sub password_check {
627 my $passwd_hash = {};
628 while (my ($mod_name, $mod_info) = each %$known_modules) {
629 my $mod_passwd = @$mod_info[1];
630 if (not defined $mod_passwd) { next; }
631 if (not exists $passwd_hash->{$mod_passwd}) {
632 $passwd_hash->{$mod_passwd} = $mod_name;
634 # escalates critical error
635 } else {
636 &daemon_log("0 ERROR: two loaded modules do have the same password. Please modify the 'key'-parameter in config file");
637 &daemon_log("0 ERROR: module='$mod_name' and module='".$passwd_hash->{$mod_passwd}."'");
638 exit( -1 );
639 }
640 }
642 }
645 #=== FUNCTION ================================================================
646 # NAME: sig_int_handler
647 # PARAMETERS: signal - string - signal came from system
648 # RETURNS: nothing
649 # DESCRIPTION: handle tasks to be done before signal becomes active
650 #===============================================================================
651 sub sig_int_handler {
652 my ($signal) = @_;
654 # if (defined($ldap_handle)) {
655 # $ldap_handle->disconnect;
656 # }
657 # TODO all ldap connections shoudl be closed
659 daemon_log("shutting down gosa-si-server", 1);
661 # asking the poe kernel to shutdown the server
662 $global_kernel->yield(TCP_SERVER => 'shutdown');
664 # removing the pidfile
665 $pid->remove or warn "Could not remove $pid_file\n";
667 exit(0);
668 }
669 $SIG{INT} = \&sig_int_handler;
672 sub check_key_and_xml_validity {
673 my ($crypted_msg, $module_key, $session_id) = @_;
674 my $msg;
675 my $msg_hash;
676 my $error_string;
677 eval{
678 $msg = &decrypt_msg($crypted_msg, $module_key);
680 if ($msg =~ /<xml>/i){
681 $msg =~ s/\s+/ /g; # just for better daemon_log
682 daemon_log("$session_id DEBUG: decrypted_msg: \n$msg", 18);
683 $msg_hash = $xml->XMLin($msg, ForceArray=>1);
685 ##############
686 # check header
687 if( not exists $msg_hash->{'header'} ) { die "no header specified"; }
688 my $header_l = $msg_hash->{'header'};
689 if( (1 > @{$header_l}) || ( ( 'HASH' eq ref @{$header_l}[0]) && (1 > keys %{@{$header_l}[0]}) ) ) { die 'empty header tag'; }
690 if( 1 < @{$header_l} ) { die 'more than one header specified'; }
691 my $header = @{$header_l}[0];
692 if( 0 == length $header) { die 'empty string in header tag'; }
694 ##############
695 # check source
696 if( not exists $msg_hash->{'source'} ) { die "no source specified"; }
697 my $source_l = $msg_hash->{'source'};
698 if( (1 > @{$source_l}) || ( ( 'HASH' eq ref @{$source_l}[0]) && (1 > keys %{@{$source_l}[0]}) ) ) { die 'empty source tag'; }
699 if( 1 < @{$source_l} ) { die 'more than one source specified'; }
700 my $source = @{$source_l}[0];
701 if( 0 == length $source) { die 'source error'; }
703 ##############
704 # check target
705 if( not exists $msg_hash->{'target'} ) { die "no target specified"; }
706 my $target_l = $msg_hash->{'target'};
707 if( (1 > @{$target_l}) || ( ('HASH' eq ref @{$target_l}[0]) && (1 > keys %{@{$target_l}[0]}) ) ) { die 'empty target tag'; }
708 }
709 };
710 if($@) {
711 daemon_log("$session_id ERROR: do not understand the message: $@", 1);
712 $msg = undef;
713 $msg_hash = undef;
714 }
716 return ($msg, $msg_hash);
717 }
720 sub check_outgoing_xml_validity {
721 my ($msg, $session_id) = @_;
723 my $msg_hash;
724 eval{
725 $msg_hash = $xml->XMLin($msg, ForceArray=>1);
727 ##############
728 # check header
729 my $header_l = $msg_hash->{'header'};
730 if( 1 != @{$header_l} ) {
731 die 'no or more than one headers specified';
732 }
733 my $header = @{$header_l}[0];
734 if( 0 == length $header) {
735 die 'header has length 0';
736 }
738 ##############
739 # check source
740 my $source_l = $msg_hash->{'source'};
741 if( 1 != @{$source_l} ) {
742 die 'no or more than 1 sources specified';
743 }
744 my $source = @{$source_l}[0];
745 if( 0 == length $source) {
746 die 'source has length 0';
747 }
749 # Check if source contains hostname instead of ip address
750 if($source =~ /^[a-z][\w\-\.]+:\d+$/i) {
751 my ($hostname,$port) = split(/:/, $source);
752 my $ip_address = inet_ntoa(scalar gethostbyname($hostname));
753 if(defined($ip_address) && $ip_address =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/ && $port =~ /^\d+$/) {
754 # Write ip address to $source variable
755 $source = "$ip_address:$port";
756 $msg_hash->{source}[0] = $source ;
757 $msg =~ s/<source>.*<\/source>/<source>$source<\/source>/;
758 }
759 }
760 unless( $source =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
761 $source =~ /^GOSA$/i) {
762 die "source '$source' is neither a complete ip-address with port nor 'GOSA'";
763 }
765 ##############
766 # check target
767 my $target_l = $msg_hash->{'target'};
768 if( 0 == @{$target_l} ) {
769 die "no targets specified";
770 }
771 foreach my $target (@$target_l) {
772 if( 0 == length $target) {
773 die "target has length 0";
774 }
775 unless( $target =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
776 $target =~ /^GOSA$/i ||
777 $target =~ /^\*$/ ||
778 $target =~ /KNOWN_SERVER/i ||
779 $target =~ /JOBDB/i ||
780 $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 ){
781 die "target '$target' is not a complete ip-address with port or a valid target name or a mac-address";
782 }
783 }
784 };
785 if($@) {
786 daemon_log("$session_id ERROR: outgoing msg is not gosa-si envelope conform: $@", 1);
787 daemon_log("$@ ".(defined($msg) && length($msg)>0)?$msg:"Empty Message", 1);
788 $msg_hash = undef;
789 }
791 return ($msg, $msg_hash);
792 }
795 sub input_from_known_server {
796 my ($input, $remote_ip, $session_id) = @_ ;
797 my ($msg, $msg_hash, $module);
799 my $sql_statement= "SELECT * FROM known_server";
800 my $query_res = $known_server_db->select_dbentry( $sql_statement );
802 while( my ($hit_num, $hit) = each %{ $query_res } ) {
803 my $host_name = $hit->{hostname};
804 if( not $host_name =~ "^$remote_ip") {
805 next;
806 }
807 my $host_key = $hit->{hostkey};
808 daemon_log("$session_id DEBUG: input_from_known_server: host_name: $host_name", 14);
809 daemon_log("$session_id DEBUG: input_from_known_server: host_key: $host_key", 14);
811 # check if module can open msg envelope with module key
812 my ($tmp_msg, $tmp_msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
813 if( (!$tmp_msg) || (!$tmp_msg_hash) ) {
814 daemon_log("$session_id DEBUG: input_from_known_server: deciphering raise error", 14);
815 daemon_log("$@", 14);
816 next;
817 }
818 else {
819 $msg = $tmp_msg;
820 $msg_hash = $tmp_msg_hash;
821 $module = "ServerPackages";
822 daemon_log("$session_id DEBUG: check_key_and_xml_validity... ok", 14);
823 last;
824 }
825 }
827 if( (!$msg) || (!$msg_hash) || (!$module) ) {
828 daemon_log("$session_id DEBUG: Incoming message is not from a known server", 14);
829 }
831 return ($msg, $msg_hash, $module);
832 }
835 sub input_from_known_client {
836 my ($input, $remote_ip, $session_id) = @_ ;
837 my ($msg, $msg_hash, $module);
839 my $sql_statement= "SELECT * FROM known_clients";
840 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
841 while( my ($hit_num, $hit) = each %{ $query_res } ) {
842 my $host_name = $hit->{hostname};
843 if( not $host_name =~ /^$remote_ip/) {
844 next;
845 }
846 my $host_key = $hit->{hostkey};
847 &daemon_log("$session_id DEBUG: input_from_known_client: host_name: $host_name", 14);
848 &daemon_log("$session_id DEBUG: input_from_known_client: host_key: $host_key", 14);
850 # check if module can open msg envelope with module key
851 ($msg, $msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
853 if( (!$msg) || (!$msg_hash) ) {
854 &daemon_log("$session_id DEGUG: input_from_known_client: deciphering raise error", 14);
855 next;
856 }
857 else {
858 $module = "ClientPackages";
859 daemon_log("$session_id DEBUG: check_key_and_xml_validity... ok", 14);
860 last;
861 }
862 }
864 if( (!$msg) || (!$msg_hash) || (!$module) ) {
865 &daemon_log("$session_id DEBUG: Incoming message is not from a known client", 14);
866 }
868 return ($msg, $msg_hash, $module);
869 }
872 sub input_from_unknown_host {
873 no strict "refs";
874 my ($input, $session_id) = @_ ;
875 my ($msg, $msg_hash, $module);
876 my $error_string;
878 my %act_modules = %$known_modules;
880 while( my ($mod, $info) = each(%act_modules)) {
882 # check a key exists for this module
883 my $module_key = ${$mod."_key"};
884 if( not defined $module_key ) {
885 if( $mod eq 'ArpHandler' ) {
886 next;
887 }
888 daemon_log("$session_id ERROR: no key specified in config file for $mod", 1);
889 next;
890 }
891 daemon_log("$session_id DEBUG: $mod: $module_key", 14);
893 # check if module can open msg envelope with module key
894 ($msg, $msg_hash) = &check_key_and_xml_validity($input, $module_key, $session_id);
895 if( (not defined $msg) || (not defined $msg_hash) ) {
896 next;
897 } else {
898 $module = $mod;
899 daemon_log("$session_id DEBUG: check_key_and_xml_validity... ok", 18);
900 last;
901 }
902 }
904 if( (!$msg) || (!$msg_hash) || (!$module)) {
905 daemon_log("$session_id DEBUG: Incoming message is not from an unknown host", 14);
906 }
908 return ($msg, $msg_hash, $module);
909 }
912 sub create_ciphering {
913 my ($passwd) = @_;
914 if((!defined($passwd)) || length($passwd)==0) {
915 $passwd = "";
916 }
917 $passwd = substr(md5_hex("$passwd") x 32, 0, 32);
918 my $iv = substr(md5_hex('GONICUS GmbH'),0, 16);
919 my $my_cipher = Crypt::Rijndael->new($passwd , Crypt::Rijndael::MODE_CBC());
920 $my_cipher->set_iv($iv);
921 return $my_cipher;
922 }
925 sub encrypt_msg {
926 my ($msg, $key) = @_;
927 my $my_cipher = &create_ciphering($key);
928 my $len;
929 {
930 use bytes;
931 $len= 16-length($msg)%16;
932 }
933 $msg = "\0"x($len).$msg;
934 $msg = $my_cipher->encrypt($msg);
935 chomp($msg = &encode_base64($msg));
936 # there are no newlines allowed inside msg
937 $msg=~ s/\n//g;
938 return $msg;
939 }
942 sub decrypt_msg {
944 my ($msg, $key) = @_ ;
945 $msg = &decode_base64($msg);
946 my $my_cipher = &create_ciphering($key);
947 $msg = $my_cipher->decrypt($msg);
948 $msg =~ s/\0*//g;
949 return $msg;
950 }
953 sub get_encrypt_key {
954 my ($target) = @_ ;
955 my $encrypt_key;
956 my $error = 0;
958 # target can be in known_server
959 if( not defined $encrypt_key ) {
960 my $sql_statement= "SELECT * FROM known_server WHERE hostname='$target'";
961 my $query_res = $known_server_db->select_dbentry( $sql_statement );
962 while( my ($hit_num, $hit) = each %{ $query_res } ) {
963 my $host_name = $hit->{hostname};
964 if( $host_name ne $target ) {
965 next;
966 }
967 $encrypt_key = $hit->{hostkey};
968 last;
969 }
970 }
972 # target can be in known_client
973 if( not defined $encrypt_key ) {
974 my $sql_statement= "SELECT * FROM known_clients WHERE hostname='$target'";
975 my $query_res = $known_clients_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 return $encrypt_key;
987 }
990 #=== FUNCTION ================================================================
991 # NAME: open_socket
992 # PARAMETERS: PeerAddr string something like 192.168.1.1 or 192.168.1.1:10000
993 # [PeerPort] string necessary if port not appended by PeerAddr
994 # RETURNS: socket IO::Socket::INET
995 # DESCRIPTION: open a socket to PeerAddr
996 #===============================================================================
997 sub open_socket {
998 my ($PeerAddr, $PeerPort) = @_ ;
999 if(defined($PeerPort)){
1000 $PeerAddr = $PeerAddr.":".$PeerPort;
1001 }
1002 my $socket;
1003 $socket = new IO::Socket::INET(PeerAddr => $PeerAddr,
1004 Porto => "tcp",
1005 Type => SOCK_STREAM,
1006 Timeout => 5,
1007 );
1008 if(not defined $socket) {
1009 return;
1010 }
1011 # &daemon_log("DEBUG: open_socket: $PeerAddr", 7);
1012 return $socket;
1013 }
1016 sub send_msg_to_target {
1017 my ($msg, $address, $encrypt_key, $msg_header, $session_id) = @_ ;
1018 my $error = 0;
1019 my $header;
1020 my $timestamp = &get_time();
1021 my $new_status;
1022 my $act_status;
1023 my ($sql_statement, $res);
1025 if( $msg_header ) {
1026 $header = "'$msg_header'-";
1027 } else {
1028 $header = "";
1029 }
1031 # Memorize own source address
1032 my $own_source_address = &get_local_ip_for_remote_ip(sprintf("%s", $address =~ /^([0-9\.]*?):.*$/));
1033 $own_source_address .= ":".$server_port;
1035 # Patch 0.0.0.0 source to real address
1036 $msg =~ s/<source>(0\.0\.0\.0):(\d*?)<\/source>/<source>$own_source_address<\/source>/s;
1037 # Patch GOSA source to real address and add forward_to_gosa tag
1038 $msg =~ s/<source>GOSA<\/source>/<source>$own_source_address<\/source> <forward_to_gosa>$own_source_address,$session_id<\/forward_to_gosa>/ ;
1040 # encrypt xml msg
1041 my $crypted_msg = &encrypt_msg($msg, $encrypt_key);
1043 # opensocket
1044 my $socket = &open_socket($address);
1045 if( !$socket ) {
1046 daemon_log("$session_id ERROR: Cannot open socket to host '$address'. Message processing aborted!", 1);
1047 $error++;
1048 }
1050 if( $error == 0 ) {
1051 # send xml msg
1052 print $socket $crypted_msg.";$own_source_address\n";
1053 daemon_log("$session_id INFO: send ".$header."msg to $address", 5);
1054 daemon_log("$session_id DEBUG: message:\n$msg", 12);
1056 }
1058 # close socket in any case
1059 if( $socket ) {
1060 close $socket;
1061 }
1063 if( $error > 0 ) { $new_status = "down"; }
1064 else { $new_status = $msg_header; }
1067 # known_clients
1068 $sql_statement = "SELECT * FROM $known_clients_tn WHERE hostname='$address'";
1069 $res = $known_clients_db->select_dbentry($sql_statement);
1070 if( keys(%$res) == 1) {
1071 $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
1072 if ($act_status eq "down" && $new_status eq "down") {
1073 $sql_statement = "DELETE FROM known_clients WHERE hostname='$address'";
1074 $res = $known_clients_db->del_dbentry($sql_statement);
1075 daemon_log("$session_id WARNING: failed 2x to send msg to host '$address', delete host from known_clients", 3);
1076 } else {
1077 $sql_statement = "UPDATE known_clients SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
1078 $res = $known_clients_db->update_dbentry($sql_statement);
1079 if($new_status eq "down"){
1080 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
1081 } else {
1082 daemon_log("$session_id DEBUG: set '$address' from status '$act_status' to '$new_status'", 138);
1083 }
1084 }
1085 }
1087 # known_server
1088 $sql_statement = "SELECT * FROM $known_server_tn WHERE hostname='$address'";
1089 $res = $known_server_db->select_dbentry($sql_statement);
1090 if( keys(%$res) == 1) {
1091 $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
1092 if ($act_status eq "down" && $new_status eq "down") {
1093 $sql_statement = "DELETE FROM known_server WHERE hostname='$address'";
1094 $res = $known_server_db->del_dbentry($sql_statement);
1095 daemon_log("$session_id WARNING: failed 2x to send a message to host '$address', delete host from known_server", 3);
1096 }
1097 else {
1098 $sql_statement = "UPDATE known_server SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
1099 $res = $known_server_db->update_dbentry($sql_statement);
1100 if($new_status eq "down"){
1101 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
1102 } else {
1103 daemon_log("$session_id DEBUG: set '$address' from status '$act_status' to '$new_status'", 138);
1104 }
1105 }
1106 }
1107 return $error;
1108 }
1111 sub update_jobdb_status_for_send_msgs {
1112 my ($session_id, $answer, $error) = @_;
1113 if( $answer =~ /<jobdb_id>(\d+)<\/jobdb_id>/ ) {
1114 &daemon_log("$session_id DEBUG: try to update job status", 138);
1115 my $jobdb_id = $1;
1117 $answer =~ /<header>(.*)<\/header>/;
1118 my $job_header = $1;
1120 $answer =~ /<target>(.*)<\/target>/;
1121 my $job_target = $1;
1123 # Sending msg failed
1124 if( $error ) {
1126 # Set jobs to done, jobs do not need to deliver their message in any case
1127 if (($job_header eq "trigger_action_localboot")
1128 ||($job_header eq "trigger_action_lock")
1129 ||($job_header eq "trigger_action_halt")
1130 ) {
1131 my $sql_statement = "UPDATE $job_queue_tn SET status='done' WHERE id=$jobdb_id";
1132 my $res = $job_db->update_dbentry($sql_statement);
1134 # Reactivate jobs, jobs need to deliver their message
1135 } elsif (($job_header eq "trigger_action_activate")
1136 ||($job_header eq "trigger_action_update")
1137 ||($job_header eq "trigger_action_reinstall")
1138 ||($job_header eq "trigger_activate_new")
1139 ) {
1140 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) {
1141 &reactivate_job_with_delay($session_id, $job_target, $job_header, 30 );
1142 } else {
1143 # If we don't have the mac adress at this time, we use the plainname
1144 my $plainname_result = $job_db->select_dbentry("SELECT plainname from jobs where id=$jobdb_id");
1145 my $plainname = $job_target;
1146 if ((keys(%$plainname_result) > 0) ) {
1147 $plainname = $plainname_result->{1}->{$job_target};
1148 }
1149 &reactivate_job_with_delay($session_id, $plainname, $job_header, 30 );
1150 }
1151 # For all other messages
1152 } else {
1153 my $sql_statement = "UPDATE $job_queue_tn ".
1154 "SET status='error', result='can not deliver msg, please consult log file' ".
1155 "WHERE id=$jobdb_id";
1156 my $res = $job_db->update_dbentry($sql_statement);
1157 }
1159 # Sending msg was successful
1160 } else {
1161 # Set jobs localboot, lock, activate, halt, reboot and wake to done
1162 # jobs reinstall, update, inst_update do themself setting to done
1163 if (($job_header eq "trigger_action_localboot")
1164 ||($job_header eq "trigger_action_lock")
1165 ||($job_header eq "trigger_action_activate")
1166 ||($job_header eq "trigger_action_halt")
1167 ||($job_header eq "trigger_action_reboot")
1168 ||($job_header eq "trigger_action_wake")
1169 ||($job_header eq "trigger_wake")
1170 ) {
1172 my $sql_statement = "UPDATE $job_queue_tn ".
1173 "SET status='done' ".
1174 "WHERE id=$jobdb_id AND status='processed'";
1175 my $res = $job_db->update_dbentry($sql_statement);
1176 } else {
1177 &daemon_log("$session_id DEBUG: sending message succeed but cannot update job status.", 138);
1178 }
1179 }
1180 } else {
1181 &daemon_log("$session_id DEBUG: cannot update job status, msg has no jobdb_id-tag.", 138);
1182 }
1183 }
1185 sub reactivate_job_with_delay {
1186 my ($session_id, $target, $header, $delay) = @_ ;
1187 # 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
1189 if (not defined $delay) { $delay = 30 } ;
1190 my $delay_timestamp = &calc_timestamp(&get_time(), "plus", $delay);
1192 my $sql = "UPDATE $job_queue_tn Set timestamp='$delay_timestamp', status='waiting' WHERE (macaddress LIKE '$target' OR plainname LIKE '$target') AND headertag='$header'";
1193 my $res = $job_db->update_dbentry($sql);
1194 daemon_log("$session_id INFO: '$header'-job will be reactivated at '$delay_timestamp' ".
1195 "cause client '$target' is currently not available", 5);
1196 return;
1197 }
1200 sub sig_handler {
1201 my ($kernel, $signal) = @_[KERNEL, ARG0] ;
1202 daemon_log("0 INFO got signal '$signal'", 1);
1203 $kernel->sig_handled();
1204 return;
1205 }
1208 sub msg_to_decrypt {
1209 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
1210 my $session_id = $session->ID;
1211 my ($msg, $msg_hash, $module);
1212 my $error = 0;
1214 # fetch new msg out of @msgs_to_decrypt
1215 my $tmp_next_msg = shift @msgs_to_decrypt;
1216 my ($next_msg, $msg_source) = split(/;/, $tmp_next_msg);
1218 # msg is from a new client or gosa
1219 ($msg, $msg_hash, $module) = &input_from_unknown_host($next_msg, $session_id);
1221 # msg is from a gosa-si-server
1222 if(((!$msg) || (!$msg_hash) || (!$module)) && ($serverPackages_enabled eq "true")){
1223 if (not defined $msg_source)
1224 {
1225 # Only needed, to be compatible with older gosa-si-server versions
1226 ($msg, $msg_hash, $module) = &input_from_known_server($next_msg, $heap->{'remote_ip'}, $session_id);
1227 }
1228 else
1229 {
1230 ($msg, $msg_hash, $module) = &input_from_known_server($next_msg, $msg_source, $session_id);
1231 }
1232 }
1233 # msg is from a gosa-si-client
1234 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1235 if (not defined $msg_source)
1236 {
1237 # Only needed, to be compatible with older gosa-si-server versions
1238 ($msg, $msg_hash, $module) = &input_from_known_client($next_msg, $heap->{'remote_ip'}, $session_id);
1239 }
1240 else
1241 {
1242 ($msg, $msg_hash, $module) = &input_from_known_client($next_msg, $msg_source, $session_id);
1243 }
1244 }
1245 # an error occurred
1246 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1247 # If an incoming msg could not be decrypted (maybe a wrong key), decide if msg comes from a client
1248 # or a server. In case of a client, send a ping. If the client could not understand a msg from its
1249 # server the client cause a re-registering process. In case of a server, decrease update_time in kown_server_db
1250 # and trigger a re-registering process for servers
1251 if (defined $msg_source && $msg_source =~ /:$server_port$/ && $serverPackages_enabled eq "true")
1252 {
1253 daemon_log("$session_id WARNING: Cannot understand incoming msg from server '$msg_source'. Cause re-registration process for servers.", 3);
1254 my $update_statement = "UPDATE $known_server_tn SET update_time='19700101000000' WHERE hostname='$msg_source'";
1255 daemon_log("$session_id DEBUG: $update_statement", 7);
1256 my $upadte_res = $known_server_db->exec_statement($update_statement);
1257 $kernel->yield("register_at_foreign_servers");
1258 }
1259 elsif ((defined $msg_source) && (not $msg_source =~ /:$server_port$/))
1260 {
1261 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);
1262 #my $remote_ip = $heap->{'remote_ip'};
1263 #my $remote_port = $heap->{'remote_port'};
1264 my $ping_msg = "<xml> <header>gosa_ping</header> <source>$server_address</source><target>$msg_source</target></xml>";
1265 my ($test_error, $test_error_string) = &send_msg_to_target($ping_msg, "$msg_source", "dummy-key", "gosa_ping", $session_id);
1266 daemon_log("$session_id WARNING: Sending msg to cause re-registering: $ping_msg", 3);
1267 }
1268 else
1269 {
1270 my $foreign_host = defined $msg_source ? $msg_source : $heap->{'remote_ip'};
1271 daemon_log("$session_id ERROR: Incoming message from host '$foreign_host' cannot be understood. Processing aborted!", 1);
1272 daemon_log("$session_id DEBUG: Aborted message: $tmp_next_msg", 11);
1273 }
1275 $error++
1276 }
1279 my $header;
1280 my $target;
1281 my $source;
1282 my $done = 0;
1283 my $sql;
1284 my $res;
1286 # check whether this message should be processed here
1287 if ($error == 0) {
1288 $header = @{$msg_hash->{'header'}}[0];
1289 $target = @{$msg_hash->{'target'}}[0];
1290 $source = @{$msg_hash->{'source'}}[0];
1291 my $not_found_in_known_clients_db = 0;
1292 my $not_found_in_known_server_db = 0;
1293 my $not_found_in_foreign_clients_db = 0;
1294 my $local_address;
1295 my $local_mac;
1296 my ($target_ip, $target_port) = split(':', $target);
1298 # Determine the local ip address if target is an ip address
1299 if ($target =~ /^\d+\.\d+\.\d+\.\d+:\d+$/) {
1300 $local_address = &get_local_ip_for_remote_ip($target_ip).":$server_port";
1301 } else {
1302 $local_address = $server_address;
1303 }
1305 # Determine the local mac address if target is a mac address
1306 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) {
1307 my $loc_ip = &get_local_ip_for_remote_ip($heap->{'remote_ip'});
1308 my $network_interface= &get_interface_for_ip($loc_ip);
1309 $local_mac = &get_mac_for_interface($network_interface);
1310 } else {
1311 $local_mac = $server_mac_address;
1312 }
1314 # target and source is equal to GOSA -> process here
1315 if (not $done) {
1316 if ($target eq "GOSA" && $source eq "GOSA") {
1317 $done = 1;
1318 &daemon_log("$session_id DEBUG: target and source is 'GOSA' -> process '$header' here", 11);
1319 }
1320 }
1322 # target is own address without forward_to_gosa-tag -> process here
1323 if (not $done) {
1324 #if ((($target eq $local_address) || ($target eq $local_mac) ) && (not exists $msg_hash->{'forward_to_gosa'})) {
1325 if (($target eq $local_address) && (not exists $msg_hash->{'forward_to_gosa'})) {
1326 $done = 1;
1327 if ($source eq "GOSA") {
1328 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1329 }
1330 &daemon_log("$session_id DEBUG: target is own address without forward_to_gosa-tag -> process '$header' here", 11);
1331 }
1332 }
1334 # target is own address with forward_to_gosa-tag not pointing to myself -> process here
1335 if (not $done) {
1336 my $forward_to_gosa = @{$msg_hash->{'forward_to_gosa'}}[0];
1337 my $gosa_at;
1338 my $gosa_session_id;
1339 if (($target eq $local_address) && (defined $forward_to_gosa)){
1340 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1341 if ($gosa_at ne $local_address) {
1342 $done = 1;
1343 &daemon_log("$session_id DEBUG: target is own address with forward_to_gosa-tag not pointing to myself -> process '$header' here", 11);
1344 }
1345 }
1346 }
1348 # Target is a client address and there is a processing function within a plugin -> process loaclly
1349 if (not $done)
1350 {
1351 # Check if target is a client address
1352 $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1353 $res = $known_clients_db->select_dbentry($sql);
1354 if ((keys(%$res) > 0) )
1355 {
1356 my $hostname = $res->{1}->{'hostname'};
1357 my $reduced_header = $header;
1358 $reduced_header =~ s/gosa_//;
1359 # Check if there is a processing function within a plugin
1360 if (exists $known_functions->{$reduced_header})
1361 {
1362 $msg =~ s/<target>\S*<\/target>/<target>$hostname<\/target>/;
1363 $done = 1;
1364 &daemon_log("$session_id DEBUG: Target is client address with processing function within a plugin -> process '$header' here", 11);
1365 }
1366 }
1367 }
1369 # If header has a 'job_' prefix, do always process message locally
1370 # which means put it into job queue
1371 if ((not $done) && ($header =~ /job_/))
1372 {
1373 $done = 1;
1374 &daemon_log("$session_id DEBUG: Header has a 'job_' prefix. Put it into job queue. -> process '$header' here", 11);
1375 }
1377 # if message should be processed here -> add message to incoming_db
1378 if ($done) {
1379 # if a 'job_' or a 'gosa_' message comes from a foreign server, fake module from
1380 # ServerPackages to GosaPackages so gosa-si-server knows how to process this kind of messages
1381 if ($header =~ /^gosa_/ || $header =~ /^job_/) {
1382 $module = "GosaPackages";
1383 }
1385 my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1386 primkey=>[],
1387 headertag=>$header,
1388 targettag=>$target,
1389 xmlmessage=>&encode_base64($msg),
1390 timestamp=>&get_time,
1391 module=>$module,
1392 sessionid=>$session_id,
1393 } );
1394 $kernel->yield('watch_for_next_tasks');
1395 }
1397 # target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa
1398 if (not $done) {
1399 my $forward_to_gosa = @{$msg_hash->{'forward_to_gosa'}}[0];
1400 my $gosa_at;
1401 my $gosa_session_id;
1402 if (($target eq $local_address) && (defined $forward_to_gosa)){
1403 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1404 if ($gosa_at eq $local_address) {
1405 my $session_reference = $kernel->ID_id_to_session($gosa_session_id);
1406 if( defined $session_reference ) {
1407 $heap = $session_reference->get_heap();
1408 }
1409 if(exists $heap->{'client'}) {
1410 $msg = &encrypt_msg($msg, $GosaPackages_key);
1411 $heap->{'client'}->put($msg);
1412 &daemon_log("$session_id DEBUG: incoming '$header' message forwarded to GOsa", 11);
1413 }
1414 $done = 1;
1415 &daemon_log("$session_id DEBUG: target is own address with forward_to_gosa-tag pointing at myself -> forward '$header' to gosa", 11);
1416 }
1417 }
1419 }
1421 # target is a client address in known_clients -> forward to client
1422 if (not $done) {
1423 $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1424 $res = $known_clients_db->select_dbentry($sql);
1425 if (keys(%$res) > 0)
1426 {
1427 $done = 1;
1428 &daemon_log("$session_id DEBUG: target is a client address in known_clients -> forward '$header' to client", 11);
1429 my $hostkey = $res->{1}->{'hostkey'};
1430 my $hostname = $res->{1}->{'hostname'};
1431 $msg =~ s/<target>\S*<\/target>/<target>$hostname<\/target>/;
1432 $msg =~ s/<header>gosa_/<header>/;
1433 my $error= &send_msg_to_target($msg, $hostname, $hostkey, $header, $session_id);
1434 if ($error) {
1435 &daemon_log("$session_id ERROR: Some problems occurred while trying to send msg to client '$hostname': $msg", 1);
1436 }
1437 }
1438 else
1439 {
1440 $not_found_in_known_clients_db = 1;
1441 }
1442 }
1444 # target is a client address in foreign_clients -> forward to registration server
1445 if (not $done) {
1446 $sql = "SELECT * FROM $foreign_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1447 $res = $foreign_clients_db->select_dbentry($sql);
1448 if (keys(%$res) > 0) {
1449 my $hostname = $res->{1}->{'hostname'};
1450 my ($host_ip, $host_port) = split(/:/, $hostname);
1451 my $local_address = &get_local_ip_for_remote_ip($host_ip).":$server_port";
1452 my $regserver = $res->{1}->{'regserver'};
1453 my $sql = "SELECT * FROM $known_server_tn WHERE hostname='$regserver'";
1454 my $res = $known_server_db->select_dbentry($sql);
1455 if (keys(%$res) > 0) {
1456 my $regserver_key = $res->{1}->{'hostkey'};
1457 $msg =~ s/<source>GOSA<\/source>/<source>$local_address<\/source>/;
1458 $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1459 if ($source eq "GOSA") {
1460 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1461 }
1462 my $error= &send_msg_to_target($msg, $regserver, $regserver_key, $header, $session_id);
1463 if ($error) {
1464 &daemon_log("$session_id ERROR: some problems occurred while trying to send msg to registration server: $msg", 1);
1465 }
1466 }
1467 $done = 1;
1468 &daemon_log("$session_id DEBUG: target is a client address in foreign_clients -> forward '$header' to registration server", 11);
1469 } else {
1470 $not_found_in_foreign_clients_db = 1;
1471 }
1472 }
1474 # target is a server address -> forward to server
1475 if (not $done) {
1476 $sql = "SELECT * FROM $known_server_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1477 $res = $known_server_db->select_dbentry($sql);
1478 if (keys(%$res) > 0) {
1479 my $hostkey = $res->{1}->{'hostkey'};
1481 if ($source eq "GOSA") {
1482 $msg =~ s/<source>GOSA<\/source>/<source>$local_address<\/source>/;
1483 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1485 }
1487 &send_msg_to_target($msg, $target, $hostkey, $header, $session_id);
1488 $done = 1;
1489 &daemon_log("$session_id DEBUG: target is a server address -> forward '$header' to server", 11);
1490 } else {
1491 $not_found_in_known_server_db = 1;
1492 }
1493 }
1496 # target is not in foreign_clients_db, known_server_db or known_clients_db, maybe it is a complete new one -> process here
1497 if ( $not_found_in_foreign_clients_db
1498 && $not_found_in_known_server_db
1499 && $not_found_in_known_clients_db) {
1500 &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);
1501 if ($header =~ /^gosa_/ || $header =~ /^job_/) {
1502 $module = "GosaPackages";
1503 }
1504 my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1505 primkey=>[],
1506 headertag=>$header,
1507 targettag=>$target,
1508 xmlmessage=>&encode_base64($msg),
1509 timestamp=>&get_time,
1510 module=>$module,
1511 sessionid=>$session_id,
1512 } );
1513 $done = 1;
1514 $kernel->yield('watch_for_next_tasks');
1515 }
1518 if (not $done) {
1519 daemon_log("$session_id ERROR: do not know what to do with this message: $msg", 1);
1520 if ($source eq "GOSA") {
1521 my %data = ('error_msg' => &encode_base64($msg), 'error_string' => "Do not know what to do with this message!");
1522 my $error_msg = &build_msg("error", $local_address, "GOSA", \%data );
1524 my $session_reference = $kernel->ID_id_to_session($session_id);
1525 if( defined $session_reference ) {
1526 $heap = $session_reference->get_heap();
1527 }
1528 if(exists $heap->{'client'}) {
1529 $error_msg = &encrypt_msg($error_msg, $GosaPackages_key);
1530 $heap->{'client'}->put($error_msg);
1531 }
1532 }
1533 }
1535 }
1537 return;
1538 }
1541 sub next_task {
1542 my ($session, $heap, $task) = @_[SESSION, HEAP, ARG0, ARG1];
1543 my $running_task = POE::Wheel::Run->new(
1544 Program => sub { process_task($session, $heap, $task) },
1545 StdioFilter => POE::Filter::Reference->new(),
1546 StdoutEvent => "task_result",
1547 StderrEvent => "task_debug",
1548 CloseEvent => "task_done",
1549 );
1550 $heap->{task}->{ $running_task->ID } = $running_task;
1551 }
1553 sub handle_task_result {
1554 my ($kernel, $heap, $result) = @_[KERNEL, HEAP, ARG0];
1555 my $client_answer = $result->{'answer'};
1556 if( $client_answer =~ s/session_id=(\d+)$// ) {
1557 my $session_id = $1;
1558 if( defined $session_id ) {
1559 my $session_reference = $kernel->ID_id_to_session($session_id);
1560 if( defined $session_reference ) {
1561 $heap = $session_reference->get_heap();
1562 }
1563 }
1565 if(exists $heap->{'client'}) {
1566 $heap->{'client'}->put($client_answer);
1567 }
1568 }
1569 $kernel->sig(CHLD => "child_reap");
1570 }
1572 sub handle_task_debug {
1573 my $result = $_[ARG0];
1574 print STDERR "$result\n";
1575 }
1577 sub handle_task_done {
1578 my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1579 delete $heap->{task}->{$task_id};
1580 if (exists $heap->{ldap_handle}->{$task_id}) {
1581 &release_ldap_handle($heap->{ldap_handle}->{$task_id});
1582 }
1583 }
1585 sub process_task {
1586 no strict "refs";
1587 #CHECK: Not @_[...]?
1588 my ($session, $heap, $task) = @_;
1589 my $error = 0;
1590 my $answer_l;
1591 my ($answer_header, @answer_target_l, $answer_source);
1592 my $client_answer = "";
1594 # prepare all variables needed to process message
1595 #my $msg = $task->{'xmlmessage'};
1596 my $msg = &decode_base64($task->{'xmlmessage'});
1597 my $incoming_id = $task->{'id'};
1598 my $module = $task->{'module'};
1599 my $header = $task->{'headertag'};
1600 my $session_id = $task->{'sessionid'};
1601 my $msg_hash;
1602 eval {
1603 $msg_hash = $xml->XMLin($msg, ForceArray=>1);
1604 };
1605 daemon_log("ERROR: XML failure '$@'") if ($@);
1606 my $source = @{$msg_hash->{'source'}}[0];
1608 # set timestamp of incoming client uptodate, so client will not
1609 # be deleted from known_clients because of expiration
1610 my $cur_time = &get_time();
1611 my $sql = "UPDATE $known_clients_tn SET timestamp='$cur_time' WHERE hostname='$source'";
1612 my $res = $known_clients_db->exec_statement($sql);
1614 ######################
1615 # process incoming msg
1616 if( $error == 0) {
1617 daemon_log("$session_id INFO: Incoming msg (session_id=$session_id) with header '".@{$msg_hash->{'header'}}[0]."'", 5);
1618 daemon_log("$session_id DEBUG: Processing module ".$module, 26);
1619 $answer_l = &{ $module."::process_incoming_msg" }($msg, $msg_hash, $session_id);
1621 if ( 0 < @{$answer_l} ) {
1622 my $answer_str = join("\n", @{$answer_l});
1623 my @headers;
1624 while ($answer_str =~ /<header>(\w+)<\/header>/g) {
1625 push(@headers, $1);
1626 }
1627 daemon_log("$session_id DEBUG: got answer message(s) with header: '".join("', '", @headers)."'", 26);
1628 } else {
1629 daemon_log("$session_id DEBUG: $module: got no answer from module!" ,26);
1630 }
1632 }
1633 if( !$answer_l ) { $error++ };
1635 ########
1636 # answer
1637 if( $error == 0 ) {
1639 foreach my $answer ( @{$answer_l} ) {
1640 # check outgoing msg to xml validity
1641 my ($answer, $answer_hash) = &check_outgoing_xml_validity($answer, $session_id);
1642 if( not defined $answer_hash ) { next; }
1644 $answer_header = @{$answer_hash->{'header'}}[0];
1645 @answer_target_l = @{$answer_hash->{'target'}};
1646 $answer_source = @{$answer_hash->{'source'}}[0];
1648 # deliver msg to all targets
1649 foreach my $answer_target ( @answer_target_l ) {
1651 # targets of msg are all gosa-si-clients in known_clients_db
1652 if( $answer_target eq "*" ) {
1653 # answer is for all clients
1654 my $sql_statement= "SELECT * FROM known_clients";
1655 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1656 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1657 my $host_name = $hit->{hostname};
1658 my $host_key = $hit->{hostkey};
1659 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1660 &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1661 }
1662 }
1664 # targets of msg are all gosa-si-server in known_server_db
1665 elsif( $answer_target eq "KNOWN_SERVER" ) {
1666 # answer is for all server in known_server
1667 my $sql_statement= "SELECT * FROM $known_server_tn";
1668 my $query_res = $known_server_db->select_dbentry( $sql_statement );
1669 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1670 my $host_name = $hit->{hostname};
1671 my $host_key = $hit->{hostkey};
1672 $answer =~ s/<target>\S+<\/target>/<target>$host_name<\/target>/g;
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 # target of msg is GOsa
1679 elsif( $answer_target eq "GOSA" ) {
1680 my $session_id = ($1) if $answer =~ /<session_id>(\d+?)<\/session_id>/;
1681 my $add_on = "";
1682 if( defined $session_id ) {
1683 $add_on = ".session_id=$session_id";
1684 }
1685 my $header = ($1) if $answer =~ /<header>(\S*)<\/header>/;
1686 daemon_log("$session_id INFO: send ".$header." message to GOsa", 5);
1687 daemon_log("$session_id DEBUG: message:\n$answer", 12);
1688 # answer is for GOSA and has to returned to connected client
1689 my $gosa_answer = &encrypt_msg($answer, $GosaPackages_key);
1690 $client_answer = $gosa_answer.$add_on;
1691 }
1693 # target of msg is job queue at this host
1694 elsif( $answer_target eq "JOBDB") {
1695 $answer =~ /<header>(\S+)<\/header>/;
1696 my $header;
1697 if( defined $1 ) { $header = $1; }
1698 my $error = &send_msg_to_target($answer, $server_address, $GosaPackages_key, $header, $session_id);
1699 &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1700 }
1702 # Target of msg is a mac address
1703 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 ) {
1704 daemon_log("$session_id DEBUG: target is mac address '$answer_target', looking for host in known_clients and foreign_clients", 138);
1706 # Looking for macaddress in known_clients
1707 my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$answer_target'";
1708 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1709 my $found_ip_flag = 0;
1710 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1711 my $host_name = $hit->{hostname};
1712 my $host_key = $hit->{hostkey};
1713 $answer =~ s/$answer_target/$host_name/g;
1714 daemon_log("$session_id INFO: found host '$host_name', associated to '$answer_target'", 5);
1715 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1716 &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1717 $found_ip_flag++ ;
1718 }
1720 # Looking for macaddress in foreign_clients
1721 if ($found_ip_flag == 0) {
1722 my $sql = "SELECT * FROM $foreign_clients_tn WHERE macaddress LIKE '$answer_target'";
1723 my $res = $foreign_clients_db->select_dbentry($sql);
1724 while( my ($hit_num, $hit) = each %{ $res } ) {
1725 my $host_name = $hit->{hostname};
1726 my $reg_server = $hit->{regserver};
1727 daemon_log("$session_id INFO: found host '$host_name' with mac '$answer_target', registered at '$reg_server'", 5);
1729 # Fetch key for reg_server
1730 my $reg_server_key;
1731 my $sql = "SELECT * FROM $known_server_tn WHERE hostname='$reg_server'";
1732 my $res = $known_server_db->select_dbentry($sql);
1733 if (exists $res->{1}) {
1734 $reg_server_key = $res->{1}->{'hostkey'};
1735 } else {
1736 daemon_log("$session_id ERROR: cannot find hostkey for '$host_name' in '$known_server_tn'", 1);
1737 daemon_log("$session_id ERROR: unable to forward answer to correct registration server, processing is aborted!", 1);
1738 $reg_server_key = undef;
1739 }
1741 # Send answer to server where client is registered
1742 if (defined $reg_server_key) {
1743 $answer =~ s/$answer_target/$host_name/g;
1744 my $error = &send_msg_to_target($answer, $reg_server, $reg_server_key, $answer_header, $session_id);
1745 &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1746 $found_ip_flag++ ;
1747 }
1748 }
1749 }
1751 # No mac to ip matching found
1752 if( $found_ip_flag == 0) {
1753 daemon_log("$session_id WARNING: no host found in known_clients or foreign_clients with mac address '$answer_target'", 3);
1754 &reactivate_job_with_delay($session_id, $answer_target, $answer_header, 30);
1755 }
1757 # Answer is for one specific host
1758 } else {
1759 # get encrypt_key
1760 my $encrypt_key = &get_encrypt_key($answer_target);
1761 if( not defined $encrypt_key ) {
1762 # unknown target
1763 daemon_log("$session_id WARNING: unknown target '$answer_target'", 3);
1764 next;
1765 }
1766 my $error = &send_msg_to_target($answer, $answer_target, $encrypt_key, $answer_header,$session_id);
1767 &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1768 }
1769 }
1770 }
1771 }
1773 my $filter = POE::Filter::Reference->new();
1774 my %result = (
1775 status => "seems ok to me",
1776 answer => $client_answer,
1777 );
1779 my $output = $filter->put( [ \%result ] );
1780 print @$output;
1783 }
1785 sub session_start {
1786 my ($kernel) = $_[KERNEL];
1787 $global_kernel = $kernel;
1788 $kernel->yield('register_at_foreign_servers');
1789 $kernel->yield('create_fai_server_db', $fai_server_tn );
1790 $kernel->yield('create_fai_release_db', $fai_release_tn );
1791 $kernel->sig(USR1 => "sig_handler");
1792 $kernel->sig(USR2 => "recreate_packages_db");
1793 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1794 $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay);
1795 $kernel->delay_set('watch_for_modified_jobs', $modified_jobs_loop_delay);
1796 $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1797 $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1798 $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1799 $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1801 # Start opsi check
1802 if ($opsi_enabled eq "true") {
1803 $kernel->delay_set('watch_for_opsi_jobs', $job_queue_opsi_delay);
1804 }
1806 }
1809 sub watch_for_done_jobs {
1810 my $kernel = $_[KERNEL];
1812 my $sql_statement = "SELECT * FROM ".$job_queue_tn." WHERE ((status='done') AND (modified='0'))";
1813 my $res = $job_db->select_dbentry( $sql_statement );
1815 while( my ($number, $hit) = each %{$res} )
1816 {
1817 # Non periodical jobs can be deleted.
1818 if ($hit->{periodic} eq "none")
1819 {
1820 my $jobdb_id = $hit->{id};
1821 my $sql_statement = "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1822 my $res = $job_db->del_dbentry($sql_statement);
1823 }
1825 # Periodical jobs should not be deleted but reactivated with new timestamp instead.
1826 else
1827 {
1828 my ($p_time, $periodic) = split("_", $hit->{periodic});
1829 my $reactivated_ts = $hit->{timestamp};
1830 my $act_ts = int(&get_time());
1831 while ($act_ts > int($reactivated_ts)) # Redo calculation to avoid multiple jobs in the past
1832 {
1833 $reactivated_ts = &calc_timestamp($reactivated_ts, "plus", $p_time, $periodic);
1834 }
1835 my $sql = "UPDATE $job_queue_tn SET status='waiting', timestamp='$reactivated_ts' WHERE id='".$hit->{id}."'";
1836 my $res = $job_db->exec_statement($sql);
1837 &daemon_log("J INFO: Update periodical job '".$hit->{headertag}."' for client '".$hit->{targettag}."'. New execution time '$reactivated_ts'.", 5);
1838 }
1839 }
1841 $kernel->delay_set('watch_for_done_jobs',$job_queue_loop_delay);
1842 }
1845 sub watch_for_opsi_jobs {
1846 my ($kernel) = $_[KERNEL];
1847 # 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
1848 # opsi install job is to parse the xml message. There is still the correct header.
1849 my $sql_statement = "SELECT * FROM ".$job_queue_tn." WHERE ((xmlmessage LIKE '%opsi_install_client</header>%') AND (status='processing') AND (siserver='localhost'))";
1850 my $res = $job_db->select_dbentry( $sql_statement );
1852 # Ask OPSI for an update of the running jobs
1853 while (my ($id, $hit) = each %$res ) {
1854 # Determine current parameters of the job
1855 my $hostId = $hit->{'plainname'};
1856 my $macaddress = $hit->{'macaddress'};
1857 my $progress = $hit->{'progress'};
1859 my $result= {};
1861 # For hosts, only return the products that are or get installed
1862 my $callobj;
1863 $callobj = {
1864 method => 'getProductStates_hash',
1865 params => [ $hostId ],
1866 id => 1,
1867 };
1869 my $hres = $opsi_client->call($opsi_url, $callobj);
1870 # TODO : move all opsi relevant statments to opsi plugin
1871 # The following 3 lines must be tested befor they can replace the rubbish above and below !!!
1872 #my ($res, $err) = &opsi_com::_getProductStates_hash(hostId=>$hostId)
1873 #if (not $err) {
1874 # my $htmp = $res->{$hostId};
1875 #
1876 if (not &check_opsi_res($hres)) {
1877 my $htmp= $hres->result->{$hostId};
1878 # Check state != not_installed or action == setup -> load and add
1879 my $products= 0;
1880 my $installed= 0;
1881 my $installing = 0;
1882 my $error= 0;
1883 my @installed_list;
1884 my @error_list;
1885 my $act_status = "none";
1886 foreach my $product (@{$htmp}){
1888 if ($product->{'installationStatus'} ne "not_installed" or
1889 $product->{'actionRequest'} eq "setup"){
1891 # Increase number of products for this host
1892 $products++;
1894 if ($product->{'installationStatus'} eq "failed"){
1895 $result->{$product->{'productId'}}= "error";
1896 unshift(@error_list, $product->{'productId'});
1897 $error++;
1898 }
1899 if ($product->{'installationStatus'} eq "installed" && $product->{'actionRequest'} eq "none"){
1900 $result->{$product->{'productId'}}= "installed";
1901 unshift(@installed_list, $product->{'productId'});
1902 $installed++;
1903 }
1904 if ($product->{'installationStatus'} eq "installing"){
1905 $result->{$product->{'productId'}}= "installing";
1906 $installing++;
1907 $act_status = "installing - ".$product->{'productId'};
1908 }
1909 }
1910 }
1912 # Estimate "rough" progress, avoid division by zero
1913 if ($products == 0) {
1914 $result->{'progress'}= 0;
1915 } else {
1916 $result->{'progress'}= int($installed * 100 / $products);
1917 }
1919 # Set updates in job queue
1920 if ((not $error) && (not $installing) && ($installed)) {
1921 $act_status = "installed - ".join(", ", @installed_list);
1922 }
1923 if ($error) {
1924 $act_status = "error - ".join(", ", @error_list);
1925 }
1926 if ($progress ne $result->{'progress'} ) {
1927 # Updating progress and result
1928 my $update_statement = "UPDATE $job_queue_tn SET modified='1', progress='".$result->{'progress'}."', result='$act_status' WHERE macaddress LIKE '$macaddress' AND siserver='localhost'";
1929 my $update_res = $job_db->update_dbentry($update_statement);
1930 }
1931 if ($progress eq 100) {
1932 # Updateing status
1933 my $done_statement = "UPDATE $job_queue_tn SET modified='1', ";
1934 if ($error) {
1935 $done_statement .= "status='error'";
1936 } else {
1937 $done_statement .= "status='done'";
1938 }
1939 $done_statement .= " WHERE macaddress LIKE '$macaddress' AND siserver='localhost'";
1940 my $done_res = $job_db->update_dbentry($done_statement);
1941 }
1944 }
1945 }
1947 $kernel->delay_set('watch_for_opsi_jobs', $job_queue_opsi_delay);
1948 }
1951 # If a job got an update or was modified anyway, send to all other si-server an update message of this jobs.
1952 sub watch_for_modified_jobs {
1953 my ($kernel,$heap) = @_[KERNEL, HEAP];
1955 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE (modified='1')";
1956 my $res = $job_db->select_dbentry( $sql_statement );
1958 # if db contains no jobs which should be update, do nothing
1959 if (keys %$res != 0) {
1961 if ($job_synchronization eq "true") {
1962 # make out of the db result a gosa-si message
1963 my $update_msg = &db_res2si_msg ($res, "foreign_job_updates", "KNOWN_SERVER", "MY_LOCAL_ADDRESS");
1965 # update all other SI-server
1966 &inform_all_other_si_server($update_msg);
1967 }
1969 # set jobs all jobs to modified = 0, wait until the next modification for updates of other si-server
1970 $sql_statement = "UPDATE $job_queue_tn SET modified='0' ";
1971 $res = $job_db->update_dbentry($sql_statement);
1972 }
1974 $kernel->delay_set('watch_for_modified_jobs', $modified_jobs_loop_delay);
1975 }
1978 sub watch_for_new_jobs {
1979 if($watch_for_new_jobs_in_progress == 0) {
1980 $watch_for_new_jobs_in_progress = 1;
1981 my ($kernel,$heap) = @_[KERNEL, HEAP];
1983 # check gosa job queue for jobs with executable timestamp
1984 my $timestamp = &get_time();
1985 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE siserver='localhost' AND status='waiting' AND (CAST(timestamp AS UNSIGNED)) < $timestamp ORDER BY timestamp";
1986 my $res = $job_db->exec_statement( $sql_statement );
1988 # Merge all new jobs that would do the same actions
1989 my @drops;
1990 my $hits;
1991 foreach my $hit (reverse @{$res} ) {
1992 my $macaddress= lc @{$hit}[8];
1993 my $headertag= @{$hit}[5];
1994 if(
1995 defined($hits->{$macaddress}) &&
1996 defined($hits->{$macaddress}->{$headertag}) &&
1997 defined($hits->{$macaddress}->{$headertag}[0])
1998 ) {
1999 push @drops, "DELETE FROM $job_queue_tn WHERE id = $hits->{$macaddress}->{$headertag}[0]";
2000 }
2001 $hits->{$macaddress}->{$headertag}= $hit;
2002 }
2004 # Delete new jobs with a matching job in state 'processing'
2005 foreach my $macaddress (keys %{$hits}) {
2006 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
2007 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
2008 if(defined($jobdb_id)) {
2009 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND headertag='$jobdb_headertag' AND status='processing'";
2010 my $res = $job_db->exec_statement( $sql_statement );
2011 foreach my $hit (@{$res}) {
2012 push @drops, "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
2013 }
2014 } else {
2015 daemon_log("J ERROR: Job without id exists for macaddress $macaddress!", 1);
2016 }
2017 }
2018 }
2020 # Commit deletion
2021 $job_db->exec_statementlist(\@drops);
2023 # Look for new jobs that could be executed
2024 foreach my $macaddress (keys %{$hits}) {
2026 # Look if there is an executing job
2027 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='processing'";
2028 my $res = $job_db->exec_statement( $sql_statement );
2030 # Skip new jobs for host if there is a processing job
2031 if(defined($res) and defined @{$res}[0]) {
2032 # Prevent race condition if there is a trigger_activate job waiting and a goto-activation job processing
2033 my $row = @{$res}[0] if (ref $res eq 'ARRAY');
2034 if(@{$row}[5] eq 'trigger_action_reinstall') {
2035 my $sql_statement_2 = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='waiting' AND headertag = 'trigger_activate_new'";
2036 my $res_2 = $job_db->exec_statement( $sql_statement_2 );
2037 if(defined($res_2) and defined @{$res_2}[0]) {
2038 # Set status from goto-activation to 'waiting' and update timestamp
2039 $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'");
2040 }
2041 }
2042 next;
2043 }
2045 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
2046 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
2047 if(defined($jobdb_id)) {
2048 my $job_msg = @{$hits->{$macaddress}->{$jobdb_headertag}}[7];
2050 daemon_log("J DEBUG: its time to execute $job_msg", 7);
2051 my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$macaddress'";
2052 my $res_hash = $known_clients_db->select_dbentry( $sql_statement );
2054 # expect macaddress is unique!!!!!!
2055 my $target = $res_hash->{1}->{hostname};
2057 # change header
2058 $job_msg =~ s/<header>job_/<header>gosa_/;
2060 # add sqlite_id
2061 $job_msg =~ s/<\/xml>$/<jobdb_id>$jobdb_id<\/jobdb_id><\/xml>/;
2063 $job_msg =~ /<header>(\S+)<\/header>/;
2064 my $header = $1 ;
2065 my $func_error = &send_msg_to_target($job_msg, $server_address, $GosaPackages_key, $header, "J");
2067 # update status in job queue to ...
2068 # ... 'processing', for jobs: 'reinstall', 'update', activate_new
2069 if (($header =~ /gosa_trigger_action_reinstall/)
2070 || ($header =~ /gosa_trigger_activate_new/)
2071 || ($header =~ /gosa_trigger_action_update/)) {
2072 my $sql_statement = "UPDATE $job_queue_tn SET status='processing' WHERE id=$jobdb_id";
2073 my $dbres = $job_db->update_dbentry($sql_statement);
2074 }
2076 # ... 'done', for all other jobs, they are no longer needed in the jobqueue
2077 else {
2078 my $sql_statement = "UPDATE $job_queue_tn SET status='done' WHERE id=$jobdb_id";
2079 my $dbres = $job_db->exec_statement($sql_statement);
2080 }
2083 # We don't want parallel processing
2084 last;
2085 }
2086 }
2087 }
2089 $watch_for_new_jobs_in_progress = 0;
2090 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
2091 }
2092 }
2095 sub watch_for_new_messages {
2096 my ($kernel,$heap) = @_[KERNEL, HEAP];
2097 my @coll_user_msg; # collection list of outgoing messages
2099 # check messaging_db for new incoming messages with executable timestamp
2100 my $timestamp = &get_time();
2101 my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( (CAST(timestamp AS UNSIGNED))<$timestamp AND flag='n' AND direction='in' )";
2102 my $res = $messaging_db->exec_statement( $sql_statement );
2103 foreach my $hit (@{$res}) {
2105 # create outgoing messages
2106 my $message_to = @{$hit}[3];
2107 # translate message_to to plain login name
2108 my @message_to_l = split(/,/, $message_to);
2109 my %receiver_h;
2110 foreach my $receiver (@message_to_l) {
2111 if ($receiver =~ /^u_([\s\S]*)$/) {
2112 $receiver_h{$1} = 0;
2113 } elsif ($receiver =~ /^g_([\s\S]*)$/) {
2114 my $group_name = $1;
2115 # fetch all group members from ldap and add them to receiver hash
2116 my $ldap_handle = &get_ldap_handle();
2117 if (defined $ldap_handle) {
2118 my $mesg = $ldap_handle->search(
2119 base => $ldap_base,
2120 scope => 'sub',
2121 attrs => ['memberUid'],
2122 filter => "cn=$group_name",
2123 );
2124 if ($mesg->count) {
2125 my @entries = $mesg->entries;
2126 foreach my $entry (@entries) {
2127 my @receivers= $entry->get_value("memberUid");
2128 foreach my $receiver (@receivers) {
2129 $receiver_h{$receiver} = 0;
2130 }
2131 }
2132 }
2133 # translating errors ?
2134 if ($mesg->code) {
2135 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: $mesg->error", 1);
2136 }
2137 &release_ldap_handle($ldap_handle);
2138 # ldap handle error ?
2139 } else {
2140 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: no ldap handle available", 1);
2141 }
2142 } else {
2143 my $sbjct = &encode_base64(@{$hit}[1]);
2144 my $msg = &encode_base64(@{$hit}[7]);
2145 &daemon_log("M WARNING: unknown receiver '$receiver' for a user-message '$sbjct - $msg'", 3);
2146 }
2147 }
2148 my @receiver_l = keys(%receiver_h);
2150 my $message_id = @{$hit}[0];
2152 #add each outgoing msg to messaging_db
2153 my $receiver;
2154 foreach $receiver (@receiver_l) {
2155 my $sql_statement = "INSERT INTO $messaging_tn (id, subject, message_from, message_to, flag, direction, delivery_time, message, timestamp) ".
2156 "VALUES ('".
2157 $message_id."', '". # id
2158 @{$hit}[1]."', '". # subject
2159 @{$hit}[2]."', '". # message_from
2160 $receiver."', '". # message_to
2161 "none"."', '". # flag
2162 "out"."', '". # direction
2163 @{$hit}[6]."', '". # delivery_time
2164 @{$hit}[7]."', '". # message
2165 $timestamp."'". # timestamp
2166 ")";
2167 &daemon_log("M DEBUG: $sql_statement", 1);
2168 my $res = $messaging_db->exec_statement($sql_statement);
2169 &daemon_log("M INFO: message '".@{$hit}[0]."' is prepared for delivery to receiver '$receiver'", 5);
2170 }
2172 # set incoming message to flag d=deliverd
2173 $sql_statement = "UPDATE $messaging_tn SET flag='p' WHERE id='$message_id'";
2174 &daemon_log("M DEBUG: $sql_statement", 7);
2175 $res = $messaging_db->update_dbentry($sql_statement);
2176 &daemon_log("M INFO: message '$message_id' is set to flag 'p' (processed)", 5);
2177 }
2179 $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
2180 return;
2181 }
2183 sub watch_for_delivery_messages {
2184 my ($kernel, $heap) = @_[KERNEL, HEAP];
2186 # select outgoing messages
2187 my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( flag='p' AND direction='out' )";
2188 my $res = $messaging_db->exec_statement( $sql_statement );
2190 # build out msg for each usr
2191 foreach my $hit (@{$res}) {
2192 my $receiver = @{$hit}[3];
2193 my $msg_id = @{$hit}[0];
2194 my $subject = @{$hit}[1];
2195 my $message = @{$hit}[7];
2197 # resolve usr -> host where usr is logged in
2198 my $sql = "SELECT * FROM $login_users_tn WHERE (user='$receiver')";
2199 my $res = $login_users_db->exec_statement($sql);
2201 # receiver is logged in nowhere
2202 if (not ref(@$res[0]) eq "ARRAY") { next; }
2204 # receiver ist logged in at a client registered at local server
2205 my $send_succeed = 0;
2206 foreach my $hit (@$res) {
2207 my $receiver_host = @$hit[0];
2208 my $delivered2host = 0;
2209 &daemon_log("M DEBUG: user '$receiver' is logged in at host '$receiver_host'", 7);
2211 # Looking for host in know_clients_db
2212 my $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$receiver_host')";
2213 my $res = $known_clients_db->exec_statement($sql);
2215 # Host is known in known_clients_db
2216 if (ref(@$res[0]) eq "ARRAY") {
2217 my $receiver_key = @{@{$res}[0]}[2];
2218 my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
2219 my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data );
2220 my $error = &send_msg_to_target($out_msg, $receiver_host, $receiver_key, "usr_msg", 0);
2221 if ($error == 0 ) {
2222 $send_succeed++ ;
2223 $delivered2host++ ;
2224 &daemon_log("M DEBUG: send message for user '$receiver' to host '$receiver_host'", 7);
2225 } else {
2226 &daemon_log("M DEBUG: cannot send message for user '$receiver' to host '$receiver_host'", 7);
2227 }
2228 }
2230 # Message already send, do not need to do anything more, otherwise ...
2231 if ($delivered2host) { next;}
2233 # ...looking for host in foreign_clients_db
2234 $sql = "SELECT * FROM $foreign_clients_tn WHERE (hostname='$receiver_host')";
2235 $res = $foreign_clients_db->exec_statement($sql);
2237 # Host is known in foreign_clients_db
2238 if (ref(@$res[0]) eq "ARRAY") {
2239 my $registration_server = @{@{$res}[0]}[2];
2241 # Fetch encryption key for registration server
2242 my $sql = "SELECT * FROM $known_server_tn WHERE (hostname='$registration_server')";
2243 my $res = $known_server_db->exec_statement($sql);
2244 if (ref(@$res[0]) eq "ARRAY") {
2245 my $registration_server_key = @{@{$res}[0]}[3];
2246 my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
2247 my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data );
2248 my $error = &send_msg_to_target($out_msg, $registration_server, $registration_server_key, "usr_msg", 0);
2249 if ($error == 0 ) {
2250 $send_succeed++ ;
2251 $delivered2host++ ;
2252 &daemon_log("M DEBUG: send message for user '$receiver' to server '$registration_server'", 7);
2253 } else {
2254 &daemon_log("M ERROR: cannot send message for user '$receiver' to server '$registration_server'", 1);
2255 }
2257 } else {
2258 &daemon_log("M ERROR: host '$receiver_host' is reported to be ".
2259 "registrated at server '$registration_server', ".
2260 "but no data available in known_server_db ", 1);
2261 }
2262 }
2264 if (not $delivered2host) {
2265 &daemon_log("M ERROR: unable to send user message to host '$receiver_host'", 1);
2266 }
2267 }
2269 if ($send_succeed) {
2270 # set outgoing msg at db to deliverd
2271 my $sql = "UPDATE $messaging_tn SET flag='d' WHERE (id='$msg_id' AND direction='out' AND message_to='$receiver')";
2272 my $res = $messaging_db->exec_statement($sql);
2273 &daemon_log("M INFO: send message for user '$receiver' to logged in hosts", 5);
2274 } else {
2275 &daemon_log("M WARNING: failed to deliver message for user '$receiver'", 3);
2276 }
2277 }
2279 $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
2280 return;
2281 }
2284 sub watch_for_done_messages {
2285 my ($kernel,$heap) = @_[KERNEL, HEAP];
2287 my $sql = "SELECT * FROM $messaging_tn WHERE (flag='p' AND direction='in')";
2288 my $res = $messaging_db->exec_statement($sql);
2290 foreach my $hit (@{$res}) {
2291 my $msg_id = @{$hit}[0];
2293 my $sql = "SELECT * FROM $messaging_tn WHERE (id='$msg_id' AND direction='out' AND (NOT flag='s'))";
2294 my $res = $messaging_db->exec_statement($sql);
2296 # not all usr msgs have been seen till now
2297 if ( ref(@$res[0]) eq "ARRAY") { next; }
2299 $sql = "DELETE FROM $messaging_tn WHERE (id='$msg_id')";
2300 $res = $messaging_db->exec_statement($sql);
2302 }
2304 $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
2305 return;
2306 }
2309 sub watch_for_old_known_clients {
2310 my ($kernel,$heap) = @_[KERNEL, HEAP];
2312 my $sql_statement = "SELECT * FROM $known_clients_tn";
2313 my $res = $known_clients_db->select_dbentry( $sql_statement );
2315 my $cur_time = int(&get_time());
2317 while ( my ($hit_num, $hit) = each %$res) {
2318 my $expired_timestamp = int($hit->{'timestamp'});
2319 $expired_timestamp =~ /(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)/;
2320 my $dt = DateTime->new( year => $1,
2321 month => $2,
2322 day => $3,
2323 hour => $4,
2324 minute => $5,
2325 second => $6,
2326 );
2328 $dt->add( seconds => 2 * int($hit->{'keylifetime'}) );
2329 $expired_timestamp = $dt->ymd('').$dt->hms('');
2330 if ($cur_time > $expired_timestamp) {
2331 my $hostname = $hit->{'hostname'};
2332 my $del_sql = "DELETE FROM $known_clients_tn WHERE hostname='$hostname'";
2333 my $del_res = $known_clients_db->exec_statement($del_sql);
2335 &main::daemon_log("0 INFO: timestamp '".$hit->{'timestamp'}."' of client '$hostname' is expired('$expired_timestamp'), client will be deleted from known_clients_db", 5);
2336 }
2338 }
2340 $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
2341 }
2344 sub watch_for_next_tasks {
2345 my ($kernel,$heap) = @_[KERNEL, HEAP];
2347 my $sql = "SELECT * FROM $incoming_tn";
2348 my $res = $incoming_db->select_dbentry($sql);
2350 while ( my ($hit_num, $hit) = each %$res) {
2351 my $headertag = $hit->{'headertag'};
2352 if ($headertag =~ /^answer_(\d+)/) {
2353 # do not start processing, this message is for a still running POE::Wheel
2354 next;
2355 }
2356 my $message_id = $hit->{'id'};
2357 my $session_id = $hit->{'sessionid'};
2358 &daemon_log("$session_id DEBUG: start processing for message with incoming id: '$message_id'", 11);
2360 $kernel->yield('next_task', $hit);
2362 my $sql = "DELETE FROM $incoming_tn WHERE id=$message_id";
2363 my $res = $incoming_db->exec_statement($sql);
2364 }
2365 }
2368 sub get_ldap_handle {
2369 my ($session_id) = @_;
2370 my $heap;
2372 if (not defined $session_id ) { $session_id = 0 };
2373 if ($session_id =~ /[^0-9]*/) { $session_id = 0 };
2375 my ($package, $file, $row, $subroutine, $hasArgs, $wantArray, $evalText, $isRequire) = caller(1);
2376 my $caller_text = "subroutine $subroutine";
2377 if ($subroutine eq "(eval)") {
2378 $caller_text = "eval block within file '$file' for '$evalText'";
2379 }
2380 daemon_log("$session_id DEBUG: new ldap handle for '$caller_text' required!", 42);
2382 get_handle:
2383 my $ldap_handle = Net::LDAP->new( $ldap_uri );
2384 if (not ref $ldap_handle) {
2385 daemon_log("$session_id ERROR: Connection to LDAP URI '$ldap_uri' failed! Retrying in $ldap_retry_sec seconds.", 1);
2386 sleep($ldap_retry_sec);
2387 goto get_handle;
2388 } else {
2389 daemon_log("$session_id DEBUG: Connection to LDAP URI '$ldap_uri' established.", 42);
2390 }
2392 $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);
2393 return $ldap_handle;
2394 }
2397 sub release_ldap_handle {
2398 my ($ldap_handle, $session_id) = @_ ;
2399 if (not defined $session_id ) { $session_id = 0 };
2401 if(ref $ldap_handle) {
2402 $ldap_handle->disconnect();
2403 }
2404 &main::daemon_log("$session_id DEBUG: Released a ldap handle!", 42);
2405 return;
2406 }
2409 sub change_fai_state {
2410 my ($st, $targets, $session_id) = @_;
2411 $session_id = 0 if not defined $session_id;
2412 # Set FAI state to localboot
2413 my %mapActions= (
2414 reboot => '',
2415 update => 'softupdate',
2416 localboot => 'localboot',
2417 reinstall => 'install',
2418 rescan => '',
2419 wake => '',
2420 memcheck => 'memcheck',
2421 sysinfo => 'sysinfo',
2422 install => 'install',
2423 );
2425 # Return if this is unknown
2426 if (!exists $mapActions{ $st }){
2427 daemon_log("$session_id ERROR: unknown action '$st', can not translate ot FAIstate", 1);
2428 return;
2429 }
2431 my $state= $mapActions{ $st };
2433 # Build search filter for hosts
2434 my $search= "(&(objectClass=GOhard)";
2435 foreach (@{$targets}){
2436 $search.= "(macAddress=$_)";
2437 }
2438 $search.= ")";
2440 # If there's any host inside of the search string, procress them
2441 if (!($search =~ /macAddress/)){
2442 daemon_log("$session_id ERROR: no macAddress found in filter statement for LDAP search: '$search'", 1);
2443 return;
2444 }
2446 my $ldap_handle = &get_ldap_handle($session_id);
2447 # Perform search for Unit Tag
2448 my $mesg = $ldap_handle->search(
2449 base => $ldap_base,
2450 scope => 'sub',
2451 attrs => ['dn', 'FAIstate', 'objectClass'],
2452 filter => "$search"
2453 );
2455 if ($mesg->count) {
2456 my @entries = $mesg->entries;
2457 if (0 == @entries) {
2458 daemon_log("$session_id ERROR: ldap search failed: ldap_base=$ldap_base, filter=$search", 1);
2459 }
2461 foreach my $entry (@entries) {
2462 # Only modify entry if it is not set to '$state'
2463 if ($entry->get_value("FAIstate") ne "$state"){
2464 daemon_log("$session_id INFO: Setting FAIstate to '$state' for ".$entry->dn, 5);
2465 my $result;
2466 my %tmp = map { $_ => 1 } $entry->get_value("objectClass");
2467 if (exists $tmp{'FAIobject'}){
2468 if ($state eq ''){
2469 $result= $ldap_handle->modify($entry->dn, changes => [ delete => [ FAIstate => [] ] ]);
2470 } else {
2471 $result= $ldap_handle->modify($entry->dn, changes => [ replace => [ FAIstate => $state ] ]);
2472 }
2473 } elsif ($state ne ''){
2474 $result= $ldap_handle->modify($entry->dn, changes => [ add => [ objectClass => 'FAIobject' ], add => [ FAIstate => $state ] ]);
2475 }
2477 # Errors?
2478 if ($result->code){
2479 daemon_log("$session_id Error: Setting FAIstate to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
2480 }
2481 } else {
2482 daemon_log("$session_id DEBUG FAIstate at host '".$entry->dn."' already at state '$st'", 42);
2483 }
2484 }
2485 } else {
2486 daemon_log("$session_id ERROR: LDAP search failed: ldap_base=$ldap_base, filter=$search", 1);
2487 }
2488 &release_ldap_handle($ldap_handle, $session_id);
2490 return;
2491 }
2494 sub change_goto_state {
2495 my ($st, $targets, $session_id) = @_;
2496 $session_id = 0 if not defined $session_id;
2498 # Switch on or off?
2499 my $state= $st eq 'active' ? 'active': 'locked';
2501 my $ldap_handle = &get_ldap_handle($session_id);
2502 if( defined($ldap_handle) ) {
2504 # Build search filter for hosts
2505 my $search= "(&(objectClass=GOhard)";
2506 foreach (@{$targets}){
2507 $search.= "(macAddress=$_)";
2508 }
2509 $search.= ")";
2511 # If there's any host inside of the search string, procress them
2512 if (!($search =~ /macAddress/)){
2513 &release_ldap_handle($ldap_handle);
2514 return;
2515 }
2517 # Perform search for Unit Tag
2518 my $mesg = $ldap_handle->search(
2519 base => $ldap_base,
2520 scope => 'sub',
2521 attrs => ['dn', 'gotoMode'],
2522 filter => "$search"
2523 );
2525 if ($mesg->count) {
2526 my @entries = $mesg->entries;
2527 foreach my $entry (@entries) {
2529 # Only modify entry if it is not set to '$state'
2530 if ($entry->get_value("gotoMode") ne $state){
2532 daemon_log("$session_id INFO: Setting gotoMode to '$state' for ".$entry->dn, 5);
2533 my $result;
2534 $result= $ldap_handle->modify($entry->dn, changes => [replace => [ gotoMode => $state ] ]);
2536 # Errors?
2537 if ($result->code){
2538 &daemon_log("$session_id Error: Setting gotoMode to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
2539 }
2541 }
2542 }
2543 } else {
2544 daemon_log("$session_id ERROR: LDAP search failed in function change_goto_state: ldap_base=$ldap_base, filter=$search", 1);
2545 }
2547 }
2548 &release_ldap_handle($ldap_handle, $session_id);
2549 return;
2550 }
2553 sub run_recreate_packages_db {
2554 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2555 my $session_id = $session->ID;
2556 &main::daemon_log("$session_id INFO: Recreating FAI Packages DB ('$fai_release_tn', '$fai_server_tn', '$packages_list_tn')", 5);
2557 $kernel->yield('create_fai_release_db', $fai_release_tn);
2558 $kernel->yield('create_fai_server_db', $fai_server_tn);
2559 return;
2560 }
2563 sub run_create_fai_server_db {
2564 my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
2565 my $session_id = $session->ID;
2566 my $task = POE::Wheel::Run->new(
2567 Program => sub { &create_fai_server_db($table_name,$kernel, undef, $session_id) },
2568 StdoutEvent => "session_run_result",
2569 StderrEvent => "session_run_debug",
2570 CloseEvent => "session_run_done",
2571 );
2573 $heap->{task}->{ $task->ID } = $task;
2574 return;
2575 }
2578 sub create_fai_server_db {
2579 my ($table_name, $kernel, $dont_create_packages_list, $session_id) = @_;
2580 my $result;
2582 if (not defined $session_id) { $session_id = 0; }
2583 my $ldap_handle = &get_ldap_handle($session_id);
2584 if(defined($ldap_handle)) {
2585 daemon_log("$session_id INFO: create_fai_server_db: start", 5);
2586 my $mesg= $ldap_handle->search(
2587 base => $ldap_base,
2588 scope => 'sub',
2589 attrs => ['FAIrepository', 'gosaUnitTag'],
2590 filter => "(&(FAIrepository=*)(objectClass=FAIrepositoryServer))",
2591 );
2592 if($mesg->{'resultCode'} == 0 &&
2593 $mesg->count != 0) {
2594 foreach my $entry (@{$mesg->{entries}}) {
2595 if($entry->exists('FAIrepository')) {
2596 # Add an entry for each Repository configured for server
2597 foreach my $repo(@{$entry->get_value('FAIrepository', asref => 1)}) {
2598 my($tmp_url,$tmp_server,$tmp_release,$tmp_sections) = split(/\|/, $repo);
2599 my $tmp_tag= $entry->get_value('gosaUnitTag') || "";
2600 $result= $fai_server_db->add_dbentry( {
2601 table => $table_name,
2602 primkey => ['server', 'fai_release', 'tag'],
2603 server => $tmp_url,
2604 fai_release => $tmp_release,
2605 sections => $tmp_sections,
2606 tag => (length($tmp_tag)>0)?$tmp_tag:"",
2607 } );
2608 }
2609 }
2610 }
2611 }
2612 daemon_log("$session_id INFO: create_fai_server_db: finished", 5);
2613 &release_ldap_handle($ldap_handle);
2615 # TODO: Find a way to post the 'create_packages_list_db' event
2616 if(not defined($dont_create_packages_list)) {
2617 &create_packages_list_db(undef, $session_id);
2618 }
2619 }
2621 return $result;
2622 }
2625 sub run_create_fai_release_db {
2626 my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
2627 my $session_id = $session->ID;
2628 my $task = POE::Wheel::Run->new(
2629 Program => sub { &create_fai_release_db($table_name, $session_id) },
2630 StdoutEvent => "session_run_result",
2631 StderrEvent => "session_run_debug",
2632 CloseEvent => "session_run_done",
2633 );
2635 $heap->{task}->{ $task->ID } = $task;
2636 return;
2637 }
2640 sub create_fai_release_db {
2641 my ($table_name, $session_id) = @_;
2642 my $result;
2644 # used for logging
2645 if (not defined $session_id) { $session_id = 0; }
2647 my $ldap_handle = &get_ldap_handle($session_id);
2648 if(defined($ldap_handle)) {
2649 daemon_log("$session_id INFO: create_fai_release_db: start",5);
2650 my $mesg= $ldap_handle->search(
2651 base => $ldap_base,
2652 scope => 'sub',
2653 attrs => [],
2654 filter => "(&(objectClass=organizationalUnit)(ou=fai))",
2655 );
2656 if(($mesg->code == 0) && ($mesg->count != 0))
2657 {
2658 daemon_log("$session_id DEBUG: create_fai_release_db: count " . $mesg->count,138);
2660 # Walk through all possible FAI container ou's
2661 my @sql_list;
2662 my $timestamp= &get_time();
2663 foreach my $ou (@{$mesg->{entries}}) {
2664 my $tmp_classes= resolve_fai_classes($ou->dn, $ldap_handle, $session_id);
2665 if(defined($tmp_classes) && ref($tmp_classes) eq 'HASH') {
2666 my @tmp_array=get_fai_release_entries($tmp_classes);
2667 if(@tmp_array) {
2668 foreach my $entry (@tmp_array) {
2669 if(defined($entry) && ref($entry) eq 'HASH') {
2670 my $sql=
2671 "INSERT INTO $table_name "
2672 ."(timestamp, fai_release, class, type, state) VALUES ("
2673 .$timestamp.","
2674 ."'".$entry->{'release'}."',"
2675 ."'".$entry->{'class'}."',"
2676 ."'".$entry->{'type'}."',"
2677 ."'".$entry->{'state'}."')";
2678 push @sql_list, $sql;
2679 }
2680 }
2681 }
2682 }
2683 }
2685 daemon_log("$session_id DEBUG: create_fai_release_db: Inserting ".scalar @sql_list." entries to DB",138);
2686 &release_ldap_handle($ldap_handle);
2687 if(@sql_list) {
2688 unshift @sql_list, "VACUUM";
2689 unshift @sql_list, "DELETE FROM $table_name";
2690 $fai_release_db->exec_statementlist(\@sql_list);
2691 }
2692 daemon_log("$session_id DEBUG: create_fai_release_db: Done with inserting",138);
2693 } else {
2694 daemon_log("$session_id INFO: create_fai_release_db: error: " . $mesg->code, 5);
2695 }
2696 daemon_log("$session_id INFO: create_fai_release_db: finished",5);
2697 }
2698 return $result;
2699 }
2701 sub get_fai_types {
2702 my $tmp_classes = shift || return undef;
2703 my @result;
2705 foreach my $type(keys %{$tmp_classes}) {
2706 if(defined($tmp_classes->{$type}[0]) && (!($tmp_classes->{$type}[0] =~ /^.*?removed.*?$/))) {
2707 my $entry = {
2708 type => $type,
2709 state => $tmp_classes->{$type}[0],
2710 };
2711 push @result, $entry;
2712 }
2713 }
2715 return @result;
2716 }
2718 sub get_fai_state {
2719 my $result = "";
2720 my $tmp_classes = shift || return $result;
2722 foreach my $type(keys %{$tmp_classes}) {
2723 if(defined($tmp_classes->{$type}[0])) {
2724 $result = $tmp_classes->{$type}[0];
2726 # State is equal for all types in class
2727 last;
2728 }
2729 }
2731 return $result;
2732 }
2734 sub resolve_fai_classes {
2735 my ($fai_base, $ldap_handle, $session_id) = @_;
2736 if (not defined $session_id) { $session_id = 0; }
2737 my $result;
2738 my @possible_fai_classes= ("FAIscript", "FAIhook", "FAIpartitionTable", "FAItemplate", "FAIvariable", "FAIprofile", "FAIpackageList");
2739 my $fai_filter= "(|(&(objectClass=FAIclass)(|(objectClass=".join(")(objectClass=", @possible_fai_classes).")))(objectClass=FAIbranch))";
2740 my $fai_classes;
2742 daemon_log("$session_id DEBUG: Searching for FAI entries in base $fai_base", 138);
2743 my $mesg= $ldap_handle->search(
2744 base => $fai_base,
2745 scope => 'sub',
2746 attrs => ['cn','objectClass','FAIstate'],
2747 filter => $fai_filter,
2748 );
2749 daemon_log("$session_id DEBUG: Found ".$mesg->count()." FAI entries", 138);
2751 if($mesg->{'resultCode'} == 0 &&
2752 $mesg->count != 0) {
2753 foreach my $entry (@{$mesg->{entries}}) {
2754 if($entry->exists('cn')) {
2755 my $tmp_dn= $entry->dn();
2756 $tmp_dn= substr( $tmp_dn, 0, length($tmp_dn)
2757 - length($fai_base) - 1 );
2759 # Skip classname and ou dn parts for class
2760 my $tmp_release = ($1) if $tmp_dn =~ /^[^,]+,[^,]+,(.*?)$/;
2762 # Skip classes without releases
2763 if((!defined($tmp_release)) || length($tmp_release)==0) {
2764 next;
2765 }
2767 my $tmp_cn= $entry->get_value('cn');
2768 my $tmp_state= $entry->get_value('FAIstate');
2770 my $tmp_type;
2771 # Get FAI type
2772 for my $oclass(@{$entry->get_value('objectClass', asref => 1)}) {
2773 if(grep $_ eq $oclass, @possible_fai_classes) {
2774 $tmp_type= $oclass;
2775 last;
2776 }
2777 }
2779 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2780 # A Subrelease
2781 my @sub_releases = split(/,/, $tmp_release);
2783 # Walk through subreleases and build hash tree
2784 my $hash;
2785 while(my $tmp_sub_release = pop @sub_releases) {
2786 $hash .= "\{'$tmp_sub_release'\}->";
2787 }
2788 eval('push @{$fai_classes->'.$hash.'{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";');
2789 } else {
2790 # A branch, no subrelease
2791 push @{$fai_classes->{$tmp_release}->{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";
2792 }
2793 } elsif (!$entry->exists('cn')) {
2794 my $tmp_dn= $entry->dn();
2795 $tmp_dn= substr( $tmp_dn, 0, length($tmp_dn)
2796 - length($fai_base) - 1 );
2797 my $tmp_release = ($1) if $tmp_dn =~ /^(.*?)$/;
2799 # Skip classes without releases
2800 if((!defined($tmp_release)) || length($tmp_release)==0) {
2801 next;
2802 }
2804 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2805 # A Subrelease
2806 my @sub_releases= split(/,/, $tmp_release);
2808 # Walk through subreleases and build hash tree
2809 my $hash;
2810 while(my $tmp_sub_release = pop @sub_releases) {
2811 $hash .= "\{'$tmp_sub_release'\}->";
2812 }
2813 # Remove the last two characters
2814 chop($hash);
2815 chop($hash);
2817 eval('$fai_classes->'.$hash.'= {}');
2818 } else {
2819 # A branch, no subrelease
2820 if(!exists($fai_classes->{$tmp_release})) {
2821 $fai_classes->{$tmp_release} = {};
2822 }
2823 }
2824 }
2825 }
2827 # The hash is complete, now we can honor the copy-on-write based missing entries
2828 foreach my $release (keys %$fai_classes) {
2829 $result->{$release}= deep_copy(apply_fai_inheritance($fai_classes->{$release}));
2830 }
2831 }
2832 return $result;
2833 }
2835 sub apply_fai_inheritance {
2836 my $fai_classes = shift || return {};
2837 my $tmp_classes;
2839 # Get the classes from the branch
2840 foreach my $class (keys %{$fai_classes}) {
2841 # Skip subreleases
2842 if($class =~ /^ou=.*$/) {
2843 next;
2844 } else {
2845 $tmp_classes->{$class}= deep_copy($fai_classes->{$class});
2846 }
2847 }
2849 # Apply to each subrelease
2850 foreach my $subrelease (keys %{$fai_classes}) {
2851 if($subrelease =~ /ou=/) {
2852 foreach my $tmp_class (keys %{$tmp_classes}) {
2853 if(!exists($fai_classes->{$subrelease}->{$tmp_class})) {
2854 $fai_classes->{$subrelease}->{$tmp_class} =
2855 deep_copy($tmp_classes->{$tmp_class});
2856 } else {
2857 foreach my $type (keys %{$tmp_classes->{$tmp_class}}) {
2858 if(!exists($fai_classes->{$subrelease}->{$tmp_class}->{$type})) {
2859 $fai_classes->{$subrelease}->{$tmp_class}->{$type}=
2860 deep_copy($tmp_classes->{$tmp_class}->{$type});
2861 }
2862 }
2863 }
2864 }
2865 }
2866 }
2868 # Find subreleases in deeper levels
2869 foreach my $subrelease (keys %{$fai_classes}) {
2870 if($subrelease =~ /ou=/) {
2871 foreach my $subsubrelease (keys %{$fai_classes->{$subrelease}}) {
2872 if($subsubrelease =~ /ou=/) {
2873 apply_fai_inheritance($fai_classes->{$subrelease});
2874 }
2875 }
2876 }
2877 }
2879 return $fai_classes;
2880 }
2882 sub get_fai_release_entries {
2883 my $tmp_classes = shift || return;
2884 my $parent = shift || "";
2885 my @result = shift || ();
2887 foreach my $entry (keys %{$tmp_classes}) {
2888 if(defined($entry)) {
2889 if($entry =~ /^ou=.*$/) {
2890 my $release_name = $entry;
2891 $release_name =~ s/ou=//g;
2892 if(length($parent)>0) {
2893 $release_name = $parent."/".$release_name;
2894 }
2895 my @bufentries = get_fai_release_entries($tmp_classes->{$entry}, $release_name, @result);
2896 foreach my $bufentry(@bufentries) {
2897 push @result, $bufentry;
2898 }
2899 } else {
2900 my @types = get_fai_types($tmp_classes->{$entry});
2901 foreach my $type (@types) {
2902 push @result,
2903 {
2904 'class' => $entry,
2905 'type' => $type->{'type'},
2906 'release' => $parent,
2907 'state' => $type->{'state'},
2908 };
2909 }
2910 }
2911 }
2912 }
2914 return @result;
2915 }
2917 sub deep_copy {
2918 my $this = shift;
2919 if (not ref $this) {
2920 $this;
2921 } elsif (ref $this eq "ARRAY") {
2922 [map deep_copy($_), @$this];
2923 } elsif (ref $this eq "HASH") {
2924 +{map { $_ => deep_copy($this->{$_}) } keys %$this};
2925 } else { die "what type is $_?" }
2926 }
2929 sub session_run_result {
2930 my ($kernel, $heap, $client_answer) = @_[KERNEL, HEAP, ARG0];
2931 $kernel->sig(CHLD => "child_reap");
2932 }
2934 sub session_run_debug {
2935 my $result = $_[ARG0];
2936 print STDERR "$result\n";
2937 }
2939 sub session_run_done {
2940 my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
2941 delete $heap->{task}->{$task_id};
2942 if (exists $heap->{ldap_handle}->{$task_id}) {
2943 &release_ldap_handle($heap->{ldap_handle}->{$task_id});
2944 }
2945 delete $heap->{ldap_handle}->{$task_id};
2946 }
2949 sub create_sources_list {
2950 my $session_id = shift || 0;
2951 my $result="/tmp/gosa_si_tmp_sources_list";
2953 # Remove old file
2954 if(stat($result)) {
2955 unlink($result);
2956 &main::daemon_log("$session_id DEBUG: remove an old version of '$result'", 7);
2957 }
2959 open(my $fh, ">", "$result");
2960 if (not defined $fh) {
2961 &main::daemon_log("$session_id DEBUG: cannot open '$result' for writing", 7);
2962 return undef;
2963 }
2964 if(defined($main::ldap_server_dn) and length($main::ldap_server_dn) > 0) {
2965 my $ldap_handle = &get_ldap_handle($session_id);
2966 my $mesg=$ldap_handle->search(
2967 base => $main::ldap_server_dn,
2968 scope => 'base',
2969 attrs => 'FAIrepository',
2970 filter => 'objectClass=FAIrepositoryServer'
2971 );
2972 if($mesg->count) {
2973 foreach my $entry(@{$mesg->{'entries'}}) {
2974 foreach my $value(@{$entry->get_value('FAIrepository', asref => 1)}) {
2975 my ($server, $tag, $release, $sections)= split /\|/, $value;
2976 my $line = "deb $server $release";
2977 $sections =~ s/,/ /g;
2978 $line.= " $sections";
2979 print $fh $line."\n";
2980 }
2981 }
2982 }
2983 &release_ldap_handle($ldap_handle);
2984 } else {
2985 if (defined $main::ldap_server_dn){
2986 &main::daemon_log("$session_id ERROR: something wrong with ldap_server_dn '$main::ldap_server_dn', abort create_sources_list", 1);
2987 } else {
2988 &main::daemon_log("$session_id ERROR: no ldap_server_dn found, abort create_sources_list", 1);
2989 }
2990 }
2991 close($fh);
2993 return $result;
2994 }
2997 sub run_create_packages_list_db {
2998 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2999 my $session_id = $session->ID;
3000 my $task = POE::Wheel::Run->new(
3001 Priority => +20,
3002 Program => sub {&create_packages_list_db(undef, $session_id)},
3003 StdoutEvent => "session_run_result",
3004 StderrEvent => "session_run_debug",
3005 CloseEvent => "session_run_done",
3006 );
3007 $heap->{task}->{ $task->ID } = $task;
3008 }
3011 sub create_packages_list_db {
3012 my ($sources_file, $session_id) = @_;
3014 # it should not be possible to trigger a recreation of packages_list_db
3015 # while packages_list_db is under construction, so set flag packages_list_under_construction
3016 # which is tested befor recreation can be started
3017 if (-r $packages_list_under_construction) {
3018 daemon_log("$session_id WARNING: packages_list_db is right now under construction, please wait until this process is finished", 3);
3019 return;
3020 } else {
3021 daemon_log("$session_id INFO: create_packages_list_db: start", 5);
3022 # set packages_list_under_construction to true
3023 system("touch $packages_list_under_construction");
3024 @packages_list_statements=();
3025 }
3027 if (not defined $session_id) { $session_id = 0; }
3029 if (not defined $sources_file) {
3030 &main::daemon_log("$session_id INFO: no sources_file given for creating packages list so trigger creation of it", 5);
3031 $sources_file = &create_sources_list($session_id);
3032 }
3034 if (not defined $sources_file) {
3035 &main::daemon_log("$session_id ERROR: no sources_file given under '$sources_file', skip create_packages_list_db", 1);
3036 unlink($packages_list_under_construction);
3037 return;
3038 }
3040 my $line;
3042 open(my $CONFIG, "<", "$sources_file") or do {
3043 daemon_log( "$session_id ERROR: create_packages_list_db: Failed to open '$sources_file'", 1);
3044 unlink($packages_list_under_construction);
3045 return;
3046 };
3048 # Read lines
3049 while ($line = <$CONFIG>){
3050 # Unify
3051 chop($line);
3052 $line =~ s/^\s+//;
3053 $line =~ s/^\s+/ /;
3055 # Strip comments
3056 $line =~ s/#.*$//g;
3058 # Skip empty lines
3059 if ($line =~ /^\s*$/){
3060 next;
3061 }
3063 # Interpret deb line
3064 if ($line =~ /^deb [^\s]+\s[^\s]+\s[^\s]+/){
3065 my( $baseurl, $dist, $sections ) = ($line =~ /^deb\s([^\s]+)\s+([^\s]+)\s+(.*)$/);
3066 my $section;
3067 foreach $section (split(' ', $sections)){
3068 &parse_package_info( $baseurl, $dist, $section, $session_id );
3069 }
3070 }
3071 else {
3072 daemon_log("$session_id ERROR: cannot parse line '$line'", 1);
3073 }
3074 }
3076 close ($CONFIG);
3078 if(keys(%repo_dirs)) {
3079 find(\&cleanup_and_extract, keys( %repo_dirs ));
3080 &main::strip_packages_list_statements();
3081 $packages_list_db->exec_statementlist(\@packages_list_statements);
3082 }
3083 unlink($packages_list_under_construction);
3084 daemon_log("$session_id INFO: create_packages_list_db: finished", 5);
3085 return;
3086 }
3088 # This function should do some intensive task to minimize the db-traffic
3089 sub strip_packages_list_statements {
3090 my @existing_entries= @{$packages_list_db->exec_statement("SELECT * FROM $main::packages_list_tn")};
3091 my @new_statement_list=();
3092 my $hash;
3093 my $insert_hash;
3094 my $update_hash;
3095 my $delete_hash;
3096 my $known_packages_hash;
3097 my $local_timestamp=get_time();
3099 foreach my $existing_entry (@existing_entries) {
3100 $hash->{@{$existing_entry}[0]}->{@{$existing_entry}[1]}->{@{$existing_entry}[2]}= $existing_entry;
3101 }
3103 foreach my $statement (@packages_list_statements) {
3104 if($statement =~ /^INSERT/i) {
3105 # Assign the values from the insert statement
3106 my ($distribution,$package,$version,$section,$description,$template,$timestamp) = ($1,$2,$3,$4,$5,$6,$7) if $statement =~
3107 /^INSERT\s+?INTO\s+?$main::packages_list_tn\s+?VALUES\s*?\('(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)'\s*?\)$/si;
3108 if(exists($hash->{$distribution}->{$package}->{$version})) {
3109 # If section or description has changed, update the DB
3110 if(
3111 (! (@{$hash->{$distribution}->{$package}->{$version}}[3] eq $section)) or
3112 (! (@{$hash->{$distribution}->{$package}->{$version}}[4] eq $description))
3113 ) {
3114 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,undef);
3115 } else {
3116 # package is already present in database. cache this knowledge for later use
3117 @{$known_packages_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
3118 }
3119 } else {
3120 # Insert a non-existing entry to db
3121 @{$insert_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
3122 }
3123 } elsif ($statement =~ /^UPDATE/i) {
3124 my ($template,$package,$version) = ($1,$2,$3) if $statement =~
3125 /^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;
3126 foreach my $distribution (keys %{$hash}) {
3127 if(exists($insert_hash->{$distribution}->{$package}->{$version})) {
3128 # update the insertion hash to execute only one query per package (insert instead insert+update)
3129 @{$insert_hash->{$distribution}->{$package}->{$version}}[5]= $template;
3130 } elsif(exists($hash->{$distribution}->{$package}->{$version})) {
3131 if( ! (@{$hash->{$distribution}->{$package}->{$version}}[5] eq $template)) {
3132 my $section;
3133 my $description;
3134 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) and
3135 length(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) > 0 ) {
3136 $section= @{$update_hash->{$distribution}->{$package}->{$version}}[3];
3137 }
3138 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
3139 $description= @{$update_hash->{$distribution}->{$package}->{$version}}[4];
3140 }
3141 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
3142 }
3143 }
3144 }
3145 }
3146 }
3148 # Check for orphaned entries
3149 foreach my $existing_entry (@existing_entries) {
3150 my $distribution= @{$existing_entry}[0];
3151 my $package= @{$existing_entry}[1];
3152 my $version= @{$existing_entry}[2];
3153 my $section= @{$existing_entry}[3];
3155 if(
3156 exists($insert_hash->{$distribution}->{$package}->{$version}) ||
3157 exists($update_hash->{$distribution}->{$package}->{$version}) ||
3158 exists($known_packages_hash->{$distribution}->{$package}->{$version})
3159 ) {
3160 next;
3161 } else {
3162 # Insert entry to delete hash
3163 @{$delete_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section);
3164 }
3165 }
3167 # unroll the insert hash
3168 foreach my $distribution (keys %{$insert_hash}) {
3169 foreach my $package (keys %{$insert_hash->{$distribution}}) {
3170 foreach my $version (keys %{$insert_hash->{$distribution}->{$package}}) {
3171 push @new_statement_list, "INSERT INTO $main::packages_list_tn VALUES ('$distribution','$package','$version',"
3172 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[3]',"
3173 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[4]',"
3174 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[5]',"
3175 ."'$local_timestamp')";
3176 }
3177 }
3178 }
3180 # unroll the update hash
3181 foreach my $distribution (keys %{$update_hash}) {
3182 foreach my $package (keys %{$update_hash->{$distribution}}) {
3183 foreach my $version (keys %{$update_hash->{$distribution}->{$package}}) {
3184 my $set = "";
3185 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3])) {
3186 $set .= "section = '@{$update_hash->{$distribution}->{$package}->{$version}}[3]', ";
3187 }
3188 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
3189 $set .= "description = '@{$update_hash->{$distribution}->{$package}->{$version}}[4]', ";
3190 }
3191 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[5])) {
3192 $set .= "template = '@{$update_hash->{$distribution}->{$package}->{$version}}[5]', ";
3193 }
3194 if(defined($set) and length($set) > 0) {
3195 $set .= "timestamp = '$local_timestamp'";
3196 } else {
3197 next;
3198 }
3199 push @new_statement_list,
3200 "UPDATE $main::packages_list_tn SET $set WHERE"
3201 ." distribution = '$distribution'"
3202 ." AND package = '$package'"
3203 ." AND version = '$version'";
3204 }
3205 }
3206 }
3208 # unroll the delete hash
3209 foreach my $distribution (keys %{$delete_hash}) {
3210 foreach my $package (keys %{$delete_hash->{$distribution}}) {
3211 foreach my $version (keys %{$delete_hash->{$distribution}->{$package}}) {
3212 my $section = @{$delete_hash->{$distribution}->{$package}->{$version}}[3];
3213 push @new_statement_list, "DELETE FROM $main::packages_list_tn WHERE distribution='$distribution' AND package='$package' AND version='$version' AND section='$section'";
3214 }
3215 }
3216 }
3218 unshift(@new_statement_list, "VACUUM");
3220 @packages_list_statements = @new_statement_list;
3221 }
3224 sub parse_package_info {
3225 my ($baseurl, $dist, $section, $session_id)= @_;
3226 my ($package);
3227 if (not defined $session_id) { $session_id = 0; }
3228 my ($path) = ($baseurl =~ m%://[^/]*(.*)$%);
3229 $repo_dirs{ "${repo_path}/pool" } = 1;
3231 foreach $package ("Packages.gz"){
3232 daemon_log("$session_id DEBUG: create_packages_list: fetch $baseurl, $dist, $section", 266);
3233 get_package( "$baseurl/dists/$dist/$section/binary-$arch/$package", "$outdir/$dist/$section", $session_id );
3234 parse_package( "$outdir/$dist/$section", $dist, $path, $session_id );
3235 }
3237 }
3240 sub get_package {
3241 my ($url, $dest, $session_id)= @_;
3242 if (not defined $session_id) { $session_id = 0; }
3244 my $tpath = dirname($dest);
3245 -d "$tpath" || mkpath "$tpath";
3247 # This is ugly, but I've no time to take a look at "how it works in perl"
3248 if(0 == system("wget '$url' -O '$dest' 2>/dev/null") ) {
3249 system("gunzip -cd '$dest' > '$dest.in'");
3250 daemon_log("$session_id DEBUG: run command: gunzip -cd '$dest' > '$dest.in'", 266);
3251 unlink($dest);
3252 daemon_log("$session_id DEBUG: delete file '$dest'", 266);
3253 } else {
3254 daemon_log("$session_id ERROR: create_packages_list_db: get_packages: fetching '$url' into '$dest' failed!", 1);
3255 }
3256 return 0;
3257 }
3260 sub parse_package {
3261 my ($path, $dist, $srv_path, $session_id)= @_;
3262 if (not defined $session_id) { $session_id = 0;}
3263 my ($package, $version, $section, $description);
3264 my $timestamp = &get_time();
3266 if(not stat("$path.in")) {
3267 daemon_log("$session_id ERROR: create_packages_list: parse_package: file '$path.in' is not readable",1);
3268 return;
3269 }
3271 open(my $PACKAGES, "<", "$path.in");
3272 if(not defined($PACKAGES)) {
3273 daemon_log("$session_id ERROR: create_packages_list_db: parse_package: cannot open '$path.in'",1);
3274 return;
3275 }
3277 # Read lines
3278 while (<$PACKAGES>){
3279 my $line = $_;
3280 # Unify
3281 chop($line);
3283 # Use empty lines as a trigger
3284 if ($line =~ /^\s*$/){
3285 my $sql = "INSERT INTO packages_list VALUES ('$dist', '$package', '$version', '$section', '$description', '', '$timestamp')";
3286 push(@packages_list_statements, $sql);
3287 $package = "none";
3288 $version = "none";
3289 $section = "none";
3290 $description = "none";
3291 next;
3292 }
3294 # Trigger for package name
3295 if ($line =~ /^Package:\s/){
3296 ($package)= ($line =~ /^Package: (.*)$/);
3297 next;
3298 }
3300 # Trigger for version
3301 if ($line =~ /^Version:\s/){
3302 ($version)= ($line =~ /^Version: (.*)$/);
3303 next;
3304 }
3306 # Trigger for description
3307 if ($line =~ /^Description:\s/){
3308 ($description)= &encode_base64(($line =~ /^Description: (.*)$/));
3309 next;
3310 }
3312 # Trigger for section
3313 if ($line =~ /^Section:\s/){
3314 ($section)= ($line =~ /^Section: (.*)$/);
3315 next;
3316 }
3318 # Trigger for filename
3319 if ($line =~ /^Filename:\s/){
3320 my ($filename) = ($line =~ /^Filename: (.*)$/);
3321 store_fileinfo( $package, $filename, $dist, $srv_path, $version, $repo_path );
3322 next;
3323 }
3324 }
3326 close( $PACKAGES );
3327 unlink( "$path.in" );
3328 }
3331 sub store_fileinfo {
3332 my( $package, $file, $dist, $path, $vers, $srvdir) = @_;
3334 my %fileinfo = (
3335 'package' => $package,
3336 'dist' => $dist,
3337 'version' => $vers,
3338 );
3340 $repo_files{ "${srvdir}/$file" } = \%fileinfo;
3341 }
3344 sub cleanup_and_extract {
3345 my $fileinfo = $repo_files{ $File::Find::name };
3347 if( defined $fileinfo ) {
3348 my $dir = "$outdir/$fileinfo->{ 'dist' }/debconf.d";
3349 my $sql;
3350 my $package = $fileinfo->{ 'package' };
3351 my $newver = $fileinfo->{ 'version' };
3353 mkpath($dir);
3354 system( "dpkg -e '$File::Find::name' '$dir/DEBIAN'" );
3356 if( -f "$dir/DEBIAN/templates" ) {
3358 daemon_log("0 DEBUG: Found debconf templates in '$package' - $newver", 266);
3360 my $tmpl= ""; {
3361 local $/=undef;
3362 open(my $FILE, "$dir/DEBIAN/templates");
3363 $tmpl = &encode_base64(<$FILE>);
3364 close($FILE);
3365 }
3366 rmtree("$dir/DEBIAN/templates");
3368 $sql= "update $main::packages_list_tn set template = '$tmpl' where package = '$package' and version = '$newver';";
3369 push @packages_list_statements, $sql;
3370 }
3371 }
3373 return;
3374 }
3377 sub prepare_server_registration
3378 {
3379 # Add foreign server from cfg file
3380 my @foreign_server_list;
3381 if ($foreign_server_string ne "") {
3382 my @cfg_foreign_server_list = split(",", $foreign_server_string);
3383 foreach my $foreign_server (@cfg_foreign_server_list) {
3384 push(@foreign_server_list, $foreign_server);
3385 }
3387 daemon_log("0 INFO: found foreign server in config file: ".join(", ", @foreign_server_list), 5);
3388 }
3390 # Perform a DNS lookup for server registration if flag is true
3391 if ($dns_lookup eq "true") {
3392 # Add foreign server from dns
3393 my @tmp_servers;
3394 if (not $server_domain) {
3395 # Try our DNS Searchlist
3396 for my $domain(get_dns_domains()) {
3397 chomp($domain);
3398 my ($tmp_domains, $error_string) = &get_server_addresses($domain);
3399 if(@$tmp_domains) {
3400 for my $tmp_server(@$tmp_domains) {
3401 push @tmp_servers, $tmp_server;
3402 }
3403 }
3404 }
3405 if(@tmp_servers && length(@tmp_servers)==0) {
3406 daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
3407 }
3408 } else {
3409 @tmp_servers = &get_server_addresses($server_domain);
3410 if( 0 == @tmp_servers ) {
3411 daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
3412 }
3413 }
3415 daemon_log("0 INFO: found foreign server via DNS ".join(", ", @tmp_servers), 5);
3417 foreach my $server (@tmp_servers) {
3418 unshift(@foreign_server_list, $server);
3419 }
3420 } else {
3421 daemon_log("0 INFO: DNS lookup for server registration is disabled", 5);
3422 }
3424 # eliminate duplicate entries
3425 @foreign_server_list = &del_doubles(@foreign_server_list);
3426 my $all_foreign_server = join(", ", @foreign_server_list);
3427 daemon_log("0 INFO: found foreign server in config file and DNS: '$all_foreign_server'", 5);
3429 # add all found foreign servers to known_server
3430 my $cur_timestamp = &get_time();
3431 foreach my $foreign_server (@foreign_server_list) {
3433 # do not add myself to known_server_db
3434 if (&is_local($foreign_server)) { next; }
3435 ######################################
3437 my $res = $known_server_db->add_dbentry( {table=>$known_server_tn,
3438 primkey=>['hostname'],
3439 hostname=>$foreign_server,
3440 macaddress=>"",
3441 status=>'not_yet_registered',
3442 hostkey=>"none",
3443 loaded_modules => "none",
3444 timestamp=>$cur_timestamp,
3445 update_time=>'19700101000000',
3446 } );
3447 }
3448 }
3450 sub register_at_foreign_servers {
3451 my ($kernel) = $_[KERNEL];
3453 # Update status and update-time of all si-server with expired update_time and
3454 # block them for race conditional registration processes of other si-servers.
3455 my $act_time = &get_time();
3456 my $block_statement = "UPDATE $known_server_tn SET status='new_server',update_time='19700101000000' WHERE (CAST(update_time AS UNSIGNED))<$act_time ";
3457 my $block_res = $known_server_db->exec_statement($block_statement);
3459 # Fetch all si-server from db where update_time is younger than act_time
3460 my $fetch_statement = "SELECT * FROM $known_server_tn WHERE update_time='19700101000000'";
3461 my $fetch_res = $known_server_db->exec_statement($fetch_statement);
3463 # Detect already connected clients. Will be added to registration msg later.
3464 my $client_sql = "SELECT * FROM $known_clients_tn";
3465 my $client_res = $known_clients_db->exec_statement($client_sql);
3467 # Send registration messag to all fetched si-server
3468 foreach my $hit (@$fetch_res) {
3469 my $hostname = @$hit[0];
3470 my $hostkey = &create_passwd;
3472 # Add already connected clients to registration message
3473 my $myhash = &create_xml_hash('new_server', $server_address, $hostname);
3474 &add_content2xml_hash($myhash, 'key', $hostkey);
3475 map(&add_content2xml_hash($myhash, 'client', @{$_}[0].",".@{$_}[4]), @$client_res);
3477 # Add locally loaded gosa-si modules to registration message
3478 my $loaded_modules = {};
3479 while (my ($package, $pck_info) = each %$known_modules) {
3480 next if ((!defined(@$pck_info[2])) || (!(ref (@$pck_info[2]) eq 'HASH')));
3481 foreach my $act_module (keys(%{@$pck_info[2]})) {
3482 $loaded_modules->{$act_module} = "";
3483 }
3484 }
3485 map(&add_content2xml_hash($myhash, "loaded_modules", $_), keys(%$loaded_modules));
3487 # Add macaddress to registration message
3488 my ($host_ip, $host_port) = split(/:/, $hostname);
3489 my $local_ip = &get_local_ip_for_remote_ip($host_ip);
3490 my $network_interface= &get_interface_for_ip($local_ip);
3491 my $host_mac = &get_mac_for_interface($network_interface);
3492 &add_content2xml_hash($myhash, 'macaddress', $host_mac);
3494 # Build registration message and send it
3495 my $foreign_server_msg = &create_xml_string($myhash);
3496 my $error = &send_msg_to_target($foreign_server_msg, $hostname, $ServerPackages_key, "new_server", 0);
3497 }
3500 # After n sec perform a check of all server registration processes
3501 $kernel->delay_set("control_server_registration", 2);
3503 return;
3504 }
3507 sub control_server_registration {
3508 my ($kernel) = $_[KERNEL];
3510 # Check if all registration processes succeed or not
3511 my $select_statement = "SELECT * FROM $known_server_tn WHERE status='new_server'";
3512 my $select_res = $known_server_db->exec_statement($select_statement);
3514 # If at least one registration process failed, maybe in case of a race condition
3515 # with a foreign registration process
3516 if (@$select_res > 0)
3517 {
3518 # Release block statement 'new_server' to make the server accessible
3519 # for foreign registration processes
3520 my $update_statement = "UPDATE $known_server_tn SET status='waiting' WHERE status='new_server'";
3521 my $update_res = $known_server_db->exec_statement($update_statement);
3523 # Set a random delay to avoid the registration race condition
3524 my $new_foreign_servers_register_delay = int(rand(4))+1;
3525 $kernel->delay_set("register_at_foreign_servers", $new_foreign_servers_register_delay);
3526 }
3527 # If all registration processes succeed
3528 else
3529 {
3530 $kernel->delay_set("register_at_foreign_servers", $foreign_servers_register_delay);
3531 }
3533 return;
3534 }
3536 sub start_daemon {
3538 #to prevent zombie child
3539 $SIG{CHLD} = 'IGNORE';
3541 if( ! $foreground ) {
3542 chdir '/' or die "Can't chdir to /: $!";
3543 umask 0;
3544 open STDIN, '+>/dev/null' or die "Can't read /dev/null: $!";
3545 open STDOUT, '+>&STDIN' or die "Can't write to /dev/null: $!";
3546 open STDERR, '+>&STDIN' or die "Can't write to /dev/null: $!";
3547 defined($pid = fork) or die "Can't fork: $!";
3548 exit if $pid;
3549 setsid or die "Can't start a new session: $!";
3550 }
3551 return;
3552 }
3554 sub put_version {
3556 # parse head url and revision from svn
3557 $server_version =~ /^\$HeadURL: (\S+) \$:\$Rev: (\d+) \$$/;
3558 $server_headURL = defined $1 ? $1 : 'unknown' ;
3559 $server_revision = defined $2 ? $2 : 'unknown' ;
3560 if ($server_headURL =~ /\/tag\// ||
3561 $server_headURL =~ /\/branches\// ) {
3562 $server_status = "stable";
3563 } else {
3564 $server_status = "developmental" ;
3565 }
3566 return;
3567 }
3569 sub get_perms_admin {
3570 # Determine root uid and adm gid, used for creating log files
3571 $root_uid = getpwnam('root');
3572 $adm_gid = getgrnam('adm');
3573 if(not defined $adm_gid){
3574 $adm_gid = getgrnam('root');
3575 }
3576 return;
3577 }
3579 sub open_log {
3580 # Prepare log file and set permissions
3581 open(my $log, ">>", "$log_file");
3582 close($log);
3583 chmod(0440, $log_file);
3584 chown($root_uid, $adm_gid, $log_file);
3586 return;
3587 }
3589 sub create_pid {
3590 # Create the PID object
3591 $pid = File::Pid->new({ file => $pid_file });
3593 # Write the PID file
3594 $pid->write;
3596 return;
3597 }
3599 #
3600 #==== MAIN = main ==============================================================
3601 #
3602 # Parse options and allow '-vvv'
3603 Getopt::Long::Configure( 'bundling' );
3604 GetOptions( 'v|verbose+' => \$verbose,
3605 'h|help' => \&usage,
3606 'c|config=s' => \$config,
3607 'x|dump-config=i' => \$dump_config,
3608 'f|foreground' => \$foreground,
3609 'd=s' => \$debug_parts)
3610 or usage( '', 1 );
3612 # We may want to dump the default configuration
3613 if( defined $dump_config ) {
3614 if($dump_config==1) {
3615 } elsif ($dump_config==2) {
3616 dump_configuration( $dump_config );
3617 } else {
3618 usage( "Dump configuration value has to be 1 or 2" );
3619 }
3620 }
3622 # read and set config parameters
3623 &read_configfile($config, %cfg_defaults);
3625 # daemonize the program
3626 &start_daemon($pid, $foreground);
3628 # create pid file
3629 &create_pid($pid, $pid_file);
3631 # Determine root uid and adm gid, used for creating log files
3632 &get_perms_admin($root_uid, $adm_gid);
3634 # put version
3635 &put_version($server_status_hash, $server_version, $server_headURL, $server_revision, $server_status);
3637 #open log file
3638 &open_log($root_uid, $adm_gid, $log_file);
3640 # prepare directory for databases
3641 mkpath('/var/lib/gosa-si', 0, {owner=>'root', group=>'root', mode=> '0755'});
3643 # remove leftover files in tmp for packaged.db populate
3644 rmtree( '/tmp/packages_list_db',0,1);
3646 # remove list of sources from apt.sources.list
3647 unlink '/tmp/gosa_si_tmp_sources_list';
3649 # remove marker that the list creation is in progress
3650 unlink '/tmp/packages_list_creation_in_progress';
3652 daemon_log(" ", 1);
3653 daemon_log("$0 started!", 1);
3654 daemon_log("status: $server_status", 1);
3655 daemon_log($server_status_hash->{$server_status}.": $server_revision", 1);
3657 # Buildup data bases
3658 {
3659 no strict "refs";
3661 daemon_log("0 INFO: importing database module '$db_module'", 1);
3663 if ($db_module eq "DBmysql") {
3665 # connect to incoming_db
3666 $incoming_db = ("GOsaSI::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3668 # connect to gosa-si job queue
3669 $job_db = ("GOsaSI::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3671 # connect to known_clients_db
3672 $known_clients_db = ("GOsaSI::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3674 # connect to foreign_clients_db
3675 $foreign_clients_db = ("GOsaSI::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3677 # connect to known_server_db
3678 $known_server_db = ("GOsaSI::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3680 # connect to login_usr_db
3681 $login_users_db = ("GOsaSI::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3683 # connect to fai_server_db
3684 $fai_server_db = ("GOsaSI::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3686 # connect to fai_release_db
3687 $fai_release_db = ("GOsaSI::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3689 # connect to packages_list_db
3690 $packages_list_db = ("GOsaSI::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3692 # connect to messaging_db
3693 $messaging_db = ("GOsaSI::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3695 } elsif ($db_module eq "DBsqlite") {
3697 daemon_log("0 INFO: Removing SQLlite lock files", 1);
3699 # delete old DBsqlite lock files to be replace by rmtree !!
3700 system('rm -f /var/lib/gosa-si/*.si.lock*');
3701 #rmtree( '/var/lib/gosa-si/',0,1);
3703 # connect to incoming_db
3704 unlink($incoming_file_name);
3705 $incoming_db = ("GOsaSI::".$db_module)->new($incoming_file_name);
3707 # connect to gosa-si job queue
3708 $job_db = ("GOsaSI::".$db_module)->new($job_queue_file_name);
3710 # connect to known_clients_db
3711 $known_clients_db = ("GOsaSI::".$db_module)->new($known_clients_file_name);
3713 # connect to foreign_clients_db
3714 $foreign_clients_db = ("GOsaSI::".$db_module)->new($foreign_clients_file_name);
3716 # connect to known_server_db
3717 $known_server_db = ("GOsaSI::".$db_module)->new($known_server_file_name);
3719 # connect to login_usr_db
3720 $login_users_db = ("GOsaSI::".$db_module)->new($login_users_file_name);
3722 # connect to fai_server_db
3723 $fai_server_db = ("GOsaSI::".$db_module)->new($fai_server_file_name);
3725 # connect to fai_release_db
3726 $fai_release_db = ("GOsaSI::".$db_module)->new($fai_release_file_name);
3728 # connect to packages_list_db
3729 $packages_list_db = ("GOsaSI::".$db_module)->new($packages_list_file_name);
3731 # connect to messaging_db
3732 $messaging_db = ("GOsaSI::".$db_module)->new($messaging_file_name);
3733 }
3734 }
3736 # Creating tables
3738 daemon_log("0 INFO: creating tables in database with '$db_module'", 1);
3740 $messaging_db->create_table($messaging_tn, \@messaging_col_names);
3741 $packages_list_db->create_table($packages_list_tn, \@packages_list_col_names);
3742 $fai_release_db->create_table($fai_release_tn, \@fai_release_col_names);
3743 $fai_server_db->create_table($fai_server_tn, \@fai_server_col_names);
3744 $login_users_db->create_table($login_users_tn, \@login_users_col_names);
3745 $known_server_db->create_table($known_server_tn, \@known_server_col_names);
3746 $foreign_clients_db->create_table($foreign_clients_tn, \@foreign_clients_col_names);
3747 $known_clients_db->create_table($known_clients_tn, \@known_clients_col_names);
3748 $incoming_db->create_table($incoming_tn, \@incoming_col_names);
3749 $job_db->create_table($job_queue_tn, \@job_queue_col_names);
3751 # create xml object used for en/decrypting
3752 $xml = new XML::Simple();
3754 # Import all modules
3755 &import_modules;
3757 # Check wether all modules are gosa-si valid passwd check
3758 &password_check;
3760 # Check DNS and config file for server registration
3761 if ($serverPackages_enabled eq "true") { &prepare_server_registration; }
3763 # Create functions hash
3764 while (my ($module, @mod_info) = each %$known_modules)
3765 {
3766 while (my ($plugin, $functions) = each %{$mod_info[0][2]})
3767 {
3768 while (my ($function, $nothing) = each %$functions )
3769 {
3770 $known_functions->{$function} = $nothing;
3771 }
3772 }
3773 }
3775 # Prepare for using Opsi
3776 if ($opsi_enabled eq "true") {
3777 use JSON::RPC::Client;
3778 use XML::Quote qw(:all);
3779 $opsi_url= "https://".$opsi_admin.":".$opsi_password."@".$opsi_server.":4447/rpc";
3780 $opsi_client = new JSON::RPC::Client;
3781 }
3783 daemon_log("0 INFO: start socket for incoming xml messages at port '$server_port' ", 1);
3785 POE::Component::Server::TCP->new(
3786 Alias => "TCP_SERVER",
3787 Port => $server_port,
3788 ClientInput => sub {
3789 my ($kernel, $input, $heap, $session) = @_[KERNEL, ARG0, HEAP, SESSION];
3790 my $session_id = $session->ID;
3791 if ($input =~ /;([\d\.]+):([\d]+)$/)
3792 {
3793 # Messages from other servers should be blocked if config option is set
3794 if (($2 eq $server_port) && ($serverPackages_enabled eq "false"))
3795 {
3796 return;
3797 }
3798 &daemon_log("$session_id DEBUG: incoming message from '$1:$2'", 11);
3799 }
3800 else
3801 {
3802 my $remote_ip = $heap->{'remote_ip'};
3803 &daemon_log("$session_id DEBUG: incoming message from '$remote_ip'", 11);
3804 }
3805 push(@msgs_to_decrypt, $input);
3806 $kernel->yield("msg_to_decrypt");
3807 },
3808 InlineStates => {
3809 msg_to_decrypt => \&msg_to_decrypt,
3810 watch_for_next_tasks => \&watch_for_next_tasks,
3811 next_task => \&next_task,
3812 task_result => \&handle_task_result,
3813 task_done => \&handle_task_done,
3814 task_debug => \&handle_task_debug,
3815 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!" },
3816 }
3817 );
3819 # create session for repeatedly checking the job queue for jobs
3820 POE::Session->create(
3821 inline_states => {
3822 _start => \&session_start,
3823 register_at_foreign_servers => \®ister_at_foreign_servers,
3824 control_server_registration => \&control_server_registration,
3825 sig_handler => \&sig_handler,
3826 next_task => \&next_task,
3827 task_result => \&handle_task_result,
3828 task_done => \&handle_task_done,
3829 task_debug => \&handle_task_debug,
3830 watch_for_new_messages => \&watch_for_new_messages,
3831 watch_for_delivery_messages => \&watch_for_delivery_messages,
3832 watch_for_done_messages => \&watch_for_done_messages,
3833 watch_for_new_jobs => \&watch_for_new_jobs,
3834 watch_for_modified_jobs => \&watch_for_modified_jobs,
3835 watch_for_done_jobs => \&watch_for_done_jobs,
3836 watch_for_opsi_jobs => \&watch_for_opsi_jobs,
3837 watch_for_old_known_clients => \&watch_for_old_known_clients,
3838 create_packages_list_db => \&run_create_packages_list_db,
3839 create_fai_server_db => \&run_create_fai_server_db,
3840 create_fai_release_db => \&run_create_fai_release_db,
3841 recreate_packages_db => \&run_recreate_packages_db,
3842 session_run_result => \&session_run_result,
3843 session_run_debug => \&session_run_debug,
3844 session_run_done => \&session_run_done,
3845 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!" },
3846 }
3847 );
3850 POE::Kernel->run();
3851 exit;