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 # Variables declared in config file are always set to 'our'
148 our (%cfg_defaults, $log_file, $pid_file, $pid,
149 $server_ip, $server_port, $ClientPackages_key, $dns_lookup,
150 $arp_activ, $gosa_unit_tag,
151 $GosaPackages_key, $gosa_timeout,
152 $serverPackages_enabled, $foreign_server_string, $server_domain, $ServerPackages_key, $foreign_servers_register_delay,
153 $wake_on_lan_passwd, $job_synchronization, $modified_jobs_loop_delay,
154 $arp_enabled, $arp_interface,
155 $opsi_enabled, $opsi_server, $opsi_admin, $opsi_password,
156 $new_systems_ou,
157 $arch,
158 );
160 # additional variable which should be globaly accessable
161 our $server_address;
162 our $server_mac_address;
163 our $gosa_address;
164 our $no_arp;
165 our $forground;
166 our $cfg_file;
167 our ($ldap_uri, $ldap_base, $ldap_admin_dn, $ldap_admin_password, $ldap_server_dn, $ldap_version, $ldap_retry_sec);
168 our ($mysql_username, $mysql_password, $mysql_database, $mysql_host);
169 our $known_modules;
170 our $known_functions;
171 our $root_uid;
172 our $adm_gid;
173 our $verbose= 0;
174 our $global_kernel;
176 # where is the config stored by default and his name
177 our $config = '/etc/gosa-si/server.conf';
179 # by default dumping of config is undefined
180 our $dump_config = undef;
182 # if foreground is not null, script will be not forked to background
183 $foreground = 0 ;
185 # specifies the timeout seconds while checking the online status of a registrating client
186 $ping_timeout = 5;
188 $no_arp = 0;
189 my $packages_list_under_construction = "/tmp/packages_list_creation_in_progress";
190 my @packages_list_statements;
191 my $watch_for_new_jobs_in_progress = 0;
193 # holds all incoming decrypted messages
194 our $incoming_db;
195 our $incoming_tn = 'incoming';
196 my $incoming_file_name;
197 my @incoming_col_names = ("id INTEGER PRIMARY KEY",
198 "timestamp VARCHAR(14) DEFAULT 'none'",
199 "headertag VARCHAR(255) DEFAULT 'none'",
200 "targettag VARCHAR(255) DEFAULT 'none'",
201 "xmlmessage TEXT",
202 "module VARCHAR(255) DEFAULT 'none'",
203 "sessionid VARCHAR(255) DEFAULT '0'",
204 );
206 # holds all gosa jobs
207 our $job_db;
208 our $job_queue_tn = 'jobs';
209 my $job_queue_file_name;
210 my @job_queue_col_names = ("id INTEGER PRIMARY KEY",
211 "timestamp VARCHAR(14) DEFAULT 'none'",
212 "status VARCHAR(255) DEFAULT 'none'",
213 "result TEXT",
214 "progress VARCHAR(255) DEFAULT 'none'",
215 "headertag VARCHAR(255) DEFAULT 'none'",
216 "targettag VARCHAR(255) DEFAULT 'none'",
217 "xmlmessage TEXT",
218 "macaddress VARCHAR(17) DEFAULT 'none'",
219 "plainname VARCHAR(255) DEFAULT 'none'",
220 "siserver VARCHAR(255) DEFAULT 'none'",
221 "modified INTEGER DEFAULT '0'",
222 "periodic VARCHAR(6) DEFAULT 'none'",
223 );
225 # holds all other gosa-si-server
226 our $known_server_db;
227 our $known_server_tn = "known_server";
228 my $known_server_file_name;
229 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)");
231 # holds all registrated clients
232 our $known_clients_db;
233 our $known_clients_tn = "known_clients";
234 my $known_clients_file_name;
235 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)");
237 # holds all registered clients at a foreign server
238 our $foreign_clients_db;
239 our $foreign_clients_tn = "foreign_clients";
240 my $foreign_clients_file_name;
241 my @foreign_clients_col_names = ("hostname VARCHAR(255)", "macaddress VARCHAR(17)", "regserver VARCHAR(255)", "timestamp VARCHAR(14)");
243 # holds all logged in user at each client
244 our $login_users_db;
245 our $login_users_tn = "login_users";
246 my $login_users_file_name;
247 my @login_users_col_names = ("client VARCHAR(255)", "user VARCHAR(255)", "timestamp VARCHAR(14)", "regserver VARCHAR(255) DEFAULT 'localhost'");
249 # holds all fai server, the debian release and tag
250 our $fai_server_db;
251 our $fai_server_tn = "fai_server";
252 my $fai_server_file_name;
253 our @fai_server_col_names = ("timestamp VARCHAR(14)", "server VARCHAR(255)", "fai_release VARCHAR(255)", "sections VARCHAR(255)", "tag VARCHAR(255)");
255 our $fai_release_db;
256 our $fai_release_tn = "fai_release";
257 my $fai_release_file_name;
258 our @fai_release_col_names = ("timestamp VARCHAR(14)", "fai_release VARCHAR(255)", "class VARCHAR(255)", "type VARCHAR(255)", "state VARCHAR(255)");
260 # holds all packages available from different repositories
261 our $packages_list_db;
262 our $packages_list_tn = "packages_list";
263 my $packages_list_file_name;
264 our @packages_list_col_names = ("distribution VARCHAR(255)", "package VARCHAR(255)", "version VARCHAR(255)", "section VARCHAR(255)", "description TEXT", "template LONGBLOB", "timestamp VARCHAR(14)");
265 my $outdir = "/tmp/packages_list_db";
267 # holds all messages which should be delivered to a user
268 our $messaging_db;
269 our $messaging_tn = "messaging";
270 our @messaging_col_names = ("id INTEGER", "subject TEXT", "message_from VARCHAR(255)", "message_to VARCHAR(255)",
271 "flag VARCHAR(255)", "direction VARCHAR(255)", "delivery_time VARCHAR(255)", "message TEXT", "timestamp VARCHAR(14)" );
272 my $messaging_file_name;
274 # path to directory to store client install log files
275 our $client_fai_log_dir = "/var/log/fai";
277 # queue which stores taskes until one of the $max_children children are ready to process the task
278 #my @tasks = qw();
279 my @msgs_to_decrypt = qw();
280 my $max_children = 2;
283 # loop delay for job queue to look for opsi jobs
284 my $job_queue_opsi_delay = 10;
285 our $opsi_client;
286 our $opsi_url;
288 # Lifetime of logged in user information. If no update information comes after n seconds,
289 # the user is expeceted to be no longer logged in or the host is no longer running. Because
290 # of this, the user is deleted from login_users_db
291 our $logged_in_user_date_of_expiry = 600;
293 # List of month names, used in function daemon_log
294 my @monthnames = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
296 # List of accepted periodical xml tags related to cpan modul DateTime
297 our $check_periodic = {"months"=>'', "weeks"=>'', "days"=>'', "hours"=>'', "minutes"=>''};
300 %cfg_defaults = (
301 "General" => {
302 "log-file" => [\$log_file, "/var/log/gosa-si/gosa-si-server.log"],
303 "pid-file" => [\$pid_file, "/var/run/gosa-si/gosa-si-server.pid"],
304 },
305 "Server" => {
306 "ip" => [\$server_ip, "0.0.0.0"],
307 "port" => [\$server_port, "20081"],
308 "known-clients" => [\$known_clients_file_name, '/var/lib/gosa-si/clients.db' ],
309 "known-servers" => [\$known_server_file_name, '/var/lib/gosa-si/servers.db'],
310 "incoming" => [\$incoming_file_name, '/var/lib/gosa-si/incoming.db'],
311 "login-users" => [\$login_users_file_name, '/var/lib/gosa-si/users.db'],
312 "fai-server" => [\$fai_server_file_name, '/var/lib/gosa-si/fai_server.db'],
313 "fai-release" => [\$fai_release_file_name, '/var/lib/gosa-si/fai_release.db'],
314 "packages-list" => [\$packages_list_file_name, '/var/lib/gosa-si/packages.db'],
315 "messaging" => [\$messaging_file_name, '/var/lib/gosa-si/messaging.db'],
316 "foreign-clients" => [\$foreign_clients_file_name, '/var/lib/gosa-si/foreign_clients.db'],
317 "source-list" => [\$sources_list, '/etc/apt/sources.list'],
318 "repo-path" => [\$repo_path, '/srv/www/debian'],
319 "debian-arch" => [\$arch, 'i386'],
320 "ldap-uri" => [\$ldap_uri, ""],
321 "ldap-base" => [\$ldap_base, ""],
322 "ldap-admin-dn" => [\$ldap_admin_dn, ""],
323 "ldap-admin-password" => [\$ldap_admin_password, ""],
324 "ldap-version" => [\$ldap_version, 3],
325 "ldap-retry-sec" => [\$ldap_retry_sec, 10],
326 "gosa-unit-tag" => [\$gosa_unit_tag, ""],
327 "max-clients" => [\$max_clients, 10],
328 "wol-password" => [\$wake_on_lan_passwd, ""],
329 "mysql-username" => [\$mysql_username, "gosa_si"],
330 "mysql-password" => [\$mysql_password, ""],
331 "mysql-database" => [\$mysql_database, "gosa_si"],
332 "mysql-host" => [\$mysql_host, "127.0.0.1"],
333 },
334 "GOsaPackages" => {
335 "job-queue" => [\$job_queue_file_name, '/var/lib/gosa-si/jobs.db'],
336 "job-queue-loop-delay" => [\$job_queue_loop_delay, 3],
337 "messaging-db-loop-delay" => [\$messaging_db_loop_delay, 3],
338 "key" => [\$GosaPackages_key, "none"],
339 "new-systems-ou" => [\$new_systems_ou, 'ou=workstations,ou=systems'],
340 },
341 "ClientPackages" => {
342 "key" => [\$ClientPackages_key, "none"],
343 "user-date-of-expiry" => [\$logged_in_user_date_of_expiry, 600],
344 },
345 "ServerPackages"=> {
346 "enabled" => [\$serverPackages_enabled, "true"],
347 "address" => [\$foreign_server_string, ""],
348 "dns-lookup" => [\$dns_lookup, "true"],
349 "domain" => [\$server_domain, ""],
350 "key" => [\$ServerPackages_key, "none"],
351 "key-lifetime" => [\$foreign_servers_register_delay, 120],
352 "job-synchronization-enabled" => [\$job_synchronization, "true"],
353 "synchronization-loop" => [\$modified_jobs_loop_delay, 5],
354 },
355 "ArpHandler" => {
356 "enabled" => [\$arp_enabled, "false"],
357 "interface" => [\$arp_interface, "all"],
358 },
359 "Opsi" => {
360 "enabled" => [\$opsi_enabled, "false"],
361 "server" => [\$opsi_server, "localhost"],
362 "admin" => [\$opsi_admin, "opsi-admin"],
363 "password" => [\$opsi_password, "secret"],
364 },
366 );
368 #############################
369 #
370 # @brief Display error message and/or help text.
371 #
372 # In correspondence to previous GetOptions
373 #
374 # @param $text - string to print as error message
375 # @param $help - set true, if you want to show usage help
376 #
377 sub usage
378 {
379 my( $text, $help ) = @_;
381 $text = undef if( 'h' eq $text );
382 (defined $text) && print STDERR "\n$text\n";
384 if( (defined $help && $help)
385 || (!defined $help && !defined $text) )
386 {
387 print STDERR << "EOF";
389 usage: $0 [-hvf] [-c config] [-d number]
391 -h : this (help) message
392 -c <file> : config file (default: ${config})
393 -x <cfg> : dump configuration to stdout
394 ( 1 = current, 2 = default )
395 -f : foreground (don't fork)
396 -v : be verbose (multiple to increase verbosity)
397 'v': error logs
398 'vvv': warning plus error logs
399 'vvvvv': info plus warning logs
400 'vvvvvvv': debug plus info logs
401 -no-arp : starts gosa-si-server without connection to arp module
402 -d <int> : if verbose level is higher than 7x 'v' specified parts can be debugged
403 1 : report incoming messages
404 2 : report unencrypted outgoing messages
405 4 : report encrypting key for messages
406 8 : report decrypted incoming message and verification if the message complies gosa-si requirements
407 16 : message processing
408 32 : ldap connectivity
409 64 : database status and connectivity
410 128 : main process
411 256 : creation of packages_list_db
412 512 : ARP debug information
413 EOF
414 }
415 print( "\n" );
417 exit( -1 );
418 }
420 #############################
421 #
422 # @brief Manage gosa-si-client configuration.
423 #
424 # Will exit after successfull dump to stdout (type = 1 | 2)
425 #
426 # Dump type can be:
427 # 1: Current gosa-si-client configuration in config file (exit)
428 # 2: Default gosa-si-client configuration (exit)
429 # 3: Dump to logfile (no exit)
430 #
431 # @param int config type
432 #
433 sub dump_configuration {
435 my( $cfg_type ) = @_;
437 return if( ! defined $cfg_type );
439 if(1==$cfg_type ) {
440 print( "# Current gosa-si-server configuration\n" );
441 } elsif (2==$cfg_type) {
442 print( "# Default gosa-si-server configuration\n" );
443 } elsif (3==$cfg_type) {
444 daemon_log( "Dumping gosa-si-server configuration\n", 2 );
445 } else {
446 return;
447 }
449 foreach my $section (keys %cfg_defaults) {
450 if( 3 != $cfg_type ) {
451 print( "\n[${section}]\n" );
452 } else {
453 daemon_log( "\n [${section}]\n", 3 );
454 }
456 foreach my $param (sort( keys %{$cfg_defaults{ $section }})) {
457 my $pinfo = $cfg_defaults{ $section }{ $param };
458 my $value;
459 if (1==$cfg_type) {
460 if( defined( ${@$pinfo[ 0 ]} ) ) {
461 $value = ${@$pinfo[ 0 ]};
462 print( "$param=$value\n" );
463 } else {
464 print( "#${param}=\n" );
465 }
466 } elsif (2==$cfg_type) {
467 $value = @{$pinfo}[ 1 ];
468 if( defined( @$pinfo[ 1 ] ) ) {
469 $value = @{$pinfo}[ 1 ];
470 print( "$param=$value\n" );
471 } else {
472 print( "#${param}=\n" );
473 }
474 } elsif (3==$cfg_type) {
475 if( defined( ${@$pinfo[ 0 ]} ) ) {
476 $value = ${@$pinfo[ 0 ]};
477 daemon_log( " $param=$value\n", 3 )
478 }
479 }
480 }
481 }
484 # We just exit at stdout dump
485 if( 3 == $cfg_type ) {
486 daemon_log( "\n", 3 );
487 } else {
488 exit( 0 );
489 }
490 }
493 #=== FUNCTION ================================================================
494 # NAME: logging
495 # PARAMETERS: level - string - default 'info'
496 # msg - string -
497 # facility - string - default 'LOG_DAEMON'
498 # RETURNS: nothing
499 # DESCRIPTION: function for logging
500 #===============================================================================
501 sub daemon_log {
502 my( $msg, $level ) = @_;
503 if (not defined $msg) { return }
504 if (not defined $level) { $level = 1 }
505 my $to_be_logged = 0;
507 # Write log line if line level is lower than verbosity given in commandline
508 if ($level <= $verbose)
509 {
510 $to_be_logged = 1 ;
511 }
513 # Write if debug flag is set and bitstring matches
514 if ($debug_parts > 0)
515 {
516 my $tmp_level = ($level - 10 >= 0) ? $level - 10 : 0 ;
517 my $tmp_level_bitstring = unpack("B32", pack("N", $tmp_level));
518 if (int($debug_parts_bitstring & $tmp_level_bitstring))
519 {
520 $to_be_logged = 1;
521 }
522 }
524 if ($to_be_logged)
525 {
526 if(defined $log_file){
527 my $open_log_fh = sysopen(LOG_HANDLE, $log_file, O_WRONLY | O_CREAT | O_APPEND , 0440);
528 if(not $open_log_fh) {
529 print STDERR "cannot open $log_file: $!";
530 return;
531 }
532 # Check owner and group of log_file and update settings if necessary
533 my ($dev, $ino, $mode, $nlink, $uid, $gid, $rdev, $size, $atime, $mtime, $ctime, $blksize, $blocks) = stat($log_file);
534 if((not $uid eq $root_uid) || (not $gid eq $adm_gid)) {
535 chown($root_uid, $adm_gid, $log_file);
536 }
538 # Prepare time string for log message
539 my ($seconds,$minutes,$hours,$monthday,$month,$year,$weekday,$yearday,$sommertime) = localtime(time);
540 $hours = $hours < 10 ? $hours = "0".$hours : $hours;
541 $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes;
542 $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds;
543 $month = $monthnames[$month];
544 $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday;
545 $year+=1900;
547 # Microseconds since epoch
548 my $microSeconds = sprintf("%.2f", &Time::HiRes::clock_gettime());
549 $microSeconds =~ s/^\d*(.\d\d)$/$1/;
551 # Build log message and write it to log file and commandline
552 chomp($msg);
553 my $log_msg = "$month $monthday $hours:$minutes:$seconds$microSeconds $0 $msg\n";
554 flock(LOG_HANDLE, LOCK_EX);
555 seek(LOG_HANDLE, 0, 2);
556 print LOG_HANDLE $log_msg;
557 flock(LOG_HANDLE, LOCK_UN);
558 if( $foreground )
559 {
560 print STDERR $log_msg;
561 }
562 close( LOG_HANDLE );
563 }
564 }
565 }
568 #=== FUNCTION ================================================================
569 # NAME: check_cmdline_param
570 # PARAMETERS: nothing
571 # RETURNS: nothing
572 # DESCRIPTION: validates commandline parameter
573 #===============================================================================
574 sub check_cmdline_param () {
575 my $err_counter = 0;
577 # Check configuration file
578 if(not defined($cfg_file)) {
579 $cfg_file = "/etc/gosa-si/server.conf";
580 if(! -r $cfg_file) {
581 print STDERR "Please specify a config file.\n";
582 $err_counter++;
583 }
584 }
586 # Prepare identification which gosa-si parts should be debugged and which not
587 if (defined $debug_parts)
588 {
589 if ($debug_parts =~ /^\d+$/)
590 {
591 $debug_parts_bitstring = unpack("B32", pack("N", $debug_parts));
592 }
593 else
594 {
595 print STDERR "Value '$debug_parts' invalid for option d (number expected)\n";
596 $err_counter++;
597 }
598 }
600 # Exit if an error occour
601 if( $err_counter > 0 ) { &usage( "", 1 ); }
602 }
605 #=== FUNCTION ================================================================
606 # NAME: check_pid
607 # PARAMETERS: nothing
608 # RETURNS: nothing
609 # DESCRIPTION: handels pid processing
610 #===============================================================================
611 sub check_pid {
612 $pid = -1;
613 # Check, if we are already running
614 if( open( my $LOCK_FILE, "<", "$pid_file" ) ) {
615 $pid = <$LOCK_FILE>;
616 if( defined $pid ) {
617 chomp( $pid );
618 if( -f "/proc/$pid/stat" ) {
619 my($stat) = `cat /proc/$pid/stat` =~ m/$pid \((.+)\).*/;
620 if( $stat ) {
621 print STDERR "\nERROR: Already running!\n";
622 close( $LOCK_FILE );
623 exit -1;
624 }
625 }
626 }
627 close( $LOCK_FILE );
628 unlink( $pid_file );
629 }
631 # create a syslog msg if it is not to possible to open PID file
632 if (not sysopen(my $LOCK_FILE, $pid_file, O_WRONLY|O_CREAT|O_EXCL, 0644)) {
633 my($msg) = "Couldn't obtain lockfile '$pid_file' ";
634 if (open(my $LOCK_FILE, '<', $pid_file)
635 && ($pid = <$LOCK_FILE>))
636 {
637 chomp($pid);
638 $msg .= "(PID $pid)\n";
639 } else {
640 $msg .= "(unable to read PID)\n";
641 }
642 if( ! ($foreground) ) {
643 openlog( $0, "cons,pid", "daemon" );
644 syslog( "warning", $msg );
645 closelog();
646 }
647 else {
648 print( STDERR " $msg " );
649 }
650 exit( -1 );
651 }
652 }
654 #=== FUNCTION ================================================================
655 # NAME: import_modules
656 # PARAMETERS: module_path - string - abs. path to the directory the modules
657 # are stored
658 # RETURNS: nothing
659 # DESCRIPTION: each file in module_path which ends with '.pm' and activation
660 # state is on is imported by "require 'file';"
661 #===============================================================================
662 sub import_modules {
663 if (not -e $modules_path) {
664 daemon_log("0 ERROR: cannot find directory or directory is not readable: $modules_path", 1);
665 }
667 opendir (DIR, $modules_path) or die "ERROR while loading modules from directory $modules_path : $!\n";
669 while (defined (my $file = readdir (DIR))) {
670 if (not $file =~ /(\S*?).pm$/) {
671 next;
672 }
673 my $mod_name = $1;
675 # ArpHandler switch
676 if( $file =~ /ArpHandler.pm/ ) {
677 if( $arp_enabled eq "false" ) { next; }
678 }
680 # ServerPackages switch
681 if ($file eq "ServerPackages.pm" && $serverPackages_enabled eq "false")
682 {
683 $dns_lookup = "false";
684 next;
685 }
687 eval { require $file; };
688 if ($@) {
689 daemon_log("0 ERROR: gosa-si-server could not load module $file", 1);
690 daemon_log("$@", 1);
691 exit;
692 } else {
693 my $info = eval($mod_name.'::get_module_info()');
694 # Only load module if get_module_info() returns a non-null object
695 if( $info ) {
696 my ($input_address, $input_key, $event_hash) = @{$info};
697 $known_modules->{$mod_name} = $info;
698 daemon_log("0 INFO: module $mod_name loaded", 5);
699 }
700 }
701 }
702 close (DIR);
703 }
705 #=== FUNCTION ================================================================
706 # NAME: password_check
707 # PARAMETERS: nothing
708 # RETURNS: nothing
709 # DESCRIPTION: escalates an critical error if two modules exist which are avaialable by
710 # the same password
711 #===============================================================================
712 sub password_check {
713 my $passwd_hash = {};
714 while (my ($mod_name, $mod_info) = each %$known_modules) {
715 my $mod_passwd = @$mod_info[1];
716 if (not defined $mod_passwd) { next; }
717 if (not exists $passwd_hash->{$mod_passwd}) {
718 $passwd_hash->{$mod_passwd} = $mod_name;
720 # escalates critical error
721 } else {
722 &daemon_log("0 ERROR: two loaded modules do have the same password. Please modify the 'key'-parameter in config file");
723 &daemon_log("0 ERROR: module='$mod_name' and module='".$passwd_hash->{$mod_passwd}."'");
724 exit( -1 );
725 }
726 }
728 }
731 #=== FUNCTION ================================================================
732 # NAME: sig_int_handler
733 # PARAMETERS: signal - string - signal came from system
734 # RETURNS: nothing
735 # DESCRIPTION: handle tasks to be done before signal becomes active
736 #===============================================================================
737 sub sig_int_handler {
738 my ($signal) = @_;
740 # if (defined($ldap_handle)) {
741 # $ldap_handle->disconnect;
742 # }
743 # TODO alle verbliebenden ldap verbindungen aus allen heaps beenden
745 daemon_log("shutting down gosa-si-server", 1);
747 $global_kernel->yield('shutdown');
749 # to be removed rather crude !!
750 #system("kill `ps -C gosa-si-server -o pid=`");
752 $pid->remove or warn "Could not remove $pid_file\n";
754 exit(0);
755 }
756 $SIG{INT} = \&sig_int_handler;
759 sub check_key_and_xml_validity {
760 my ($crypted_msg, $module_key, $session_id) = @_;
761 my $msg;
762 my $msg_hash;
763 my $error_string;
764 eval{
765 $msg = &decrypt_msg($crypted_msg, $module_key);
767 if ($msg =~ /<xml>/i){
768 $msg =~ s/\s+/ /g; # just for better daemon_log
769 daemon_log("$session_id DEBUG: decrypted_msg: \n$msg", 18);
770 $msg_hash = $xml->XMLin($msg, ForceArray=>1);
772 ##############
773 # check header
774 if( not exists $msg_hash->{'header'} ) { die "no header specified"; }
775 my $header_l = $msg_hash->{'header'};
776 if( (1 > @{$header_l}) || ( ( 'HASH' eq ref @{$header_l}[0]) && (1 > keys %{@{$header_l}[0]}) ) ) { die 'empty header tag'; }
777 if( 1 < @{$header_l} ) { die 'more than one header specified'; }
778 my $header = @{$header_l}[0];
779 if( 0 == length $header) { die 'empty string in header tag'; }
781 ##############
782 # check source
783 if( not exists $msg_hash->{'source'} ) { die "no source specified"; }
784 my $source_l = $msg_hash->{'source'};
785 if( (1 > @{$source_l}) || ( ( 'HASH' eq ref @{$source_l}[0]) && (1 > keys %{@{$source_l}[0]}) ) ) { die 'empty source tag'; }
786 if( 1 < @{$source_l} ) { die 'more than one source specified'; }
787 my $source = @{$source_l}[0];
788 if( 0 == length $source) { die 'source error'; }
790 ##############
791 # check target
792 if( not exists $msg_hash->{'target'} ) { die "no target specified"; }
793 my $target_l = $msg_hash->{'target'};
794 if( (1 > @{$target_l}) || ( ('HASH' eq ref @{$target_l}[0]) && (1 > keys %{@{$target_l}[0]}) ) ) { die 'empty target tag'; }
795 }
796 };
797 if($@) {
798 daemon_log("$session_id ERROR: do not understand the message: $@", 1);
799 $msg = undef;
800 $msg_hash = undef;
801 }
803 return ($msg, $msg_hash);
804 }
807 sub check_outgoing_xml_validity {
808 my ($msg, $session_id) = @_;
810 my $msg_hash;
811 eval{
812 $msg_hash = $xml->XMLin($msg, ForceArray=>1);
814 ##############
815 # check header
816 my $header_l = $msg_hash->{'header'};
817 if( 1 != @{$header_l} ) {
818 die 'no or more than one headers specified';
819 }
820 my $header = @{$header_l}[0];
821 if( 0 == length $header) {
822 die 'header has length 0';
823 }
825 ##############
826 # check source
827 my $source_l = $msg_hash->{'source'};
828 if( 1 != @{$source_l} ) {
829 die 'no or more than 1 sources specified';
830 }
831 my $source = @{$source_l}[0];
832 if( 0 == length $source) {
833 die 'source has length 0';
834 }
836 # Check if source contains hostname instead of ip address
837 if($source =~ /^[a-z][\w\-\.]+:\d+$/i) {
838 my ($hostname,$port) = split(/:/, $source);
839 my $ip_address = inet_ntoa(scalar gethostbyname($hostname));
840 if(defined($ip_address) && $ip_address =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/ && $port =~ /^\d+$/) {
841 # Write ip address to $source variable
842 $source = "$ip_address:$port";
843 $msg_hash->{source}[0] = $source ;
844 $msg =~ s/<source>.*<\/source>/<source>$source<\/source>/;
845 }
846 }
847 unless( $source =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
848 $source =~ /^GOSA$/i) {
849 die "source '$source' is neither a complete ip-address with port nor 'GOSA'";
850 }
852 ##############
853 # check target
854 my $target_l = $msg_hash->{'target'};
855 if( 0 == @{$target_l} ) {
856 die "no targets specified";
857 }
858 foreach my $target (@$target_l) {
859 if( 0 == length $target) {
860 die "target has length 0";
861 }
862 unless( $target =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
863 $target =~ /^GOSA$/i ||
864 $target =~ /^\*$/ ||
865 $target =~ /KNOWN_SERVER/i ||
866 $target =~ /JOBDB/i ||
867 $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 ){
868 die "target '$target' is not a complete ip-address with port or a valid target name or a mac-address";
869 }
870 }
871 };
872 if($@) {
873 daemon_log("$session_id ERROR: outgoing msg is not gosa-si envelope conform: $@", 1);
874 daemon_log("$@ ".(defined($msg) && length($msg)>0)?$msg:"Empty Message", 1);
875 $msg_hash = undef;
876 }
878 return ($msg, $msg_hash);
879 }
882 sub input_from_known_server {
883 my ($input, $remote_ip, $session_id) = @_ ;
884 my ($msg, $msg_hash, $module);
886 my $sql_statement= "SELECT * FROM known_server";
887 my $query_res = $known_server_db->select_dbentry( $sql_statement );
889 while( my ($hit_num, $hit) = each %{ $query_res } ) {
890 my $host_name = $hit->{hostname};
891 if( not $host_name =~ "^$remote_ip") {
892 next;
893 }
894 my $host_key = $hit->{hostkey};
895 daemon_log("$session_id DEBUG: input_from_known_server: host_name: $host_name", 14);
896 daemon_log("$session_id DEBUG: input_from_known_server: host_key: $host_key", 14);
898 # check if module can open msg envelope with module key
899 my ($tmp_msg, $tmp_msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
900 if( (!$tmp_msg) || (!$tmp_msg_hash) ) {
901 daemon_log("$session_id DEBUG: input_from_known_server: deciphering raise error", 14);
902 daemon_log("$@", 14);
903 next;
904 }
905 else {
906 $msg = $tmp_msg;
907 $msg_hash = $tmp_msg_hash;
908 $module = "ServerPackages";
909 daemon_log("$session_id DEBUG: check_key_and_xml_validity... ok", 14);
910 last;
911 }
912 }
914 if( (!$msg) || (!$msg_hash) || (!$module) ) {
915 daemon_log("$session_id DEBUG: Incoming message is not from a known server", 14);
916 }
918 return ($msg, $msg_hash, $module);
919 }
922 sub input_from_known_client {
923 my ($input, $remote_ip, $session_id) = @_ ;
924 my ($msg, $msg_hash, $module);
926 my $sql_statement= "SELECT * FROM known_clients";
927 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
928 while( my ($hit_num, $hit) = each %{ $query_res } ) {
929 my $host_name = $hit->{hostname};
930 if( not $host_name =~ /^$remote_ip/) {
931 next;
932 }
933 my $host_key = $hit->{hostkey};
934 &daemon_log("$session_id DEBUG: input_from_known_client: host_name: $host_name", 14);
935 &daemon_log("$session_id DEBUG: input_from_known_client: host_key: $host_key", 14);
937 # check if module can open msg envelope with module key
938 ($msg, $msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
940 if( (!$msg) || (!$msg_hash) ) {
941 &daemon_log("$session_id DEGUG: input_from_known_client: deciphering raise error", 14);
942 next;
943 }
944 else {
945 $module = "ClientPackages";
946 daemon_log("$session_id DEBUG: check_key_and_xml_validity... ok", 14);
947 last;
948 }
949 }
951 if( (!$msg) || (!$msg_hash) || (!$module) ) {
952 &daemon_log("$session_id DEBUG: Incoming message is not from a known client", 14);
953 }
955 return ($msg, $msg_hash, $module);
956 }
959 sub input_from_unknown_host {
960 no strict "refs";
961 my ($input, $session_id) = @_ ;
962 my ($msg, $msg_hash, $module);
963 my $error_string;
965 my %act_modules = %$known_modules;
967 while( my ($mod, $info) = each(%act_modules)) {
969 # check a key exists for this module
970 my $module_key = ${$mod."_key"};
971 if( not defined $module_key ) {
972 if( $mod eq 'ArpHandler' ) {
973 next;
974 }
975 daemon_log("$session_id ERROR: no key specified in config file for $mod", 1);
976 next;
977 }
978 daemon_log("$session_id DEBUG: $mod: $module_key", 14);
980 # check if module can open msg envelope with module key
981 ($msg, $msg_hash) = &check_key_and_xml_validity($input, $module_key, $session_id);
982 if( (not defined $msg) || (not defined $msg_hash) ) {
983 next;
984 } else {
985 $module = $mod;
986 daemon_log("$session_id DEBUG: check_key_and_xml_validity... ok", 18);
987 last;
988 }
989 }
991 if( (!$msg) || (!$msg_hash) || (!$module)) {
992 daemon_log("$session_id DEBUG: Incoming message is not from an unknown host", 14);
993 }
995 return ($msg, $msg_hash, $module);
996 }
999 sub create_ciphering {
1000 my ($passwd) = @_;
1001 if((!defined($passwd)) || length($passwd)==0) {
1002 $passwd = "";
1003 }
1004 $passwd = substr(md5_hex("$passwd") x 32, 0, 32);
1005 my $iv = substr(md5_hex('GONICUS GmbH'),0, 16);
1006 my $my_cipher = Crypt::Rijndael->new($passwd , Crypt::Rijndael::MODE_CBC());
1007 $my_cipher->set_iv($iv);
1008 return $my_cipher;
1009 }
1012 sub encrypt_msg {
1013 my ($msg, $key) = @_;
1014 my $my_cipher = &create_ciphering($key);
1015 my $len;
1016 {
1017 use bytes;
1018 $len= 16-length($msg)%16;
1019 }
1020 $msg = "\0"x($len).$msg;
1021 $msg = $my_cipher->encrypt($msg);
1022 chomp($msg = &encode_base64($msg));
1023 # there are no newlines allowed inside msg
1024 $msg=~ s/\n//g;
1025 return $msg;
1026 }
1029 sub decrypt_msg {
1031 my ($msg, $key) = @_ ;
1032 $msg = &decode_base64($msg);
1033 my $my_cipher = &create_ciphering($key);
1034 $msg = $my_cipher->decrypt($msg);
1035 $msg =~ s/\0*//g;
1036 return $msg;
1037 }
1040 sub get_encrypt_key {
1041 my ($target) = @_ ;
1042 my $encrypt_key;
1043 my $error = 0;
1045 # target can be in known_server
1046 if( not defined $encrypt_key ) {
1047 my $sql_statement= "SELECT * FROM known_server WHERE hostname='$target'";
1048 my $query_res = $known_server_db->select_dbentry( $sql_statement );
1049 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1050 my $host_name = $hit->{hostname};
1051 if( $host_name ne $target ) {
1052 next;
1053 }
1054 $encrypt_key = $hit->{hostkey};
1055 last;
1056 }
1057 }
1059 # target can be in known_client
1060 if( not defined $encrypt_key ) {
1061 my $sql_statement= "SELECT * FROM known_clients WHERE hostname='$target'";
1062 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1063 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1064 my $host_name = $hit->{hostname};
1065 if( $host_name ne $target ) {
1066 next;
1067 }
1068 $encrypt_key = $hit->{hostkey};
1069 last;
1070 }
1071 }
1073 return $encrypt_key;
1074 }
1077 #=== FUNCTION ================================================================
1078 # NAME: open_socket
1079 # PARAMETERS: PeerAddr string something like 192.168.1.1 or 192.168.1.1:10000
1080 # [PeerPort] string necessary if port not appended by PeerAddr
1081 # RETURNS: socket IO::Socket::INET
1082 # DESCRIPTION: open a socket to PeerAddr
1083 #===============================================================================
1084 sub open_socket {
1085 my ($PeerAddr, $PeerPort) = @_ ;
1086 if(defined($PeerPort)){
1087 $PeerAddr = $PeerAddr.":".$PeerPort;
1088 }
1089 my $socket;
1090 $socket = new IO::Socket::INET(PeerAddr => $PeerAddr,
1091 Porto => "tcp",
1092 Type => SOCK_STREAM,
1093 Timeout => 5,
1094 );
1095 if(not defined $socket) {
1096 return;
1097 }
1098 # &daemon_log("DEBUG: open_socket: $PeerAddr", 7);
1099 return $socket;
1100 }
1103 sub send_msg_to_target {
1104 my ($msg, $address, $encrypt_key, $msg_header, $session_id) = @_ ;
1105 my $error = 0;
1106 my $header;
1107 my $timestamp = &get_time();
1108 my $new_status;
1109 my $act_status;
1110 my ($sql_statement, $res);
1112 if( $msg_header ) {
1113 $header = "'$msg_header'-";
1114 } else {
1115 $header = "";
1116 }
1118 # Memorize own source address
1119 my $own_source_address = &get_local_ip_for_remote_ip(sprintf("%s", $address =~ /^([0-9\.]*?):.*$/));
1120 $own_source_address .= ":".$server_port;
1122 # Patch 0.0.0.0 source to real address
1123 $msg =~ s/<source>(0\.0\.0\.0):(\d*?)<\/source>/<source>$own_source_address<\/source>/s;
1124 # Patch GOSA source to real address and add forward_to_gosa tag
1125 $msg =~ s/<source>GOSA<\/source>/<source>$own_source_address<\/source> <forward_to_gosa>$own_source_address,$session_id<\/forward_to_gosa>/ ;
1127 # encrypt xml msg
1128 my $crypted_msg = &encrypt_msg($msg, $encrypt_key);
1130 # opensocket
1131 my $socket = &open_socket($address);
1132 if( !$socket ) {
1133 daemon_log("$session_id ERROR: Cannot open socket to host '$address'. Message processing aborted!", 1);
1134 $error++;
1135 }
1137 if( $error == 0 ) {
1138 # send xml msg
1139 print $socket $crypted_msg.";$own_source_address\n";
1140 daemon_log("$session_id INFO: send ".$header."msg to $address", 5);
1141 daemon_log("$session_id DEBUG: message:\n$msg", 12);
1143 }
1145 # close socket in any case
1146 if( $socket ) {
1147 close $socket;
1148 }
1150 if( $error > 0 ) { $new_status = "down"; }
1151 else { $new_status = $msg_header; }
1154 # known_clients
1155 $sql_statement = "SELECT * FROM $known_clients_tn WHERE hostname='$address'";
1156 $res = $known_clients_db->select_dbentry($sql_statement);
1157 if( keys(%$res) == 1) {
1158 $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
1159 if ($act_status eq "down" && $new_status eq "down") {
1160 $sql_statement = "DELETE FROM known_clients WHERE hostname='$address'";
1161 $res = $known_clients_db->del_dbentry($sql_statement);
1162 daemon_log("$session_id WARNING: failed 2x to send msg to host '$address', delete host from known_clients", 3);
1163 } else {
1164 $sql_statement = "UPDATE known_clients SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
1165 $res = $known_clients_db->update_dbentry($sql_statement);
1166 if($new_status eq "down"){
1167 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
1168 } else {
1169 daemon_log("$session_id DEBUG: set '$address' from status '$act_status' to '$new_status'", 138);
1170 }
1171 }
1172 }
1174 # known_server
1175 $sql_statement = "SELECT * FROM $known_server_tn WHERE hostname='$address'";
1176 $res = $known_server_db->select_dbentry($sql_statement);
1177 if( keys(%$res) == 1) {
1178 $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
1179 if ($act_status eq "down" && $new_status eq "down") {
1180 $sql_statement = "DELETE FROM known_server WHERE hostname='$address'";
1181 $res = $known_server_db->del_dbentry($sql_statement);
1182 daemon_log("$session_id WARNING: failed 2x to send a message to host '$address', delete host from known_server", 3);
1183 }
1184 else {
1185 $sql_statement = "UPDATE known_server SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
1186 $res = $known_server_db->update_dbentry($sql_statement);
1187 if($new_status eq "down"){
1188 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
1189 } else {
1190 daemon_log("$session_id DEBUG: set '$address' from status '$act_status' to '$new_status'", 138);
1191 }
1192 }
1193 }
1194 return $error;
1195 }
1198 sub update_jobdb_status_for_send_msgs {
1199 my ($session_id, $answer, $error) = @_;
1200 if( $answer =~ /<jobdb_id>(\d+)<\/jobdb_id>/ ) {
1201 &daemon_log("$session_id DEBUG: try to update job status", 138);
1202 my $jobdb_id = $1;
1204 $answer =~ /<header>(.*)<\/header>/;
1205 my $job_header = $1;
1207 $answer =~ /<target>(.*)<\/target>/;
1208 my $job_target = $1;
1210 # Sending msg failed
1211 if( $error ) {
1213 # Set jobs to done, jobs do not need to deliver their message in any case
1214 if (($job_header eq "trigger_action_localboot")
1215 ||($job_header eq "trigger_action_lock")
1216 ||($job_header eq "trigger_action_halt")
1217 ) {
1218 my $sql_statement = "UPDATE $job_queue_tn SET status='done' WHERE id=$jobdb_id";
1219 my $res = $job_db->update_dbentry($sql_statement);
1221 # Reactivate jobs, jobs need to deliver their message
1222 } elsif (($job_header eq "trigger_action_activate")
1223 ||($job_header eq "trigger_action_update")
1224 ||($job_header eq "trigger_action_reinstall")
1225 ||($job_header eq "trigger_activate_new")
1226 ) {
1227 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) {
1228 &reactivate_job_with_delay($session_id, $job_target, $job_header, 30 );
1229 } else {
1230 # If we don't have the mac adress at this time, we use the plainname
1231 my $plainname_result = $job_db->select_dbentry("SELECT plainname from jobs where id=$jobdb_id");
1232 my $plainname = $job_target;
1233 if ((keys(%$plainname_result) > 0) ) {
1234 $plainname = $plainname_result->{1}->{$job_target};
1235 }
1236 &reactivate_job_with_delay($session_id, $plainname, $job_header, 30 );
1237 }
1238 # For all other messages
1239 } else {
1240 my $sql_statement = "UPDATE $job_queue_tn ".
1241 "SET status='error', result='can not deliver msg, please consult log file' ".
1242 "WHERE id=$jobdb_id";
1243 my $res = $job_db->update_dbentry($sql_statement);
1244 }
1246 # Sending msg was successful
1247 } else {
1248 # Set jobs localboot, lock, activate, halt, reboot and wake to done
1249 # jobs reinstall, update, inst_update do themself setting to done
1250 if (($job_header eq "trigger_action_localboot")
1251 ||($job_header eq "trigger_action_lock")
1252 ||($job_header eq "trigger_action_activate")
1253 ||($job_header eq "trigger_action_halt")
1254 ||($job_header eq "trigger_action_reboot")
1255 ||($job_header eq "trigger_action_wake")
1256 ||($job_header eq "trigger_wake")
1257 ) {
1259 my $sql_statement = "UPDATE $job_queue_tn ".
1260 "SET status='done' ".
1261 "WHERE id=$jobdb_id AND status='processed'";
1262 my $res = $job_db->update_dbentry($sql_statement);
1263 } else {
1264 &daemon_log("$session_id DEBUG: sending message succeed but cannot update job status.", 138);
1265 }
1266 }
1267 } else {
1268 &daemon_log("$session_id DEBUG: cannot update job status, msg has no jobdb_id-tag.", 138);
1269 }
1270 }
1272 sub reactivate_job_with_delay {
1273 my ($session_id, $target, $header, $delay) = @_ ;
1274 # 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
1276 if (not defined $delay) { $delay = 30 } ;
1277 my $delay_timestamp = &calc_timestamp(&get_time(), "plus", $delay);
1279 my $sql = "UPDATE $job_queue_tn Set timestamp='$delay_timestamp', status='waiting' WHERE (macaddress LIKE '$target' OR plainname LIKE '$target') AND headertag='$header'";
1280 my $res = $job_db->update_dbentry($sql);
1281 daemon_log("$session_id INFO: '$header'-job will be reactivated at '$delay_timestamp' ".
1282 "cause client '$target' is currently not available", 5);
1283 return;
1284 }
1287 sub sig_handler {
1288 my ($kernel, $signal) = @_[KERNEL, ARG0] ;
1289 daemon_log("0 INFO got signal '$signal'", 1);
1290 $kernel->sig_handled();
1291 return;
1292 }
1295 sub msg_to_decrypt {
1296 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
1297 my $session_id = $session->ID;
1298 my ($msg, $msg_hash, $module);
1299 my $error = 0;
1301 # fetch new msg out of @msgs_to_decrypt
1302 my $tmp_next_msg = shift @msgs_to_decrypt;
1303 my ($next_msg, $msg_source) = split(/;/, $tmp_next_msg);
1305 # msg is from a new client or gosa
1306 ($msg, $msg_hash, $module) = &input_from_unknown_host($next_msg, $session_id);
1308 # msg is from a gosa-si-server
1309 if(((!$msg) || (!$msg_hash) || (!$module)) && ($serverPackages_enabled eq "true")){
1310 if (not defined $msg_source)
1311 {
1312 # Only needed, to be compatible with older gosa-si-server versions
1313 ($msg, $msg_hash, $module) = &input_from_known_server($next_msg, $heap->{'remote_ip'}, $session_id);
1314 }
1315 else
1316 {
1317 ($msg, $msg_hash, $module) = &input_from_known_server($next_msg, $msg_source, $session_id);
1318 }
1319 }
1320 # msg is from a gosa-si-client
1321 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1322 if (not defined $msg_source)
1323 {
1324 # Only needed, to be compatible with older gosa-si-server versions
1325 ($msg, $msg_hash, $module) = &input_from_known_client($next_msg, $heap->{'remote_ip'}, $session_id);
1326 }
1327 else
1328 {
1329 ($msg, $msg_hash, $module) = &input_from_known_client($next_msg, $msg_source, $session_id);
1330 }
1331 }
1332 # an error occurred
1333 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1334 # If an incoming msg could not be decrypted (maybe a wrong key), decide if msg comes from a client
1335 # or a server. In case of a client, send a ping. If the client could not understand a msg from its
1336 # server the client cause a re-registering process. In case of a server, decrease update_time in kown_server_db
1337 # and trigger a re-registering process for servers
1338 if (defined $msg_source && $msg_source =~ /:$server_port$/ && $serverPackages_enabled eq "true")
1339 {
1340 daemon_log("$session_id WARNING: Cannot understand incoming msg from server '$msg_source'. Cause re-registration process for servers.", 3);
1341 my $update_statement = "UPDATE $known_server_tn SET update_time='19700101000000' WHERE hostname='$msg_source'";
1342 daemon_log("$session_id DEBUG: $update_statement", 7);
1343 my $upadte_res = $known_server_db->exec_statement($update_statement);
1344 $kernel->yield("register_at_foreign_servers");
1345 }
1346 elsif ((defined $msg_source) && (not $msg_source =~ /:$server_port$/))
1347 {
1348 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);
1349 #my $remote_ip = $heap->{'remote_ip'};
1350 #my $remote_port = $heap->{'remote_port'};
1351 my $ping_msg = "<xml> <header>gosa_ping</header> <source>$server_address</source><target>$msg_source</target></xml>";
1352 my ($test_error, $test_error_string) = &send_msg_to_target($ping_msg, "$msg_source", "dummy-key", "gosa_ping", $session_id);
1353 daemon_log("$session_id WARNING: Sending msg to cause re-registering: $ping_msg", 3);
1354 }
1355 else
1356 {
1357 my $foreign_host = defined $msg_source ? $msg_source : $heap->{'remote_ip'};
1358 daemon_log("$session_id ERROR: Incoming message from host '$foreign_host' cannot be understood. Processing aborted!", 1);
1359 daemon_log("$session_id DEBUG: Aborted message: $tmp_next_msg", 11);
1360 }
1362 $error++
1363 }
1366 my $header;
1367 my $target;
1368 my $source;
1369 my $done = 0;
1370 my $sql;
1371 my $res;
1373 # check whether this message should be processed here
1374 if ($error == 0) {
1375 $header = @{$msg_hash->{'header'}}[0];
1376 $target = @{$msg_hash->{'target'}}[0];
1377 $source = @{$msg_hash->{'source'}}[0];
1378 my $not_found_in_known_clients_db = 0;
1379 my $not_found_in_known_server_db = 0;
1380 my $not_found_in_foreign_clients_db = 0;
1381 my $local_address;
1382 my $local_mac;
1383 my ($target_ip, $target_port) = split(':', $target);
1385 # Determine the local ip address if target is an ip address
1386 if ($target =~ /^\d+\.\d+\.\d+\.\d+:\d+$/) {
1387 $local_address = &get_local_ip_for_remote_ip($target_ip).":$server_port";
1388 } else {
1389 $local_address = $server_address;
1390 }
1392 # Determine the local mac address if target is a mac address
1393 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) {
1394 my $loc_ip = &get_local_ip_for_remote_ip($heap->{'remote_ip'});
1395 my $network_interface= &get_interface_for_ip($loc_ip);
1396 $local_mac = &get_mac_for_interface($network_interface);
1397 } else {
1398 $local_mac = $server_mac_address;
1399 }
1401 # target and source is equal to GOSA -> process here
1402 if (not $done) {
1403 if ($target eq "GOSA" && $source eq "GOSA") {
1404 $done = 1;
1405 &daemon_log("$session_id DEBUG: target and source is 'GOSA' -> process '$header' here", 11);
1406 }
1407 }
1409 # target is own address without forward_to_gosa-tag -> process here
1410 if (not $done) {
1411 #if ((($target eq $local_address) || ($target eq $local_mac) ) && (not exists $msg_hash->{'forward_to_gosa'})) {
1412 if (($target eq $local_address) && (not exists $msg_hash->{'forward_to_gosa'})) {
1413 $done = 1;
1414 if ($source eq "GOSA") {
1415 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1416 }
1417 &daemon_log("$session_id DEBUG: target is own address without forward_to_gosa-tag -> process '$header' here", 11);
1418 }
1419 }
1421 # target is own address with forward_to_gosa-tag not pointing to myself -> process here
1422 if (not $done) {
1423 my $forward_to_gosa = @{$msg_hash->{'forward_to_gosa'}}[0];
1424 my $gosa_at;
1425 my $gosa_session_id;
1426 if (($target eq $local_address) && (defined $forward_to_gosa)){
1427 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1428 if ($gosa_at ne $local_address) {
1429 $done = 1;
1430 &daemon_log("$session_id DEBUG: target is own address with forward_to_gosa-tag not pointing to myself -> process '$header' here", 11);
1431 }
1432 }
1433 }
1435 # Target is a client address and there is a processing function within a plugin -> process loaclly
1436 if (not $done)
1437 {
1438 # Check if target is a client address
1439 $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1440 $res = $known_clients_db->select_dbentry($sql);
1441 if ((keys(%$res) > 0) )
1442 {
1443 my $hostname = $res->{1}->{'hostname'};
1444 my $reduced_header = $header;
1445 $reduced_header =~ s/gosa_//;
1446 # Check if there is a processing function within a plugin
1447 if (exists $known_functions->{$reduced_header})
1448 {
1449 $msg =~ s/<target>\S*<\/target>/<target>$hostname<\/target>/;
1450 $done = 1;
1451 &daemon_log("$session_id DEBUG: Target is client address with processing function within a plugin -> process '$header' here", 11);
1452 }
1453 }
1454 }
1456 # If header has a 'job_' prefix, do always process message locally
1457 # which means put it into job queue
1458 if ((not $done) && ($header =~ /job_/))
1459 {
1460 $done = 1;
1461 &daemon_log("$session_id DEBUG: Header has a 'job_' prefix. Put it into job queue. -> process '$header' here", 11);
1462 }
1464 # if message should be processed here -> add message to incoming_db
1465 if ($done) {
1466 # if a 'job_' or a 'gosa_' message comes from a foreign server, fake module from
1467 # ServerPackages to GosaPackages so gosa-si-server knows how to process this kind of messages
1468 if ($header =~ /^gosa_/ || $header =~ /^job_/) {
1469 $module = "GosaPackages";
1470 }
1472 my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1473 primkey=>[],
1474 headertag=>$header,
1475 targettag=>$target,
1476 xmlmessage=>&encode_base64($msg),
1477 timestamp=>&get_time,
1478 module=>$module,
1479 sessionid=>$session_id,
1480 } );
1481 $kernel->yield('watch_for_next_tasks');
1482 }
1484 # target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa
1485 if (not $done) {
1486 my $forward_to_gosa = @{$msg_hash->{'forward_to_gosa'}}[0];
1487 my $gosa_at;
1488 my $gosa_session_id;
1489 if (($target eq $local_address) && (defined $forward_to_gosa)){
1490 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1491 if ($gosa_at eq $local_address) {
1492 my $session_reference = $kernel->ID_id_to_session($gosa_session_id);
1493 if( defined $session_reference ) {
1494 $heap = $session_reference->get_heap();
1495 }
1496 if(exists $heap->{'client'}) {
1497 $msg = &encrypt_msg($msg, $GosaPackages_key);
1498 $heap->{'client'}->put($msg);
1499 &daemon_log("$session_id DEBUG: incoming '$header' message forwarded to GOsa", 11);
1500 }
1501 $done = 1;
1502 &daemon_log("$session_id DEBUG: target is own address with forward_to_gosa-tag pointing at myself -> forward '$header' to gosa", 11);
1503 }
1504 }
1506 }
1508 # target is a client address in known_clients -> forward to client
1509 if (not $done) {
1510 $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1511 $res = $known_clients_db->select_dbentry($sql);
1512 if (keys(%$res) > 0)
1513 {
1514 $done = 1;
1515 &daemon_log("$session_id DEBUG: target is a client address in known_clients -> forward '$header' to client", 11);
1516 my $hostkey = $res->{1}->{'hostkey'};
1517 my $hostname = $res->{1}->{'hostname'};
1518 $msg =~ s/<target>\S*<\/target>/<target>$hostname<\/target>/;
1519 $msg =~ s/<header>gosa_/<header>/;
1520 my $error= &send_msg_to_target($msg, $hostname, $hostkey, $header, $session_id);
1521 if ($error) {
1522 &daemon_log("$session_id ERROR: Some problems occurred while trying to send msg to client '$hostname': $msg", 1);
1523 }
1524 }
1525 else
1526 {
1527 $not_found_in_known_clients_db = 1;
1528 }
1529 }
1531 # target is a client address in foreign_clients -> forward to registration server
1532 if (not $done) {
1533 $sql = "SELECT * FROM $foreign_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1534 $res = $foreign_clients_db->select_dbentry($sql);
1535 if (keys(%$res) > 0) {
1536 my $hostname = $res->{1}->{'hostname'};
1537 my ($host_ip, $host_port) = split(/:/, $hostname);
1538 my $local_address = &get_local_ip_for_remote_ip($host_ip).":$server_port";
1539 my $regserver = $res->{1}->{'regserver'};
1540 my $sql = "SELECT * FROM $known_server_tn WHERE hostname='$regserver'";
1541 my $res = $known_server_db->select_dbentry($sql);
1542 if (keys(%$res) > 0) {
1543 my $regserver_key = $res->{1}->{'hostkey'};
1544 $msg =~ s/<source>GOSA<\/source>/<source>$local_address<\/source>/;
1545 $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1546 if ($source eq "GOSA") {
1547 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1548 }
1549 my $error= &send_msg_to_target($msg, $regserver, $regserver_key, $header, $session_id);
1550 if ($error) {
1551 &daemon_log("$session_id ERROR: some problems occurred while trying to send msg to registration server: $msg", 1);
1552 }
1553 }
1554 $done = 1;
1555 &daemon_log("$session_id DEBUG: target is a client address in foreign_clients -> forward '$header' to registration server", 11);
1556 } else {
1557 $not_found_in_foreign_clients_db = 1;
1558 }
1559 }
1561 # target is a server address -> forward to server
1562 if (not $done) {
1563 $sql = "SELECT * FROM $known_server_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1564 $res = $known_server_db->select_dbentry($sql);
1565 if (keys(%$res) > 0) {
1566 my $hostkey = $res->{1}->{'hostkey'};
1568 if ($source eq "GOSA") {
1569 $msg =~ s/<source>GOSA<\/source>/<source>$local_address<\/source>/;
1570 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1572 }
1574 &send_msg_to_target($msg, $target, $hostkey, $header, $session_id);
1575 $done = 1;
1576 &daemon_log("$session_id DEBUG: target is a server address -> forward '$header' to server", 11);
1577 } else {
1578 $not_found_in_known_server_db = 1;
1579 }
1580 }
1583 # target is not in foreign_clients_db, known_server_db or known_clients_db, maybe it is a complete new one -> process here
1584 if ( $not_found_in_foreign_clients_db
1585 && $not_found_in_known_server_db
1586 && $not_found_in_known_clients_db) {
1587 &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);
1588 if ($header =~ /^gosa_/ || $header =~ /^job_/) {
1589 $module = "GosaPackages";
1590 }
1591 my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1592 primkey=>[],
1593 headertag=>$header,
1594 targettag=>$target,
1595 xmlmessage=>&encode_base64($msg),
1596 timestamp=>&get_time,
1597 module=>$module,
1598 sessionid=>$session_id,
1599 } );
1600 $done = 1;
1601 $kernel->yield('watch_for_next_tasks');
1602 }
1605 if (not $done) {
1606 daemon_log("$session_id ERROR: do not know what to do with this message: $msg", 1);
1607 if ($source eq "GOSA") {
1608 my %data = ('error_msg' => &encode_base64($msg), 'error_string' => "Do not know what to do with this message!");
1609 my $error_msg = &build_msg("error", $local_address, "GOSA", \%data );
1611 my $session_reference = $kernel->ID_id_to_session($session_id);
1612 if( defined $session_reference ) {
1613 $heap = $session_reference->get_heap();
1614 }
1615 if(exists $heap->{'client'}) {
1616 $error_msg = &encrypt_msg($error_msg, $GosaPackages_key);
1617 $heap->{'client'}->put($error_msg);
1618 }
1619 }
1620 }
1622 }
1624 return;
1625 }
1628 sub next_task {
1629 my ($session, $heap, $task) = @_[SESSION, HEAP, ARG0, ARG1];
1630 my $running_task = POE::Wheel::Run->new(
1631 Program => sub { process_task($session, $heap, $task) },
1632 StdioFilter => POE::Filter::Reference->new(),
1633 StdoutEvent => "task_result",
1634 StderrEvent => "task_debug",
1635 CloseEvent => "task_done",
1636 );
1637 $heap->{task}->{ $running_task->ID } = $running_task;
1638 }
1640 sub handle_task_result {
1641 my ($kernel, $heap, $result) = @_[KERNEL, HEAP, ARG0];
1642 my $client_answer = $result->{'answer'};
1643 if( $client_answer =~ s/session_id=(\d+)$// ) {
1644 my $session_id = $1;
1645 if( defined $session_id ) {
1646 my $session_reference = $kernel->ID_id_to_session($session_id);
1647 if( defined $session_reference ) {
1648 $heap = $session_reference->get_heap();
1649 }
1650 }
1652 if(exists $heap->{'client'}) {
1653 $heap->{'client'}->put($client_answer);
1654 }
1655 }
1656 $kernel->sig(CHLD => "child_reap");
1657 }
1659 sub handle_task_debug {
1660 my $result = $_[ARG0];
1661 print STDERR "$result\n";
1662 }
1664 sub handle_task_done {
1665 my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1666 delete $heap->{task}->{$task_id};
1667 if (exists $heap->{ldap_handle}->{$task_id}) {
1668 &release_ldap_handle($heap->{ldap_handle}->{$task_id});
1669 }
1670 }
1672 sub process_task {
1673 no strict "refs";
1674 #CHECK: Not @_[...]?
1675 my ($session, $heap, $task) = @_;
1676 my $error = 0;
1677 my $answer_l;
1678 my ($answer_header, @answer_target_l, $answer_source);
1679 my $client_answer = "";
1681 # prepare all variables needed to process message
1682 #my $msg = $task->{'xmlmessage'};
1683 my $msg = &decode_base64($task->{'xmlmessage'});
1684 my $incoming_id = $task->{'id'};
1685 my $module = $task->{'module'};
1686 my $header = $task->{'headertag'};
1687 my $session_id = $task->{'sessionid'};
1688 my $msg_hash;
1689 eval {
1690 $msg_hash = $xml->XMLin($msg, ForceArray=>1);
1691 };
1692 daemon_log("ERROR: XML failure '$@'") if ($@);
1693 my $source = @{$msg_hash->{'source'}}[0];
1695 # set timestamp of incoming client uptodate, so client will not
1696 # be deleted from known_clients because of expiration
1697 my $cur_time = &get_time();
1698 my $sql = "UPDATE $known_clients_tn SET timestamp='$cur_time' WHERE hostname='$source'";
1699 my $res = $known_clients_db->exec_statement($sql);
1701 ######################
1702 # process incoming msg
1703 if( $error == 0) {
1704 daemon_log("$session_id INFO: Incoming msg (session_id=$session_id) with header '".@{$msg_hash->{'header'}}[0]."'", 5);
1705 daemon_log("$session_id DEBUG: Processing module ".$module, 26);
1706 $answer_l = &{ $module."::process_incoming_msg" }($msg, $msg_hash, $session_id);
1708 if ( 0 < @{$answer_l} ) {
1709 my $answer_str = join("\n", @{$answer_l});
1710 my @headers;
1711 while ($answer_str =~ /<header>(\w+)<\/header>/g) {
1712 push(@headers, $1);
1713 }
1714 daemon_log("$session_id DEBUG: got answer message(s) with header: '".join("', '", @headers)."'", 26);
1715 } else {
1716 daemon_log("$session_id DEBUG: $module: got no answer from module!" ,26);
1717 }
1719 }
1720 if( !$answer_l ) { $error++ };
1722 ########
1723 # answer
1724 if( $error == 0 ) {
1726 foreach my $answer ( @{$answer_l} ) {
1727 # check outgoing msg to xml validity
1728 my ($answer, $answer_hash) = &check_outgoing_xml_validity($answer, $session_id);
1729 if( not defined $answer_hash ) { next; }
1731 $answer_header = @{$answer_hash->{'header'}}[0];
1732 @answer_target_l = @{$answer_hash->{'target'}};
1733 $answer_source = @{$answer_hash->{'source'}}[0];
1735 # deliver msg to all targets
1736 foreach my $answer_target ( @answer_target_l ) {
1738 # targets of msg are all gosa-si-clients in known_clients_db
1739 if( $answer_target eq "*" ) {
1740 # answer is for all clients
1741 my $sql_statement= "SELECT * FROM known_clients";
1742 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1743 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1744 my $host_name = $hit->{hostname};
1745 my $host_key = $hit->{hostkey};
1746 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1747 &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1748 }
1749 }
1751 # targets of msg are all gosa-si-server in known_server_db
1752 elsif( $answer_target eq "KNOWN_SERVER" ) {
1753 # answer is for all server in known_server
1754 my $sql_statement= "SELECT * FROM $known_server_tn";
1755 my $query_res = $known_server_db->select_dbentry( $sql_statement );
1756 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1757 my $host_name = $hit->{hostname};
1758 my $host_key = $hit->{hostkey};
1759 $answer =~ s/<target>\S+<\/target>/<target>$host_name<\/target>/g;
1760 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1761 &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1762 }
1763 }
1765 # target of msg is GOsa
1766 elsif( $answer_target eq "GOSA" ) {
1767 my $session_id = ($1) if $answer =~ /<session_id>(\d+?)<\/session_id>/;
1768 my $add_on = "";
1769 if( defined $session_id ) {
1770 $add_on = ".session_id=$session_id";
1771 }
1772 my $header = ($1) if $answer =~ /<header>(\S*)<\/header>/;
1773 daemon_log("$session_id INFO: send ".$header." message to GOsa", 5);
1774 daemon_log("$session_id DEBUG: message:\n$answer", 12);
1775 # answer is for GOSA and has to returned to connected client
1776 my $gosa_answer = &encrypt_msg($answer, $GosaPackages_key);
1777 $client_answer = $gosa_answer.$add_on;
1778 }
1780 # target of msg is job queue at this host
1781 elsif( $answer_target eq "JOBDB") {
1782 $answer =~ /<header>(\S+)<\/header>/;
1783 my $header;
1784 if( defined $1 ) { $header = $1; }
1785 my $error = &send_msg_to_target($answer, $server_address, $GosaPackages_key, $header, $session_id);
1786 &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1787 }
1789 # Target of msg is a mac address
1790 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 ) {
1791 daemon_log("$session_id DEBUG: target is mac address '$answer_target', looking for host in known_clients and foreign_clients", 138);
1793 # Looking for macaddress in known_clients
1794 my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$answer_target'";
1795 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1796 my $found_ip_flag = 0;
1797 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1798 my $host_name = $hit->{hostname};
1799 my $host_key = $hit->{hostkey};
1800 $answer =~ s/$answer_target/$host_name/g;
1801 daemon_log("$session_id INFO: found host '$host_name', associated to '$answer_target'", 5);
1802 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1803 &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1804 $found_ip_flag++ ;
1805 }
1807 # Looking for macaddress in foreign_clients
1808 if ($found_ip_flag == 0) {
1809 my $sql = "SELECT * FROM $foreign_clients_tn WHERE macaddress LIKE '$answer_target'";
1810 my $res = $foreign_clients_db->select_dbentry($sql);
1811 while( my ($hit_num, $hit) = each %{ $res } ) {
1812 my $host_name = $hit->{hostname};
1813 my $reg_server = $hit->{regserver};
1814 daemon_log("$session_id INFO: found host '$host_name' with mac '$answer_target', registered at '$reg_server'", 5);
1816 # Fetch key for reg_server
1817 my $reg_server_key;
1818 my $sql = "SELECT * FROM $known_server_tn WHERE hostname='$reg_server'";
1819 my $res = $known_server_db->select_dbentry($sql);
1820 if (exists $res->{1}) {
1821 $reg_server_key = $res->{1}->{'hostkey'};
1822 } else {
1823 daemon_log("$session_id ERROR: cannot find hostkey for '$host_name' in '$known_server_tn'", 1);
1824 daemon_log("$session_id ERROR: unable to forward answer to correct registration server, processing is aborted!", 1);
1825 $reg_server_key = undef;
1826 }
1828 # Send answer to server where client is registered
1829 if (defined $reg_server_key) {
1830 $answer =~ s/$answer_target/$host_name/g;
1831 my $error = &send_msg_to_target($answer, $reg_server, $reg_server_key, $answer_header, $session_id);
1832 &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1833 $found_ip_flag++ ;
1834 }
1835 }
1836 }
1838 # No mac to ip matching found
1839 if( $found_ip_flag == 0) {
1840 daemon_log("$session_id WARNING: no host found in known_clients or foreign_clients with mac address '$answer_target'", 3);
1841 &reactivate_job_with_delay($session_id, $answer_target, $answer_header, 30);
1842 }
1844 # Answer is for one specific host
1845 } else {
1846 # get encrypt_key
1847 my $encrypt_key = &get_encrypt_key($answer_target);
1848 if( not defined $encrypt_key ) {
1849 # unknown target
1850 daemon_log("$session_id WARNING: unknown target '$answer_target'", 3);
1851 next;
1852 }
1853 my $error = &send_msg_to_target($answer, $answer_target, $encrypt_key, $answer_header,$session_id);
1854 &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1855 }
1856 }
1857 }
1858 }
1860 my $filter = POE::Filter::Reference->new();
1861 my %result = (
1862 status => "seems ok to me",
1863 answer => $client_answer,
1864 );
1866 my $output = $filter->put( [ \%result ] );
1867 print @$output;
1870 }
1872 sub session_start {
1873 my ($kernel) = $_[KERNEL];
1874 $global_kernel = $kernel;
1875 $kernel->yield('register_at_foreign_servers');
1876 $kernel->yield('create_fai_server_db', $fai_server_tn );
1877 $kernel->yield('create_fai_release_db', $fai_release_tn );
1878 $kernel->sig(USR1 => "sig_handler");
1879 $kernel->sig(USR2 => "recreate_packages_db");
1880 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1881 $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay);
1882 $kernel->delay_set('watch_for_modified_jobs', $modified_jobs_loop_delay);
1883 $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1884 $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1885 $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1886 $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1888 # Start opsi check
1889 if ($opsi_enabled eq "true") {
1890 $kernel->delay_set('watch_for_opsi_jobs', $job_queue_opsi_delay);
1891 }
1893 }
1896 sub watch_for_done_jobs {
1897 my $kernel = $_[KERNEL];
1899 my $sql_statement = "SELECT * FROM ".$job_queue_tn." WHERE ((status='done') AND (modified='0'))";
1900 my $res = $job_db->select_dbentry( $sql_statement );
1902 while( my ($number, $hit) = each %{$res} )
1903 {
1904 # Non periodical jobs can be deleted.
1905 if ($hit->{periodic} eq "none")
1906 {
1907 my $jobdb_id = $hit->{id};
1908 my $sql_statement = "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1909 my $res = $job_db->del_dbentry($sql_statement);
1910 }
1912 # Periodical jobs should not be deleted but reactivated with new timestamp instead.
1913 else
1914 {
1915 my ($p_time, $periodic) = split("_", $hit->{periodic});
1916 my $reactivated_ts = $hit->{timestamp};
1917 my $act_ts = int(&get_time());
1918 while ($act_ts > int($reactivated_ts)) # Redo calculation to avoid multiple jobs in the past
1919 {
1920 $reactivated_ts = &calc_timestamp($reactivated_ts, "plus", $p_time, $periodic);
1921 }
1922 my $sql = "UPDATE $job_queue_tn SET status='waiting', timestamp='$reactivated_ts' WHERE id='".$hit->{id}."'";
1923 my $res = $job_db->exec_statement($sql);
1924 &daemon_log("J INFO: Update periodical job '".$hit->{headertag}."' for client '".$hit->{targettag}."'. New execution time '$reactivated_ts'.", 5);
1925 }
1926 }
1928 $kernel->delay_set('watch_for_done_jobs',$job_queue_loop_delay);
1929 }
1932 sub watch_for_opsi_jobs {
1933 my ($kernel) = $_[KERNEL];
1934 # 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
1935 # opsi install job is to parse the xml message. There is still the correct header.
1936 my $sql_statement = "SELECT * FROM ".$job_queue_tn." WHERE ((xmlmessage LIKE '%opsi_install_client</header>%') AND (status='processing') AND (siserver='localhost'))";
1937 my $res = $job_db->select_dbentry( $sql_statement );
1939 # Ask OPSI for an update of the running jobs
1940 while (my ($id, $hit) = each %$res ) {
1941 # Determine current parameters of the job
1942 my $hostId = $hit->{'plainname'};
1943 my $macaddress = $hit->{'macaddress'};
1944 my $progress = $hit->{'progress'};
1946 my $result= {};
1948 # For hosts, only return the products that are or get installed
1949 my $callobj;
1950 $callobj = {
1951 method => 'getProductStates_hash',
1952 params => [ $hostId ],
1953 id => 1,
1954 };
1956 my $hres = $opsi_client->call($opsi_url, $callobj);
1957 # TODO : move all opsi relevant statments to opsi plugin
1958 # The following 3 lines must be tested befor they can replace the rubbish above and below !!!
1959 #my ($res, $err) = &opsi_com::_getProductStates_hash(hostId=>$hostId)
1960 #if (not $err) {
1961 # my $htmp = $res->{$hostId};
1962 #
1963 if (not &check_opsi_res($hres)) {
1964 my $htmp= $hres->result->{$hostId};
1965 # Check state != not_installed or action == setup -> load and add
1966 my $products= 0;
1967 my $installed= 0;
1968 my $installing = 0;
1969 my $error= 0;
1970 my @installed_list;
1971 my @error_list;
1972 my $act_status = "none";
1973 foreach my $product (@{$htmp}){
1975 if ($product->{'installationStatus'} ne "not_installed" or
1976 $product->{'actionRequest'} eq "setup"){
1978 # Increase number of products for this host
1979 $products++;
1981 if ($product->{'installationStatus'} eq "failed"){
1982 $result->{$product->{'productId'}}= "error";
1983 unshift(@error_list, $product->{'productId'});
1984 $error++;
1985 }
1986 if ($product->{'installationStatus'} eq "installed" && $product->{'actionRequest'} eq "none"){
1987 $result->{$product->{'productId'}}= "installed";
1988 unshift(@installed_list, $product->{'productId'});
1989 $installed++;
1990 }
1991 if ($product->{'installationStatus'} eq "installing"){
1992 $result->{$product->{'productId'}}= "installing";
1993 $installing++;
1994 $act_status = "installing - ".$product->{'productId'};
1995 }
1996 }
1997 }
1999 # Estimate "rough" progress, avoid division by zero
2000 if ($products == 0) {
2001 $result->{'progress'}= 0;
2002 } else {
2003 $result->{'progress'}= int($installed * 100 / $products);
2004 }
2006 # Set updates in job queue
2007 if ((not $error) && (not $installing) && ($installed)) {
2008 $act_status = "installed - ".join(", ", @installed_list);
2009 }
2010 if ($error) {
2011 $act_status = "error - ".join(", ", @error_list);
2012 }
2013 if ($progress ne $result->{'progress'} ) {
2014 # Updating progress and result
2015 my $update_statement = "UPDATE $job_queue_tn SET modified='1', progress='".$result->{'progress'}."', result='$act_status' WHERE macaddress LIKE '$macaddress' AND siserver='localhost'";
2016 my $update_res = $job_db->update_dbentry($update_statement);
2017 }
2018 if ($progress eq 100) {
2019 # Updateing status
2020 my $done_statement = "UPDATE $job_queue_tn SET modified='1', ";
2021 if ($error) {
2022 $done_statement .= "status='error'";
2023 } else {
2024 $done_statement .= "status='done'";
2025 }
2026 $done_statement .= " WHERE macaddress LIKE '$macaddress' AND siserver='localhost'";
2027 my $done_res = $job_db->update_dbentry($done_statement);
2028 }
2031 }
2032 }
2034 $kernel->delay_set('watch_for_opsi_jobs', $job_queue_opsi_delay);
2035 }
2038 # If a job got an update or was modified anyway, send to all other si-server an update message of this jobs.
2039 sub watch_for_modified_jobs {
2040 my ($kernel,$heap) = @_[KERNEL, HEAP];
2042 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE (modified='1')";
2043 my $res = $job_db->select_dbentry( $sql_statement );
2045 # if db contains no jobs which should be update, do nothing
2046 if (keys %$res != 0) {
2048 if ($job_synchronization eq "true") {
2049 # make out of the db result a gosa-si message
2050 my $update_msg = &db_res2si_msg ($res, "foreign_job_updates", "KNOWN_SERVER", "MY_LOCAL_ADDRESS");
2052 # update all other SI-server
2053 &inform_all_other_si_server($update_msg);
2054 }
2056 # set jobs all jobs to modified = 0, wait until the next modification for updates of other si-server
2057 $sql_statement = "UPDATE $job_queue_tn SET modified='0' ";
2058 $res = $job_db->update_dbentry($sql_statement);
2059 }
2061 $kernel->delay_set('watch_for_modified_jobs', $modified_jobs_loop_delay);
2062 }
2065 sub watch_for_new_jobs {
2066 if($watch_for_new_jobs_in_progress == 0) {
2067 $watch_for_new_jobs_in_progress = 1;
2068 my ($kernel,$heap) = @_[KERNEL, HEAP];
2070 # check gosa job queue for jobs with executable timestamp
2071 my $timestamp = &get_time();
2072 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE siserver='localhost' AND status='waiting' AND (CAST(timestamp AS UNSIGNED)) < $timestamp ORDER BY timestamp";
2073 my $res = $job_db->exec_statement( $sql_statement );
2075 # Merge all new jobs that would do the same actions
2076 my @drops;
2077 my $hits;
2078 foreach my $hit (reverse @{$res} ) {
2079 my $macaddress= lc @{$hit}[8];
2080 my $headertag= @{$hit}[5];
2081 if(
2082 defined($hits->{$macaddress}) &&
2083 defined($hits->{$macaddress}->{$headertag}) &&
2084 defined($hits->{$macaddress}->{$headertag}[0])
2085 ) {
2086 push @drops, "DELETE FROM $job_queue_tn WHERE id = $hits->{$macaddress}->{$headertag}[0]";
2087 }
2088 $hits->{$macaddress}->{$headertag}= $hit;
2089 }
2091 # Delete new jobs with a matching job in state 'processing'
2092 foreach my $macaddress (keys %{$hits}) {
2093 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
2094 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
2095 if(defined($jobdb_id)) {
2096 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND headertag='$jobdb_headertag' AND status='processing'";
2097 my $res = $job_db->exec_statement( $sql_statement );
2098 foreach my $hit (@{$res}) {
2099 push @drops, "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
2100 }
2101 } else {
2102 daemon_log("J ERROR: Job without id exists for macaddress $macaddress!", 1);
2103 }
2104 }
2105 }
2107 # Commit deletion
2108 $job_db->exec_statementlist(\@drops);
2110 # Look for new jobs that could be executed
2111 foreach my $macaddress (keys %{$hits}) {
2113 # Look if there is an executing job
2114 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='processing'";
2115 my $res = $job_db->exec_statement( $sql_statement );
2117 # Skip new jobs for host if there is a processing job
2118 if(defined($res) and defined @{$res}[0]) {
2119 # Prevent race condition if there is a trigger_activate job waiting and a goto-activation job processing
2120 my $row = @{$res}[0] if (ref $res eq 'ARRAY');
2121 if(@{$row}[5] eq 'trigger_action_reinstall') {
2122 my $sql_statement_2 = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='waiting' AND headertag = 'trigger_activate_new'";
2123 my $res_2 = $job_db->exec_statement( $sql_statement_2 );
2124 if(defined($res_2) and defined @{$res_2}[0]) {
2125 # Set status from goto-activation to 'waiting' and update timestamp
2126 $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'");
2127 }
2128 }
2129 next;
2130 }
2132 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
2133 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
2134 if(defined($jobdb_id)) {
2135 my $job_msg = @{$hits->{$macaddress}->{$jobdb_headertag}}[7];
2137 daemon_log("J DEBUG: its time to execute $job_msg", 7);
2138 my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$macaddress'";
2139 my $res_hash = $known_clients_db->select_dbentry( $sql_statement );
2141 # expect macaddress is unique!!!!!!
2142 my $target = $res_hash->{1}->{hostname};
2144 # change header
2145 $job_msg =~ s/<header>job_/<header>gosa_/;
2147 # add sqlite_id
2148 $job_msg =~ s/<\/xml>$/<jobdb_id>$jobdb_id<\/jobdb_id><\/xml>/;
2150 $job_msg =~ /<header>(\S+)<\/header>/;
2151 my $header = $1 ;
2152 my $func_error = &send_msg_to_target($job_msg, $server_address, $GosaPackages_key, $header, "J");
2154 # update status in job queue to ...
2155 # ... 'processing', for jobs: 'reinstall', 'update', activate_new
2156 if (($header =~ /gosa_trigger_action_reinstall/)
2157 || ($header =~ /gosa_trigger_activate_new/)
2158 || ($header =~ /gosa_trigger_action_update/)) {
2159 my $sql_statement = "UPDATE $job_queue_tn SET status='processing' WHERE id=$jobdb_id";
2160 my $dbres = $job_db->update_dbentry($sql_statement);
2161 }
2163 # ... 'done', for all other jobs, they are no longer needed in the jobqueue
2164 else {
2165 my $sql_statement = "UPDATE $job_queue_tn SET status='done' WHERE id=$jobdb_id";
2166 my $dbres = $job_db->exec_statement($sql_statement);
2167 }
2170 # We don't want parallel processing
2171 last;
2172 }
2173 }
2174 }
2176 $watch_for_new_jobs_in_progress = 0;
2177 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
2178 }
2179 }
2182 sub watch_for_new_messages {
2183 my ($kernel,$heap) = @_[KERNEL, HEAP];
2184 my @coll_user_msg; # collection list of outgoing messages
2186 # check messaging_db for new incoming messages with executable timestamp
2187 my $timestamp = &get_time();
2188 my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( (CAST(timestamp AS UNSIGNED))<$timestamp AND flag='n' AND direction='in' )";
2189 my $res = $messaging_db->exec_statement( $sql_statement );
2190 foreach my $hit (@{$res}) {
2192 # create outgoing messages
2193 my $message_to = @{$hit}[3];
2194 # translate message_to to plain login name
2195 my @message_to_l = split(/,/, $message_to);
2196 my %receiver_h;
2197 foreach my $receiver (@message_to_l) {
2198 if ($receiver =~ /^u_([\s\S]*)$/) {
2199 $receiver_h{$1} = 0;
2200 } elsif ($receiver =~ /^g_([\s\S]*)$/) {
2201 my $group_name = $1;
2202 # fetch all group members from ldap and add them to receiver hash
2203 my $ldap_handle = &get_ldap_handle();
2204 if (defined $ldap_handle) {
2205 my $mesg = $ldap_handle->search(
2206 base => $ldap_base,
2207 scope => 'sub',
2208 attrs => ['memberUid'],
2209 filter => "cn=$group_name",
2210 );
2211 if ($mesg->count) {
2212 my @entries = $mesg->entries;
2213 foreach my $entry (@entries) {
2214 my @receivers= $entry->get_value("memberUid");
2215 foreach my $receiver (@receivers) {
2216 $receiver_h{$receiver} = 0;
2217 }
2218 }
2219 }
2220 # translating errors ?
2221 if ($mesg->code) {
2222 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: $mesg->error", 1);
2223 }
2224 &release_ldap_handle($ldap_handle);
2225 # ldap handle error ?
2226 } else {
2227 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: no ldap handle available", 1);
2228 }
2229 } else {
2230 my $sbjct = &encode_base64(@{$hit}[1]);
2231 my $msg = &encode_base64(@{$hit}[7]);
2232 &daemon_log("M WARNING: unknown receiver '$receiver' for a user-message '$sbjct - $msg'", 3);
2233 }
2234 }
2235 my @receiver_l = keys(%receiver_h);
2237 my $message_id = @{$hit}[0];
2239 #add each outgoing msg to messaging_db
2240 my $receiver;
2241 foreach $receiver (@receiver_l) {
2242 my $sql_statement = "INSERT INTO $messaging_tn (id, subject, message_from, message_to, flag, direction, delivery_time, message, timestamp) ".
2243 "VALUES ('".
2244 $message_id."', '". # id
2245 @{$hit}[1]."', '". # subject
2246 @{$hit}[2]."', '". # message_from
2247 $receiver."', '". # message_to
2248 "none"."', '". # flag
2249 "out"."', '". # direction
2250 @{$hit}[6]."', '". # delivery_time
2251 @{$hit}[7]."', '". # message
2252 $timestamp."'". # timestamp
2253 ")";
2254 &daemon_log("M DEBUG: $sql_statement", 1);
2255 my $res = $messaging_db->exec_statement($sql_statement);
2256 &daemon_log("M INFO: message '".@{$hit}[0]."' is prepared for delivery to receiver '$receiver'", 5);
2257 }
2259 # set incoming message to flag d=deliverd
2260 $sql_statement = "UPDATE $messaging_tn SET flag='p' WHERE id='$message_id'";
2261 &daemon_log("M DEBUG: $sql_statement", 7);
2262 $res = $messaging_db->update_dbentry($sql_statement);
2263 &daemon_log("M INFO: message '$message_id' is set to flag 'p' (processed)", 5);
2264 }
2266 $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
2267 return;
2268 }
2270 sub watch_for_delivery_messages {
2271 my ($kernel, $heap) = @_[KERNEL, HEAP];
2273 # select outgoing messages
2274 my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( flag='p' AND direction='out' )";
2275 my $res = $messaging_db->exec_statement( $sql_statement );
2277 # build out msg for each usr
2278 foreach my $hit (@{$res}) {
2279 my $receiver = @{$hit}[3];
2280 my $msg_id = @{$hit}[0];
2281 my $subject = @{$hit}[1];
2282 my $message = @{$hit}[7];
2284 # resolve usr -> host where usr is logged in
2285 my $sql = "SELECT * FROM $login_users_tn WHERE (user='$receiver')";
2286 my $res = $login_users_db->exec_statement($sql);
2288 # receiver is logged in nowhere
2289 if (not ref(@$res[0]) eq "ARRAY") { next; }
2291 # receiver ist logged in at a client registered at local server
2292 my $send_succeed = 0;
2293 foreach my $hit (@$res) {
2294 my $receiver_host = @$hit[0];
2295 my $delivered2host = 0;
2296 &daemon_log("M DEBUG: user '$receiver' is logged in at host '$receiver_host'", 7);
2298 # Looking for host in know_clients_db
2299 my $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$receiver_host')";
2300 my $res = $known_clients_db->exec_statement($sql);
2302 # Host is known in known_clients_db
2303 if (ref(@$res[0]) eq "ARRAY") {
2304 my $receiver_key = @{@{$res}[0]}[2];
2305 my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
2306 my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data );
2307 my $error = &send_msg_to_target($out_msg, $receiver_host, $receiver_key, "usr_msg", 0);
2308 if ($error == 0 ) {
2309 $send_succeed++ ;
2310 $delivered2host++ ;
2311 &daemon_log("M DEBUG: send message for user '$receiver' to host '$receiver_host'", 7);
2312 } else {
2313 &daemon_log("M DEBUG: cannot send message for user '$receiver' to host '$receiver_host'", 7);
2314 }
2315 }
2317 # Message already send, do not need to do anything more, otherwise ...
2318 if ($delivered2host) { next;}
2320 # ...looking for host in foreign_clients_db
2321 $sql = "SELECT * FROM $foreign_clients_tn WHERE (hostname='$receiver_host')";
2322 $res = $foreign_clients_db->exec_statement($sql);
2324 # Host is known in foreign_clients_db
2325 if (ref(@$res[0]) eq "ARRAY") {
2326 my $registration_server = @{@{$res}[0]}[2];
2328 # Fetch encryption key for registration server
2329 my $sql = "SELECT * FROM $known_server_tn WHERE (hostname='$registration_server')";
2330 my $res = $known_server_db->exec_statement($sql);
2331 if (ref(@$res[0]) eq "ARRAY") {
2332 my $registration_server_key = @{@{$res}[0]}[3];
2333 my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
2334 my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data );
2335 my $error = &send_msg_to_target($out_msg, $registration_server, $registration_server_key, "usr_msg", 0);
2336 if ($error == 0 ) {
2337 $send_succeed++ ;
2338 $delivered2host++ ;
2339 &daemon_log("M DEBUG: send message for user '$receiver' to server '$registration_server'", 7);
2340 } else {
2341 &daemon_log("M ERROR: cannot send message for user '$receiver' to server '$registration_server'", 1);
2342 }
2344 } else {
2345 &daemon_log("M ERROR: host '$receiver_host' is reported to be ".
2346 "registrated at server '$registration_server', ".
2347 "but no data available in known_server_db ", 1);
2348 }
2349 }
2351 if (not $delivered2host) {
2352 &daemon_log("M ERROR: unable to send user message to host '$receiver_host'", 1);
2353 }
2354 }
2356 if ($send_succeed) {
2357 # set outgoing msg at db to deliverd
2358 my $sql = "UPDATE $messaging_tn SET flag='d' WHERE (id='$msg_id' AND direction='out' AND message_to='$receiver')";
2359 my $res = $messaging_db->exec_statement($sql);
2360 &daemon_log("M INFO: send message for user '$receiver' to logged in hosts", 5);
2361 } else {
2362 &daemon_log("M WARNING: failed to deliver message for user '$receiver'", 3);
2363 }
2364 }
2366 $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
2367 return;
2368 }
2371 sub watch_for_done_messages {
2372 my ($kernel,$heap) = @_[KERNEL, HEAP];
2374 my $sql = "SELECT * FROM $messaging_tn WHERE (flag='p' AND direction='in')";
2375 my $res = $messaging_db->exec_statement($sql);
2377 foreach my $hit (@{$res}) {
2378 my $msg_id = @{$hit}[0];
2380 my $sql = "SELECT * FROM $messaging_tn WHERE (id='$msg_id' AND direction='out' AND (NOT flag='s'))";
2381 my $res = $messaging_db->exec_statement($sql);
2383 # not all usr msgs have been seen till now
2384 if ( ref(@$res[0]) eq "ARRAY") { next; }
2386 $sql = "DELETE FROM $messaging_tn WHERE (id='$msg_id')";
2387 $res = $messaging_db->exec_statement($sql);
2389 }
2391 $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
2392 return;
2393 }
2396 sub watch_for_old_known_clients {
2397 my ($kernel,$heap) = @_[KERNEL, HEAP];
2399 my $sql_statement = "SELECT * FROM $known_clients_tn";
2400 my $res = $known_clients_db->select_dbentry( $sql_statement );
2402 my $cur_time = int(&get_time());
2404 while ( my ($hit_num, $hit) = each %$res) {
2405 my $expired_timestamp = int($hit->{'timestamp'});
2406 $expired_timestamp =~ /(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)/;
2407 my $dt = DateTime->new( year => $1,
2408 month => $2,
2409 day => $3,
2410 hour => $4,
2411 minute => $5,
2412 second => $6,
2413 );
2415 $dt->add( seconds => 2 * int($hit->{'keylifetime'}) );
2416 $expired_timestamp = $dt->ymd('').$dt->hms('');
2417 if ($cur_time > $expired_timestamp) {
2418 my $hostname = $hit->{'hostname'};
2419 my $del_sql = "DELETE FROM $known_clients_tn WHERE hostname='$hostname'";
2420 my $del_res = $known_clients_db->exec_statement($del_sql);
2422 &main::daemon_log("0 INFO: timestamp '".$hit->{'timestamp'}."' of client '$hostname' is expired('$expired_timestamp'), client will be deleted from known_clients_db", 5);
2423 }
2425 }
2427 $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
2428 }
2431 sub watch_for_next_tasks {
2432 my ($kernel,$heap) = @_[KERNEL, HEAP];
2434 my $sql = "SELECT * FROM $incoming_tn";
2435 my $res = $incoming_db->select_dbentry($sql);
2437 while ( my ($hit_num, $hit) = each %$res) {
2438 my $headertag = $hit->{'headertag'};
2439 if ($headertag =~ /^answer_(\d+)/) {
2440 # do not start processing, this message is for a still running POE::Wheel
2441 next;
2442 }
2443 my $message_id = $hit->{'id'};
2444 my $session_id = $hit->{'sessionid'};
2445 &daemon_log("$session_id DEBUG: start processing for message with incoming id: '$message_id'", 11);
2447 $kernel->yield('next_task', $hit);
2449 my $sql = "DELETE FROM $incoming_tn WHERE id=$message_id";
2450 my $res = $incoming_db->exec_statement($sql);
2451 }
2452 }
2455 sub get_ldap_handle {
2456 my ($session_id) = @_;
2457 my $heap;
2459 if (not defined $session_id ) { $session_id = 0 };
2460 if ($session_id =~ /[^0-9]*/) { $session_id = 0 };
2462 my ($package, $file, $row, $subroutine, $hasArgs, $wantArray, $evalText, $isRequire) = caller(1);
2463 my $caller_text = "subroutine $subroutine";
2464 if ($subroutine eq "(eval)") {
2465 $caller_text = "eval block within file '$file' for '$evalText'";
2466 }
2467 daemon_log("$session_id DEBUG: new ldap handle for '$caller_text' required!", 42);
2469 get_handle:
2470 my $ldap_handle = Net::LDAP->new( $ldap_uri );
2471 if (not ref $ldap_handle) {
2472 daemon_log("$session_id ERROR: Connection to LDAP URI '$ldap_uri' failed! Retrying in $ldap_retry_sec seconds.", 1);
2473 sleep($ldap_retry_sec);
2474 goto get_handle;
2475 } else {
2476 daemon_log("$session_id DEBUG: Connection to LDAP URI '$ldap_uri' established.", 42);
2477 }
2479 $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);
2480 return $ldap_handle;
2481 }
2484 sub release_ldap_handle {
2485 my ($ldap_handle, $session_id) = @_ ;
2486 if (not defined $session_id ) { $session_id = 0 };
2488 if(ref $ldap_handle) {
2489 $ldap_handle->disconnect();
2490 }
2491 &main::daemon_log("$session_id DEBUG: Released a ldap handle!", 42);
2492 return;
2493 }
2496 sub change_fai_state {
2497 my ($st, $targets, $session_id) = @_;
2498 $session_id = 0 if not defined $session_id;
2499 # Set FAI state to localboot
2500 my %mapActions= (
2501 reboot => '',
2502 update => 'softupdate',
2503 localboot => 'localboot',
2504 reinstall => 'install',
2505 rescan => '',
2506 wake => '',
2507 memcheck => 'memcheck',
2508 sysinfo => 'sysinfo',
2509 install => 'install',
2510 );
2512 # Return if this is unknown
2513 if (!exists $mapActions{ $st }){
2514 daemon_log("$session_id ERROR: unknown action '$st', can not translate ot FAIstate", 1);
2515 return;
2516 }
2518 my $state= $mapActions{ $st };
2520 # Build search filter for hosts
2521 my $search= "(&(objectClass=GOhard)";
2522 foreach (@{$targets}){
2523 $search.= "(macAddress=$_)";
2524 }
2525 $search.= ")";
2527 # If there's any host inside of the search string, procress them
2528 if (!($search =~ /macAddress/)){
2529 daemon_log("$session_id ERROR: no macAddress found in filter statement for LDAP search: '$search'", 1);
2530 return;
2531 }
2533 my $ldap_handle = &get_ldap_handle($session_id);
2534 # Perform search for Unit Tag
2535 my $mesg = $ldap_handle->search(
2536 base => $ldap_base,
2537 scope => 'sub',
2538 attrs => ['dn', 'FAIstate', 'objectClass'],
2539 filter => "$search"
2540 );
2542 if ($mesg->count) {
2543 my @entries = $mesg->entries;
2544 if (0 == @entries) {
2545 daemon_log("$session_id ERROR: ldap search failed: ldap_base=$ldap_base, filter=$search", 1);
2546 }
2548 foreach my $entry (@entries) {
2549 # Only modify entry if it is not set to '$state'
2550 if ($entry->get_value("FAIstate") ne "$state"){
2551 daemon_log("$session_id INFO: Setting FAIstate to '$state' for ".$entry->dn, 5);
2552 my $result;
2553 my %tmp = map { $_ => 1 } $entry->get_value("objectClass");
2554 if (exists $tmp{'FAIobject'}){
2555 if ($state eq ''){
2556 $result= $ldap_handle->modify($entry->dn, changes => [ delete => [ FAIstate => [] ] ]);
2557 } else {
2558 $result= $ldap_handle->modify($entry->dn, changes => [ replace => [ FAIstate => $state ] ]);
2559 }
2560 } elsif ($state ne ''){
2561 $result= $ldap_handle->modify($entry->dn, changes => [ add => [ objectClass => 'FAIobject' ], add => [ FAIstate => $state ] ]);
2562 }
2564 # Errors?
2565 if ($result->code){
2566 daemon_log("$session_id Error: Setting FAIstate to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
2567 }
2568 } else {
2569 daemon_log("$session_id DEBUG FAIstate at host '".$entry->dn."' already at state '$st'", 42);
2570 }
2571 }
2572 } else {
2573 daemon_log("$session_id ERROR: LDAP search failed: ldap_base=$ldap_base, filter=$search", 1);
2574 }
2575 &release_ldap_handle($ldap_handle, $session_id);
2577 return;
2578 }
2581 sub change_goto_state {
2582 my ($st, $targets, $session_id) = @_;
2583 $session_id = 0 if not defined $session_id;
2585 # Switch on or off?
2586 my $state= $st eq 'active' ? 'active': 'locked';
2588 my $ldap_handle = &get_ldap_handle($session_id);
2589 if( defined($ldap_handle) ) {
2591 # Build search filter for hosts
2592 my $search= "(&(objectClass=GOhard)";
2593 foreach (@{$targets}){
2594 $search.= "(macAddress=$_)";
2595 }
2596 $search.= ")";
2598 # If there's any host inside of the search string, procress them
2599 if (!($search =~ /macAddress/)){
2600 &release_ldap_handle($ldap_handle);
2601 return;
2602 }
2604 # Perform search for Unit Tag
2605 my $mesg = $ldap_handle->search(
2606 base => $ldap_base,
2607 scope => 'sub',
2608 attrs => ['dn', 'gotoMode'],
2609 filter => "$search"
2610 );
2612 if ($mesg->count) {
2613 my @entries = $mesg->entries;
2614 foreach my $entry (@entries) {
2616 # Only modify entry if it is not set to '$state'
2617 if ($entry->get_value("gotoMode") ne $state){
2619 daemon_log("$session_id INFO: Setting gotoMode to '$state' for ".$entry->dn, 5);
2620 my $result;
2621 $result= $ldap_handle->modify($entry->dn, changes => [replace => [ gotoMode => $state ] ]);
2623 # Errors?
2624 if ($result->code){
2625 &daemon_log("$session_id Error: Setting gotoMode to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
2626 }
2628 }
2629 }
2630 } else {
2631 daemon_log("$session_id ERROR: LDAP search failed in function change_goto_state: ldap_base=$ldap_base, filter=$search", 1);
2632 }
2634 }
2635 &release_ldap_handle($ldap_handle, $session_id);
2636 return;
2637 }
2640 sub run_recreate_packages_db {
2641 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2642 my $session_id = $session->ID;
2643 &main::daemon_log("$session_id INFO: Recreating FAI Packages DB ('$fai_release_tn', '$fai_server_tn', '$packages_list_tn')", 5);
2644 $kernel->yield('create_fai_release_db', $fai_release_tn);
2645 $kernel->yield('create_fai_server_db', $fai_server_tn);
2646 return;
2647 }
2650 sub run_create_fai_server_db {
2651 my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
2652 my $session_id = $session->ID;
2653 my $task = POE::Wheel::Run->new(
2654 Program => sub { &create_fai_server_db($table_name,$kernel, undef, $session_id) },
2655 StdoutEvent => "session_run_result",
2656 StderrEvent => "session_run_debug",
2657 CloseEvent => "session_run_done",
2658 );
2660 $heap->{task}->{ $task->ID } = $task;
2661 return;
2662 }
2665 sub create_fai_server_db {
2666 my ($table_name, $kernel, $dont_create_packages_list, $session_id) = @_;
2667 my $result;
2669 if (not defined $session_id) { $session_id = 0; }
2670 my $ldap_handle = &get_ldap_handle($session_id);
2671 if(defined($ldap_handle)) {
2672 daemon_log("$session_id INFO: create_fai_server_db: start", 5);
2673 my $mesg= $ldap_handle->search(
2674 base => $ldap_base,
2675 scope => 'sub',
2676 attrs => ['FAIrepository', 'gosaUnitTag'],
2677 filter => "(&(FAIrepository=*)(objectClass=FAIrepositoryServer))",
2678 );
2679 if($mesg->{'resultCode'} == 0 &&
2680 $mesg->count != 0) {
2681 foreach my $entry (@{$mesg->{entries}}) {
2682 if($entry->exists('FAIrepository')) {
2683 # Add an entry for each Repository configured for server
2684 foreach my $repo(@{$entry->get_value('FAIrepository', asref => 1)}) {
2685 my($tmp_url,$tmp_server,$tmp_release,$tmp_sections) = split(/\|/, $repo);
2686 my $tmp_tag= $entry->get_value('gosaUnitTag') || "";
2687 $result= $fai_server_db->add_dbentry( {
2688 table => $table_name,
2689 primkey => ['server', 'fai_release', 'tag'],
2690 server => $tmp_url,
2691 fai_release => $tmp_release,
2692 sections => $tmp_sections,
2693 tag => (length($tmp_tag)>0)?$tmp_tag:"",
2694 } );
2695 }
2696 }
2697 }
2698 }
2699 daemon_log("$session_id INFO: create_fai_server_db: finished", 5);
2700 &release_ldap_handle($ldap_handle);
2702 # TODO: Find a way to post the 'create_packages_list_db' event
2703 if(not defined($dont_create_packages_list)) {
2704 &create_packages_list_db(undef, $session_id);
2705 }
2706 }
2708 return $result;
2709 }
2712 sub run_create_fai_release_db {
2713 my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
2714 my $session_id = $session->ID;
2715 my $task = POE::Wheel::Run->new(
2716 Program => sub { &create_fai_release_db($table_name, $session_id) },
2717 StdoutEvent => "session_run_result",
2718 StderrEvent => "session_run_debug",
2719 CloseEvent => "session_run_done",
2720 );
2722 $heap->{task}->{ $task->ID } = $task;
2723 return;
2724 }
2727 sub create_fai_release_db {
2728 my ($table_name, $session_id) = @_;
2729 my $result;
2731 # used for logging
2732 if (not defined $session_id) { $session_id = 0; }
2734 my $ldap_handle = &get_ldap_handle($session_id);
2735 if(defined($ldap_handle)) {
2736 daemon_log("$session_id INFO: create_fai_release_db: start",5);
2737 my $mesg= $ldap_handle->search(
2738 base => $ldap_base,
2739 scope => 'sub',
2740 attrs => [],
2741 filter => "(&(objectClass=organizationalUnit)(ou=fai))",
2742 );
2743 if(($mesg->code == 0) && ($mesg->count != 0))
2744 {
2745 daemon_log("$session_id DEBUG: create_fai_release_db: count " . $mesg->count,138);
2747 # Walk through all possible FAI container ou's
2748 my @sql_list;
2749 my $timestamp= &get_time();
2750 foreach my $ou (@{$mesg->{entries}}) {
2751 my $tmp_classes= resolve_fai_classes($ou->dn, $ldap_handle, $session_id);
2752 if(defined($tmp_classes) && ref($tmp_classes) eq 'HASH') {
2753 my @tmp_array=get_fai_release_entries($tmp_classes);
2754 if(@tmp_array) {
2755 foreach my $entry (@tmp_array) {
2756 if(defined($entry) && ref($entry) eq 'HASH') {
2757 my $sql=
2758 "INSERT INTO $table_name "
2759 ."(timestamp, fai_release, class, type, state) VALUES ("
2760 .$timestamp.","
2761 ."'".$entry->{'release'}."',"
2762 ."'".$entry->{'class'}."',"
2763 ."'".$entry->{'type'}."',"
2764 ."'".$entry->{'state'}."')";
2765 push @sql_list, $sql;
2766 }
2767 }
2768 }
2769 }
2770 }
2772 daemon_log("$session_id DEBUG: create_fai_release_db: Inserting ".scalar @sql_list." entries to DB",138);
2773 &release_ldap_handle($ldap_handle);
2774 if(@sql_list) {
2775 unshift @sql_list, "VACUUM";
2776 unshift @sql_list, "DELETE FROM $table_name";
2777 $fai_release_db->exec_statementlist(\@sql_list);
2778 }
2779 daemon_log("$session_id DEBUG: create_fai_release_db: Done with inserting",138);
2780 } else {
2781 daemon_log("$session_id INFO: create_fai_release_db: error: " . $mesg->code, 5);
2782 }
2783 daemon_log("$session_id INFO: create_fai_release_db: finished",5);
2784 }
2785 return $result;
2786 }
2788 sub get_fai_types {
2789 my $tmp_classes = shift || return undef;
2790 my @result;
2792 foreach my $type(keys %{$tmp_classes}) {
2793 if(defined($tmp_classes->{$type}[0]) && (!($tmp_classes->{$type}[0] =~ /^.*?removed.*?$/))) {
2794 my $entry = {
2795 type => $type,
2796 state => $tmp_classes->{$type}[0],
2797 };
2798 push @result, $entry;
2799 }
2800 }
2802 return @result;
2803 }
2805 sub get_fai_state {
2806 my $result = "";
2807 my $tmp_classes = shift || return $result;
2809 foreach my $type(keys %{$tmp_classes}) {
2810 if(defined($tmp_classes->{$type}[0])) {
2811 $result = $tmp_classes->{$type}[0];
2813 # State is equal for all types in class
2814 last;
2815 }
2816 }
2818 return $result;
2819 }
2821 sub resolve_fai_classes {
2822 my ($fai_base, $ldap_handle, $session_id) = @_;
2823 if (not defined $session_id) { $session_id = 0; }
2824 my $result;
2825 my @possible_fai_classes= ("FAIscript", "FAIhook", "FAIpartitionTable", "FAItemplate", "FAIvariable", "FAIprofile", "FAIpackageList");
2826 my $fai_filter= "(|(&(objectClass=FAIclass)(|(objectClass=".join(")(objectClass=", @possible_fai_classes).")))(objectClass=FAIbranch))";
2827 my $fai_classes;
2829 daemon_log("$session_id DEBUG: Searching for FAI entries in base $fai_base", 138);
2830 my $mesg= $ldap_handle->search(
2831 base => $fai_base,
2832 scope => 'sub',
2833 attrs => ['cn','objectClass','FAIstate'],
2834 filter => $fai_filter,
2835 );
2836 daemon_log("$session_id DEBUG: Found ".$mesg->count()." FAI entries", 138);
2838 if($mesg->{'resultCode'} == 0 &&
2839 $mesg->count != 0) {
2840 foreach my $entry (@{$mesg->{entries}}) {
2841 if($entry->exists('cn')) {
2842 my $tmp_dn= $entry->dn();
2843 $tmp_dn= substr( $tmp_dn, 0, length($tmp_dn)
2844 - length($fai_base) - 1 );
2846 # Skip classname and ou dn parts for class
2847 my $tmp_release = ($1) if $tmp_dn =~ /^[^,]+,[^,]+,(.*?)$/;
2849 # Skip classes without releases
2850 if((!defined($tmp_release)) || length($tmp_release)==0) {
2851 next;
2852 }
2854 my $tmp_cn= $entry->get_value('cn');
2855 my $tmp_state= $entry->get_value('FAIstate');
2857 my $tmp_type;
2858 # Get FAI type
2859 for my $oclass(@{$entry->get_value('objectClass', asref => 1)}) {
2860 if(grep $_ eq $oclass, @possible_fai_classes) {
2861 $tmp_type= $oclass;
2862 last;
2863 }
2864 }
2866 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2867 # A Subrelease
2868 my @sub_releases = split(/,/, $tmp_release);
2870 # Walk through subreleases and build hash tree
2871 my $hash;
2872 while(my $tmp_sub_release = pop @sub_releases) {
2873 $hash .= "\{'$tmp_sub_release'\}->";
2874 }
2875 eval('push @{$fai_classes->'.$hash.'{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";');
2876 } else {
2877 # A branch, no subrelease
2878 push @{$fai_classes->{$tmp_release}->{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";
2879 }
2880 } elsif (!$entry->exists('cn')) {
2881 my $tmp_dn= $entry->dn();
2882 $tmp_dn= substr( $tmp_dn, 0, length($tmp_dn)
2883 - length($fai_base) - 1 );
2884 my $tmp_release = ($1) if $tmp_dn =~ /^(.*?)$/;
2886 # Skip classes without releases
2887 if((!defined($tmp_release)) || length($tmp_release)==0) {
2888 next;
2889 }
2891 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2892 # A Subrelease
2893 my @sub_releases= split(/,/, $tmp_release);
2895 # Walk through subreleases and build hash tree
2896 my $hash;
2897 while(my $tmp_sub_release = pop @sub_releases) {
2898 $hash .= "\{'$tmp_sub_release'\}->";
2899 }
2900 # Remove the last two characters
2901 chop($hash);
2902 chop($hash);
2904 eval('$fai_classes->'.$hash.'= {}');
2905 } else {
2906 # A branch, no subrelease
2907 if(!exists($fai_classes->{$tmp_release})) {
2908 $fai_classes->{$tmp_release} = {};
2909 }
2910 }
2911 }
2912 }
2914 # The hash is complete, now we can honor the copy-on-write based missing entries
2915 foreach my $release (keys %$fai_classes) {
2916 $result->{$release}= deep_copy(apply_fai_inheritance($fai_classes->{$release}));
2917 }
2918 }
2919 return $result;
2920 }
2922 sub apply_fai_inheritance {
2923 my $fai_classes = shift || return {};
2924 my $tmp_classes;
2926 # Get the classes from the branch
2927 foreach my $class (keys %{$fai_classes}) {
2928 # Skip subreleases
2929 if($class =~ /^ou=.*$/) {
2930 next;
2931 } else {
2932 $tmp_classes->{$class}= deep_copy($fai_classes->{$class});
2933 }
2934 }
2936 # Apply to each subrelease
2937 foreach my $subrelease (keys %{$fai_classes}) {
2938 if($subrelease =~ /ou=/) {
2939 foreach my $tmp_class (keys %{$tmp_classes}) {
2940 if(!exists($fai_classes->{$subrelease}->{$tmp_class})) {
2941 $fai_classes->{$subrelease}->{$tmp_class} =
2942 deep_copy($tmp_classes->{$tmp_class});
2943 } else {
2944 foreach my $type (keys %{$tmp_classes->{$tmp_class}}) {
2945 if(!exists($fai_classes->{$subrelease}->{$tmp_class}->{$type})) {
2946 $fai_classes->{$subrelease}->{$tmp_class}->{$type}=
2947 deep_copy($tmp_classes->{$tmp_class}->{$type});
2948 }
2949 }
2950 }
2951 }
2952 }
2953 }
2955 # Find subreleases in deeper levels
2956 foreach my $subrelease (keys %{$fai_classes}) {
2957 if($subrelease =~ /ou=/) {
2958 foreach my $subsubrelease (keys %{$fai_classes->{$subrelease}}) {
2959 if($subsubrelease =~ /ou=/) {
2960 apply_fai_inheritance($fai_classes->{$subrelease});
2961 }
2962 }
2963 }
2964 }
2966 return $fai_classes;
2967 }
2969 sub get_fai_release_entries {
2970 my $tmp_classes = shift || return;
2971 my $parent = shift || "";
2972 my @result = shift || ();
2974 foreach my $entry (keys %{$tmp_classes}) {
2975 if(defined($entry)) {
2976 if($entry =~ /^ou=.*$/) {
2977 my $release_name = $entry;
2978 $release_name =~ s/ou=//g;
2979 if(length($parent)>0) {
2980 $release_name = $parent."/".$release_name;
2981 }
2982 my @bufentries = get_fai_release_entries($tmp_classes->{$entry}, $release_name, @result);
2983 foreach my $bufentry(@bufentries) {
2984 push @result, $bufentry;
2985 }
2986 } else {
2987 my @types = get_fai_types($tmp_classes->{$entry});
2988 foreach my $type (@types) {
2989 push @result,
2990 {
2991 'class' => $entry,
2992 'type' => $type->{'type'},
2993 'release' => $parent,
2994 'state' => $type->{'state'},
2995 };
2996 }
2997 }
2998 }
2999 }
3001 return @result;
3002 }
3004 sub deep_copy {
3005 my $this = shift;
3006 if (not ref $this) {
3007 $this;
3008 } elsif (ref $this eq "ARRAY") {
3009 [map deep_copy($_), @$this];
3010 } elsif (ref $this eq "HASH") {
3011 +{map { $_ => deep_copy($this->{$_}) } keys %$this};
3012 } else { die "what type is $_?" }
3013 }
3016 sub session_run_result {
3017 my ($kernel, $heap, $client_answer) = @_[KERNEL, HEAP, ARG0];
3018 $kernel->sig(CHLD => "child_reap");
3019 }
3021 sub session_run_debug {
3022 my $result = $_[ARG0];
3023 print STDERR "$result\n";
3024 }
3026 sub session_run_done {
3027 my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
3028 delete $heap->{task}->{$task_id};
3029 if (exists $heap->{ldap_handle}->{$task_id}) {
3030 &release_ldap_handle($heap->{ldap_handle}->{$task_id});
3031 }
3032 delete $heap->{ldap_handle}->{$task_id};
3033 }
3036 sub create_sources_list {
3037 my $session_id = shift || 0;
3038 my $result="/tmp/gosa_si_tmp_sources_list";
3040 # Remove old file
3041 if(stat($result)) {
3042 unlink($result);
3043 &main::daemon_log("$session_id DEBUG: remove an old version of '$result'", 7);
3044 }
3046 open(my $fh, ">", "$result");
3047 if (not defined $fh) {
3048 &main::daemon_log("$session_id DEBUG: cannot open '$result' for writing", 7);
3049 return undef;
3050 }
3051 if(defined($main::ldap_server_dn) and length($main::ldap_server_dn) > 0) {
3052 my $ldap_handle = &get_ldap_handle($session_id);
3053 my $mesg=$ldap_handle->search(
3054 base => $main::ldap_server_dn,
3055 scope => 'base',
3056 attrs => 'FAIrepository',
3057 filter => 'objectClass=FAIrepositoryServer'
3058 );
3059 if($mesg->count) {
3060 foreach my $entry(@{$mesg->{'entries'}}) {
3061 foreach my $value(@{$entry->get_value('FAIrepository', asref => 1)}) {
3062 my ($server, $tag, $release, $sections)= split /\|/, $value;
3063 my $line = "deb $server $release";
3064 $sections =~ s/,/ /g;
3065 $line.= " $sections";
3066 print $fh $line."\n";
3067 }
3068 }
3069 }
3070 &release_ldap_handle($ldap_handle);
3071 } else {
3072 if (defined $main::ldap_server_dn){
3073 &main::daemon_log("$session_id ERROR: something wrong with ldap_server_dn '$main::ldap_server_dn', abort create_sources_list", 1);
3074 } else {
3075 &main::daemon_log("$session_id ERROR: no ldap_server_dn found, abort create_sources_list", 1);
3076 }
3077 }
3078 close($fh);
3080 return $result;
3081 }
3084 sub run_create_packages_list_db {
3085 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
3086 my $session_id = $session->ID;
3087 my $task = POE::Wheel::Run->new(
3088 Priority => +20,
3089 Program => sub {&create_packages_list_db(undef, $session_id)},
3090 StdoutEvent => "session_run_result",
3091 StderrEvent => "session_run_debug",
3092 CloseEvent => "session_run_done",
3093 );
3094 $heap->{task}->{ $task->ID } = $task;
3095 }
3098 sub create_packages_list_db {
3099 my ($sources_file, $session_id) = @_;
3101 # it should not be possible to trigger a recreation of packages_list_db
3102 # while packages_list_db is under construction, so set flag packages_list_under_construction
3103 # which is tested befor recreation can be started
3104 if (-r $packages_list_under_construction) {
3105 daemon_log("$session_id WARNING: packages_list_db is right now under construction, please wait until this process is finished", 3);
3106 return;
3107 } else {
3108 daemon_log("$session_id INFO: create_packages_list_db: start", 5);
3109 # set packages_list_under_construction to true
3110 system("touch $packages_list_under_construction");
3111 @packages_list_statements=();
3112 }
3114 if (not defined $session_id) { $session_id = 0; }
3116 if (not defined $sources_file) {
3117 &main::daemon_log("$session_id INFO: no sources_file given for creating packages list so trigger creation of it", 5);
3118 $sources_file = &create_sources_list($session_id);
3119 }
3121 if (not defined $sources_file) {
3122 &main::daemon_log("$session_id ERROR: no sources_file given under '$sources_file', skip create_packages_list_db", 1);
3123 unlink($packages_list_under_construction);
3124 return;
3125 }
3127 my $line;
3129 open(my $CONFIG, "<", "$sources_file") or do {
3130 daemon_log( "$session_id ERROR: create_packages_list_db: Failed to open '$sources_file'", 1);
3131 unlink($packages_list_under_construction);
3132 return;
3133 };
3135 # Read lines
3136 while ($line = <$CONFIG>){
3137 # Unify
3138 chop($line);
3139 $line =~ s/^\s+//;
3140 $line =~ s/^\s+/ /;
3142 # Strip comments
3143 $line =~ s/#.*$//g;
3145 # Skip empty lines
3146 if ($line =~ /^\s*$/){
3147 next;
3148 }
3150 # Interpret deb line
3151 if ($line =~ /^deb [^\s]+\s[^\s]+\s[^\s]+/){
3152 my( $baseurl, $dist, $sections ) = ($line =~ /^deb\s([^\s]+)\s+([^\s]+)\s+(.*)$/);
3153 my $section;
3154 foreach $section (split(' ', $sections)){
3155 &parse_package_info( $baseurl, $dist, $section, $session_id );
3156 }
3157 }
3158 else {
3159 daemon_log("$session_id ERROR: cannot parse line '$line'", 1);
3160 }
3161 }
3163 close ($CONFIG);
3165 if(keys(%repo_dirs)) {
3166 find(\&cleanup_and_extract, keys( %repo_dirs ));
3167 &main::strip_packages_list_statements();
3168 $packages_list_db->exec_statementlist(\@packages_list_statements);
3169 }
3170 unlink($packages_list_under_construction);
3171 daemon_log("$session_id INFO: create_packages_list_db: finished", 5);
3172 return;
3173 }
3175 # This function should do some intensive task to minimize the db-traffic
3176 sub strip_packages_list_statements {
3177 my @existing_entries= @{$packages_list_db->exec_statement("SELECT * FROM $main::packages_list_tn")};
3178 my @new_statement_list=();
3179 my $hash;
3180 my $insert_hash;
3181 my $update_hash;
3182 my $delete_hash;
3183 my $known_packages_hash;
3184 my $local_timestamp=get_time();
3186 foreach my $existing_entry (@existing_entries) {
3187 $hash->{@{$existing_entry}[0]}->{@{$existing_entry}[1]}->{@{$existing_entry}[2]}= $existing_entry;
3188 }
3190 foreach my $statement (@packages_list_statements) {
3191 if($statement =~ /^INSERT/i) {
3192 # Assign the values from the insert statement
3193 my ($distribution,$package,$version,$section,$description,$template,$timestamp) = ($1,$2,$3,$4,$5,$6,$7) if $statement =~
3194 /^INSERT\s+?INTO\s+?$main::packages_list_tn\s+?VALUES\s*?\('(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)'\s*?\)$/si;
3195 if(exists($hash->{$distribution}->{$package}->{$version})) {
3196 # If section or description has changed, update the DB
3197 if(
3198 (! (@{$hash->{$distribution}->{$package}->{$version}}[3] eq $section)) or
3199 (! (@{$hash->{$distribution}->{$package}->{$version}}[4] eq $description))
3200 ) {
3201 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,undef);
3202 } else {
3203 # package is already present in database. cache this knowledge for later use
3204 @{$known_packages_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
3205 }
3206 } else {
3207 # Insert a non-existing entry to db
3208 @{$insert_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
3209 }
3210 } elsif ($statement =~ /^UPDATE/i) {
3211 my ($template,$package,$version) = ($1,$2,$3) if $statement =~
3212 /^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;
3213 foreach my $distribution (keys %{$hash}) {
3214 if(exists($insert_hash->{$distribution}->{$package}->{$version})) {
3215 # update the insertion hash to execute only one query per package (insert instead insert+update)
3216 @{$insert_hash->{$distribution}->{$package}->{$version}}[5]= $template;
3217 } elsif(exists($hash->{$distribution}->{$package}->{$version})) {
3218 if( ! (@{$hash->{$distribution}->{$package}->{$version}}[5] eq $template)) {
3219 my $section;
3220 my $description;
3221 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) and
3222 length(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) > 0 ) {
3223 $section= @{$update_hash->{$distribution}->{$package}->{$version}}[3];
3224 }
3225 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
3226 $description= @{$update_hash->{$distribution}->{$package}->{$version}}[4];
3227 }
3228 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
3229 }
3230 }
3231 }
3232 }
3233 }
3235 # Check for orphaned entries
3236 foreach my $existing_entry (@existing_entries) {
3237 my $distribution= @{$existing_entry}[0];
3238 my $package= @{$existing_entry}[1];
3239 my $version= @{$existing_entry}[2];
3240 my $section= @{$existing_entry}[3];
3242 if(
3243 exists($insert_hash->{$distribution}->{$package}->{$version}) ||
3244 exists($update_hash->{$distribution}->{$package}->{$version}) ||
3245 exists($known_packages_hash->{$distribution}->{$package}->{$version})
3246 ) {
3247 next;
3248 } else {
3249 # Insert entry to delete hash
3250 @{$delete_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section);
3251 }
3252 }
3254 # unroll the insert hash
3255 foreach my $distribution (keys %{$insert_hash}) {
3256 foreach my $package (keys %{$insert_hash->{$distribution}}) {
3257 foreach my $version (keys %{$insert_hash->{$distribution}->{$package}}) {
3258 push @new_statement_list, "INSERT INTO $main::packages_list_tn VALUES ('$distribution','$package','$version',"
3259 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[3]',"
3260 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[4]',"
3261 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[5]',"
3262 ."'$local_timestamp')";
3263 }
3264 }
3265 }
3267 # unroll the update hash
3268 foreach my $distribution (keys %{$update_hash}) {
3269 foreach my $package (keys %{$update_hash->{$distribution}}) {
3270 foreach my $version (keys %{$update_hash->{$distribution}->{$package}}) {
3271 my $set = "";
3272 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3])) {
3273 $set .= "section = '@{$update_hash->{$distribution}->{$package}->{$version}}[3]', ";
3274 }
3275 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
3276 $set .= "description = '@{$update_hash->{$distribution}->{$package}->{$version}}[4]', ";
3277 }
3278 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[5])) {
3279 $set .= "template = '@{$update_hash->{$distribution}->{$package}->{$version}}[5]', ";
3280 }
3281 if(defined($set) and length($set) > 0) {
3282 $set .= "timestamp = '$local_timestamp'";
3283 } else {
3284 next;
3285 }
3286 push @new_statement_list,
3287 "UPDATE $main::packages_list_tn SET $set WHERE"
3288 ." distribution = '$distribution'"
3289 ." AND package = '$package'"
3290 ." AND version = '$version'";
3291 }
3292 }
3293 }
3295 # unroll the delete hash
3296 foreach my $distribution (keys %{$delete_hash}) {
3297 foreach my $package (keys %{$delete_hash->{$distribution}}) {
3298 foreach my $version (keys %{$delete_hash->{$distribution}->{$package}}) {
3299 my $section = @{$delete_hash->{$distribution}->{$package}->{$version}}[3];
3300 push @new_statement_list, "DELETE FROM $main::packages_list_tn WHERE distribution='$distribution' AND package='$package' AND version='$version' AND section='$section'";
3301 }
3302 }
3303 }
3305 unshift(@new_statement_list, "VACUUM");
3307 @packages_list_statements = @new_statement_list;
3308 }
3311 sub parse_package_info {
3312 my ($baseurl, $dist, $section, $session_id)= @_;
3313 my ($package);
3314 if (not defined $session_id) { $session_id = 0; }
3315 my ($path) = ($baseurl =~ m%://[^/]*(.*)$%);
3316 $repo_dirs{ "${repo_path}/pool" } = 1;
3318 foreach $package ("Packages.gz"){
3319 daemon_log("$session_id DEBUG: create_packages_list: fetch $baseurl, $dist, $section", 266);
3320 get_package( "$baseurl/dists/$dist/$section/binary-$arch/$package", "$outdir/$dist/$section", $session_id );
3321 parse_package( "$outdir/$dist/$section", $dist, $path, $session_id );
3322 }
3324 }
3327 sub get_package {
3328 my ($url, $dest, $session_id)= @_;
3329 if (not defined $session_id) { $session_id = 0; }
3331 my $tpath = dirname($dest);
3332 -d "$tpath" || mkpath "$tpath";
3334 # This is ugly, but I've no time to take a look at "how it works in perl"
3335 if(0 == system("wget '$url' -O '$dest' 2>/dev/null") ) {
3336 system("gunzip -cd '$dest' > '$dest.in'");
3337 daemon_log("$session_id DEBUG: run command: gunzip -cd '$dest' > '$dest.in'", 266);
3338 unlink($dest);
3339 daemon_log("$session_id DEBUG: delete file '$dest'", 266);
3340 } else {
3341 daemon_log("$session_id ERROR: create_packages_list_db: get_packages: fetching '$url' into '$dest' failed!", 1);
3342 }
3343 return 0;
3344 }
3347 sub parse_package {
3348 my ($path, $dist, $srv_path, $session_id)= @_;
3349 if (not defined $session_id) { $session_id = 0;}
3350 my ($package, $version, $section, $description);
3351 my $timestamp = &get_time();
3353 if(not stat("$path.in")) {
3354 daemon_log("$session_id ERROR: create_packages_list: parse_package: file '$path.in' is not readable",1);
3355 return;
3356 }
3358 open(my $PACKAGES, "<", "$path.in");
3359 if(not defined($PACKAGES)) {
3360 daemon_log("$session_id ERROR: create_packages_list_db: parse_package: cannot open '$path.in'",1);
3361 return;
3362 }
3364 # Read lines
3365 while (<$PACKAGES>){
3366 my $line = $_;
3367 # Unify
3368 chop($line);
3370 # Use empty lines as a trigger
3371 if ($line =~ /^\s*$/){
3372 my $sql = "INSERT INTO packages_list VALUES ('$dist', '$package', '$version', '$section', '$description', '', '$timestamp')";
3373 push(@packages_list_statements, $sql);
3374 $package = "none";
3375 $version = "none";
3376 $section = "none";
3377 $description = "none";
3378 next;
3379 }
3381 # Trigger for package name
3382 if ($line =~ /^Package:\s/){
3383 ($package)= ($line =~ /^Package: (.*)$/);
3384 next;
3385 }
3387 # Trigger for version
3388 if ($line =~ /^Version:\s/){
3389 ($version)= ($line =~ /^Version: (.*)$/);
3390 next;
3391 }
3393 # Trigger for description
3394 if ($line =~ /^Description:\s/){
3395 ($description)= &encode_base64(($line =~ /^Description: (.*)$/));
3396 next;
3397 }
3399 # Trigger for section
3400 if ($line =~ /^Section:\s/){
3401 ($section)= ($line =~ /^Section: (.*)$/);
3402 next;
3403 }
3405 # Trigger for filename
3406 if ($line =~ /^Filename:\s/){
3407 my ($filename) = ($line =~ /^Filename: (.*)$/);
3408 store_fileinfo( $package, $filename, $dist, $srv_path, $version, $repo_path );
3409 next;
3410 }
3411 }
3413 close( $PACKAGES );
3414 unlink( "$path.in" );
3415 }
3418 sub store_fileinfo {
3419 my( $package, $file, $dist, $path, $vers, $srvdir) = @_;
3421 my %fileinfo = (
3422 'package' => $package,
3423 'dist' => $dist,
3424 'version' => $vers,
3425 );
3427 $repo_files{ "${srvdir}/$file" } = \%fileinfo;
3428 }
3431 sub cleanup_and_extract {
3432 my $fileinfo = $repo_files{ $File::Find::name };
3434 if( defined $fileinfo ) {
3435 my $dir = "$outdir/$fileinfo->{ 'dist' }/debconf.d";
3436 my $sql;
3437 my $package = $fileinfo->{ 'package' };
3438 my $newver = $fileinfo->{ 'version' };
3440 mkpath($dir);
3441 system( "dpkg -e '$File::Find::name' '$dir/DEBIAN'" );
3443 if( -f "$dir/DEBIAN/templates" ) {
3445 daemon_log("0 DEBUG: Found debconf templates in '$package' - $newver", 266);
3447 my $tmpl= ""; {
3448 local $/=undef;
3449 open(my $FILE, "$dir/DEBIAN/templates");
3450 $tmpl = &encode_base64(<$FILE>);
3451 close($FILE);
3452 }
3453 rmtree("$dir/DEBIAN/templates");
3455 $sql= "update $main::packages_list_tn set template = '$tmpl' where package = '$package' and version = '$newver';";
3456 push @packages_list_statements, $sql;
3457 }
3458 }
3460 return;
3461 }
3464 sub prepare_server_registration
3465 {
3466 # Add foreign server from cfg file
3467 my @foreign_server_list;
3468 if ($foreign_server_string ne "") {
3469 my @cfg_foreign_server_list = split(",", $foreign_server_string);
3470 foreach my $foreign_server (@cfg_foreign_server_list) {
3471 push(@foreign_server_list, $foreign_server);
3472 }
3474 daemon_log("0 INFO: found foreign server in config file: ".join(", ", @foreign_server_list), 5);
3475 }
3477 # Perform a DNS lookup for server registration if flag is true
3478 if ($dns_lookup eq "true") {
3479 # Add foreign server from dns
3480 my @tmp_servers;
3481 if (not $server_domain) {
3482 # Try our DNS Searchlist
3483 for my $domain(get_dns_domains()) {
3484 chomp($domain);
3485 my ($tmp_domains, $error_string) = &get_server_addresses($domain);
3486 if(@$tmp_domains) {
3487 for my $tmp_server(@$tmp_domains) {
3488 push @tmp_servers, $tmp_server;
3489 }
3490 }
3491 }
3492 if(@tmp_servers && length(@tmp_servers)==0) {
3493 daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
3494 }
3495 } else {
3496 @tmp_servers = &get_server_addresses($server_domain);
3497 if( 0 == @tmp_servers ) {
3498 daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
3499 }
3500 }
3502 daemon_log("0 INFO: found foreign server via DNS ".join(", ", @tmp_servers), 5);
3504 foreach my $server (@tmp_servers) {
3505 unshift(@foreign_server_list, $server);
3506 }
3507 } else {
3508 daemon_log("0 INFO: DNS lookup for server registration is disabled", 5);
3509 }
3511 # eliminate duplicate entries
3512 @foreign_server_list = &del_doubles(@foreign_server_list);
3513 my $all_foreign_server = join(", ", @foreign_server_list);
3514 daemon_log("0 INFO: found foreign server in config file and DNS: '$all_foreign_server'", 5);
3516 # add all found foreign servers to known_server
3517 my $cur_timestamp = &get_time();
3518 foreach my $foreign_server (@foreign_server_list) {
3520 # do not add myself to known_server_db
3521 if (&is_local($foreign_server)) { next; }
3522 ######################################
3524 my $res = $known_server_db->add_dbentry( {table=>$known_server_tn,
3525 primkey=>['hostname'],
3526 hostname=>$foreign_server,
3527 macaddress=>"",
3528 status=>'not_yet_registered',
3529 hostkey=>"none",
3530 loaded_modules => "none",
3531 timestamp=>$cur_timestamp,
3532 update_time=>'19700101000000',
3533 } );
3534 }
3535 }
3537 sub register_at_foreign_servers {
3538 my ($kernel) = $_[KERNEL];
3540 # Update status and update-time of all si-server with expired update_time and
3541 # block them for race conditional registration processes of other si-servers.
3542 my $act_time = &get_time();
3543 my $block_statement = "UPDATE $known_server_tn SET status='new_server',update_time='19700101000000' WHERE (CAST(update_time AS UNSIGNED))<$act_time ";
3544 my $block_res = $known_server_db->exec_statement($block_statement);
3546 # Fetch all si-server from db where update_time is younger than act_time
3547 my $fetch_statement = "SELECT * FROM $known_server_tn WHERE update_time='19700101000000'";
3548 my $fetch_res = $known_server_db->exec_statement($fetch_statement);
3550 # Detect already connected clients. Will be added to registration msg later.
3551 my $client_sql = "SELECT * FROM $known_clients_tn";
3552 my $client_res = $known_clients_db->exec_statement($client_sql);
3554 # Send registration messag to all fetched si-server
3555 foreach my $hit (@$fetch_res) {
3556 my $hostname = @$hit[0];
3557 my $hostkey = &create_passwd;
3559 # Add already connected clients to registration message
3560 my $myhash = &create_xml_hash('new_server', $server_address, $hostname);
3561 &add_content2xml_hash($myhash, 'key', $hostkey);
3562 map(&add_content2xml_hash($myhash, 'client', @{$_}[0].",".@{$_}[4]), @$client_res);
3564 # Add locally loaded gosa-si modules to registration message
3565 my $loaded_modules = {};
3566 while (my ($package, $pck_info) = each %$known_modules) {
3567 next if ((!defined(@$pck_info[2])) || (!(ref (@$pck_info[2]) eq 'HASH')));
3568 foreach my $act_module (keys(%{@$pck_info[2]})) {
3569 $loaded_modules->{$act_module} = "";
3570 }
3571 }
3572 map(&add_content2xml_hash($myhash, "loaded_modules", $_), keys(%$loaded_modules));
3574 # Add macaddress to registration message
3575 my ($host_ip, $host_port) = split(/:/, $hostname);
3576 my $local_ip = &get_local_ip_for_remote_ip($host_ip);
3577 my $network_interface= &get_interface_for_ip($local_ip);
3578 my $host_mac = &get_mac_for_interface($network_interface);
3579 &add_content2xml_hash($myhash, 'macaddress', $host_mac);
3581 # Build registration message and send it
3582 my $foreign_server_msg = &create_xml_string($myhash);
3583 my $error = &send_msg_to_target($foreign_server_msg, $hostname, $ServerPackages_key, "new_server", 0);
3584 }
3587 # After n sec perform a check of all server registration processes
3588 $kernel->delay_set("control_server_registration", 2);
3590 return;
3591 }
3594 sub control_server_registration {
3595 my ($kernel) = $_[KERNEL];
3597 # Check if all registration processes succeed or not
3598 my $select_statement = "SELECT * FROM $known_server_tn WHERE status='new_server'";
3599 my $select_res = $known_server_db->exec_statement($select_statement);
3601 # If at least one registration process failed, maybe in case of a race condition
3602 # with a foreign registration process
3603 if (@$select_res > 0)
3604 {
3605 # Release block statement 'new_server' to make the server accessible
3606 # for foreign registration processes
3607 my $update_statement = "UPDATE $known_server_tn SET status='waiting' WHERE status='new_server'";
3608 my $update_res = $known_server_db->exec_statement($update_statement);
3610 # Set a random delay to avoid the registration race condition
3611 my $new_foreign_servers_register_delay = int(rand(4))+1;
3612 $kernel->delay_set("register_at_foreign_servers", $new_foreign_servers_register_delay);
3613 }
3614 # If all registration processes succeed
3615 else
3616 {
3617 $kernel->delay_set("register_at_foreign_servers", $foreign_servers_register_delay);
3618 }
3620 return;
3621 }
3623 #
3624 #==== MAIN = main ==============================================================
3625 #
3626 # Parse options and allow '-vvv'
3627 Getopt::Long::Configure( 'bundling' );
3628 GetOptions( 'v|verbose+' => \$verbose,
3629 'h|help' => \&usage,
3630 'c|config=s' => \$config,
3631 'x|dump-config=i' => \$dump_config,
3632 'f|foreground' => \$foreground,
3633 'd=s' => \$debug_parts)
3634 or usage( '', 1 );
3636 # We may want to dump the default configuration
3637 if( defined $dump_config ) {
3638 if($dump_config==1) {
3639 } elsif ($dump_config==2) {
3640 dump_configuration( $dump_config );
3641 } else {
3642 usage( "Dump configuration value has to be 1 or 2" );
3643 }
3644 }
3646 # read and set config parameters
3647 &read_configfile($config, %cfg_defaults);
3648 #&check_pid;
3650 # not sure but seems to be for igonring phantom
3651 $SIG{CHLD} = 'IGNORE';
3653 # Create the PID object
3654 #
3655 $pid = File::Pid->new({
3656 file => $pid_file,
3657 });
3659 # Write the PID file
3660 $pid->write;
3662 # forward error messages to logfile
3663 if( ! $foreground ) {
3664 open( STDIN, '+>/dev/null' );
3665 open( STDOUT, '+>&STDIN' );
3666 open( STDERR, '+>&STDIN' );
3667 }
3669 # Just fork, if we are not in foreground mode
3670 if( ! $foreground ) {
3671 chdir '/' or die "Can't chdir to /: $!";
3672 $pid = fork;
3673 setsid or die "Can't start a new session: $!";
3674 umask 0;
3675 #} else {
3676 # $pid = $$;
3677 }
3679 # Do something useful - put our PID into the pid_file
3680 #if( 0 != $pid ) {
3681 # open( my $LOCK_FILE, ">", "$pid_file" );
3682 # print $LOCK_FILE "$pid\n";
3683 # close( $LOCK_FILE );
3684 # if( !$foreground ) {
3685 # exit( 0 )
3686 # };
3687 #}
3689 # parse head url and revision from svn
3690 my $server_status_hash = { 'developmental'=>'revision', 'stable'=>'release'};
3691 $server_version =~ /^\$HeadURL: (\S+) \$:\$Rev: (\d+) \$$/;
3692 $server_headURL = defined $1 ? $1 : 'unknown' ;
3693 $server_revision = defined $2 ? $2 : 'unknown' ;
3694 if ($server_headURL =~ /\/tag\// ||
3695 $server_headURL =~ /\/branches\// ) {
3696 $server_status = "stable";
3697 } else {
3698 $server_status = "developmental" ;
3699 }
3700 # Prepare log file and set permissions
3701 $root_uid = getpwnam('root');
3702 $adm_gid = getgrnam('adm');
3703 open(my $FH, ">>", "$log_file");
3704 close($FH);
3705 chmod(0440, $log_file);
3706 chown($root_uid, $adm_gid, $log_file);
3708 # prepare directory for databases
3709 mkpath('/var/lib/gosa-si', 0, {owner=>'root', group=>'root'});
3711 # remove leftover files in tmp for packaged.db populate
3712 #rmtree( '/tmp/packages_list_db',0,1);
3714 # remove list of sources from apt.sources.list
3715 #unlink '/tmp/gosa_si_tmp_sources_list';
3717 # remove marker that the list creation is in progress
3718 unlink '/tmp/packages_list_creation_in_progress';
3721 daemon_log(" ", 1);
3722 daemon_log("$0 started!", 1);
3723 daemon_log("status: $server_status", 1);
3724 daemon_log($server_status_hash->{$server_status}.": $server_revision", 1);
3726 # Buildup data bases
3727 {
3728 no strict "refs";
3730 daemon_log("0 INFO: importing database module '$db_module'", 1);
3732 if ($db_module eq "DBmysql") {
3734 # connect to incoming_db
3735 $incoming_db = ("GOsaSI::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3737 # connect to gosa-si job queue
3738 $job_db = ("GOsaSI::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3740 # connect to known_clients_db
3741 $known_clients_db = ("GOsaSI::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3743 # connect to foreign_clients_db
3744 $foreign_clients_db = ("GOsaSI::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3746 # connect to known_server_db
3747 $known_server_db = ("GOsaSI::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3749 # connect to login_usr_db
3750 $login_users_db = ("GOsaSI::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3752 # connect to fai_server_db
3753 $fai_server_db = ("GOsaSI::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3755 # connect to fai_release_db
3756 $fai_release_db = ("GOsaSI::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3758 # connect to packages_list_db
3759 $packages_list_db = ("GOsaSI::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3761 # connect to messaging_db
3762 $messaging_db = ("GOsaSI::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3764 } elsif ($db_module eq "DBsqlite") {
3766 daemon_log("0 INFO: Removing SQLlite lock files", 1);
3768 # delete old DBsqlite lock files to be replace by rmtree !!
3769 system('rm -f /var/lib/gosa-si/*.si.lock*');
3770 #rmtree( '/var/lib/gosa-si/',0,1);
3772 # connect to incoming_db
3773 unlink($incoming_file_name);
3774 $incoming_db = ("GOsaSI::".$db_module)->new($incoming_file_name);
3775 #chmod(0640, $incoming_file_name);
3776 #chown($root_uid, $adm_gid, $incoming_file_name);
3778 # connect to gosa-si job queue
3779 $job_db = ("GOsaSI::".$db_module)->new($job_queue_file_name);
3780 #chmod(0640, $job_queue_file_name);
3781 #chown($root_uid, $adm_gid, $job_queue_file_name);
3783 # connect to known_clients_db
3784 #unlink($known_clients_file_name);
3785 $known_clients_db = ("GOsaSI::".$db_module)->new($known_clients_file_name);
3786 #chmod(0640, $known_clients_file_name);
3787 #chown($root_uid, $adm_gid, $known_clients_file_name);
3789 # connect to foreign_clients_db
3790 #unlink($foreign_clients_file_name);
3791 $foreign_clients_db = ("GOsaSI::".$db_module)->new($foreign_clients_file_name);
3792 #chmod(0640, $foreign_clients_file_name);
3793 #chown($root_uid, $adm_gid, $foreign_clients_file_name);
3795 # connect to known_server_db
3796 #unlink($known_server_file_name); # do not delete, gosa-si-server should be forced to check config file and dns at each start
3797 $known_server_db = ("GOsaSI::".$db_module)->new($known_server_file_name);
3798 #chmod(0640, $known_server_file_name);
3799 #chown($root_uid, $adm_gid, $known_server_file_name);
3801 # connect to login_usr_db
3802 #unlink($login_users_file_name);
3803 $login_users_db = ("GOsaSI::".$db_module)->new($login_users_file_name);
3804 #chmod(0640, $login_users_file_name);
3805 #chown($root_uid, $adm_gid, $login_users_file_name);
3807 # connect to fai_server_db
3808 #unlink($fai_server_file_name);
3809 $fai_server_db = ("GOsaSI::".$db_module)->new($fai_server_file_name);
3810 #chmod(0640, $fai_server_file_name);
3811 #chown($root_uid, $adm_gid, $fai_server_file_name);
3813 # connect to fai_release_db
3814 #unlink($fai_release_file_name);
3815 $fai_release_db = ("GOsaSI::".$db_module)->new($fai_release_file_name);
3816 #chmod(0640, $fai_release_file_name);
3817 #chown($root_uid, $adm_gid, $fai_release_file_name);
3819 # connect to packages_list_db
3820 #unlink($packages_list_under_construction);
3821 $packages_list_db = ("GOsaSI::".$db_module)->new($packages_list_file_name);
3822 #chmod(0640, $packages_list_file_name);
3823 #chown($root_uid, $adm_gid, $packages_list_file_name);
3825 # connect to messaging_db
3826 #unlink($messaging_file_name);
3827 $messaging_db = ("GOsaSI::".$db_module)->new($messaging_file_name);
3828 #chmod(0640, $messaging_file_name);
3829 #chown($root_uid, $adm_gid, $messaging_file_name);
3830 }
3831 }
3833 # Creating tables
3835 daemon_log("0 INFO: creating tables in database with '$db_module'", 1);
3837 $messaging_db->create_table($messaging_tn, \@messaging_col_names);
3838 $packages_list_db->create_table($packages_list_tn, \@packages_list_col_names);
3839 $fai_release_db->create_table($fai_release_tn, \@fai_release_col_names);
3840 $fai_server_db->create_table($fai_server_tn, \@fai_server_col_names);
3841 $login_users_db->create_table($login_users_tn, \@login_users_col_names);
3842 $known_server_db->create_table($known_server_tn, \@known_server_col_names);
3843 $foreign_clients_db->create_table($foreign_clients_tn, \@foreign_clients_col_names);
3844 $known_clients_db->create_table($known_clients_tn, \@known_clients_col_names);
3845 $incoming_db->create_table($incoming_tn, \@incoming_col_names);
3846 $job_db->create_table($job_queue_tn, \@job_queue_col_names);
3848 # create xml object used for en/decrypting
3849 $xml = new XML::Simple();
3851 # Import all modules
3852 &import_modules;
3854 # Check wether all modules are gosa-si valid passwd check
3855 &password_check;
3857 # Check DNS and config file for server registration
3858 if ($serverPackages_enabled eq "true") { &prepare_server_registration; }
3860 # Create functions hash
3861 while (my ($module, @mod_info) = each %$known_modules)
3862 {
3863 while (my ($plugin, $functions) = each %{$mod_info[0][2]})
3864 {
3865 while (my ($function, $nothing) = each %$functions )
3866 {
3867 $known_functions->{$function} = $nothing;
3868 }
3869 }
3870 }
3872 # Prepare for using Opsi
3873 if ($opsi_enabled eq "true") {
3874 use JSON::RPC::Client;
3875 use XML::Quote qw(:all);
3876 $opsi_url= "https://".$opsi_admin.":".$opsi_password."@".$opsi_server.":4447/rpc";
3877 $opsi_client = new JSON::RPC::Client;
3878 }
3881 POE::Component::Server::TCP->new(
3882 Alias => "TCP_SERVER",
3883 Port => $server_port,
3884 ClientInput => sub {
3885 my ($kernel, $input, $heap, $session) = @_[KERNEL, ARG0, HEAP, SESSION];
3886 my $session_id = $session->ID;
3887 if ($input =~ /;([\d\.]+):([\d]+)$/)
3888 {
3889 # Messages from other servers should be blocked if config option is set
3890 if (($2 eq $server_port) && ($serverPackages_enabled eq "false"))
3891 {
3892 return;
3893 }
3894 &daemon_log("$session_id DEBUG: incoming message from '$1:$2'", 11);
3895 }
3896 else
3897 {
3898 my $remote_ip = $heap->{'remote_ip'};
3899 &daemon_log("$session_id DEBUG: incoming message from '$remote_ip'", 11);
3900 }
3901 push(@msgs_to_decrypt, $input);
3902 $kernel->yield("msg_to_decrypt");
3903 },
3904 InlineStates => {
3905 msg_to_decrypt => \&msg_to_decrypt,
3906 watch_for_next_tasks => \&watch_for_next_tasks,
3907 next_task => \&next_task,
3908 task_result => \&handle_task_result,
3909 task_done => \&handle_task_done,
3910 task_debug => \&handle_task_debug,
3911 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!" },
3912 }
3913 );
3915 daemon_log("0 INFO: start socket for incoming xml messages at port '$server_port' ", 1);
3917 # create session for repeatedly checking the job queue for jobs
3918 POE::Session->create(
3919 inline_states => {
3920 _start => \&session_start,
3921 register_at_foreign_servers => \®ister_at_foreign_servers,
3922 control_server_registration => \&control_server_registration,
3923 sig_handler => \&sig_handler,
3924 next_task => \&next_task,
3925 task_result => \&handle_task_result,
3926 task_done => \&handle_task_done,
3927 task_debug => \&handle_task_debug,
3928 watch_for_new_messages => \&watch_for_new_messages,
3929 watch_for_delivery_messages => \&watch_for_delivery_messages,
3930 watch_for_done_messages => \&watch_for_done_messages,
3931 watch_for_new_jobs => \&watch_for_new_jobs,
3932 watch_for_modified_jobs => \&watch_for_modified_jobs,
3933 watch_for_done_jobs => \&watch_for_done_jobs,
3934 watch_for_opsi_jobs => \&watch_for_opsi_jobs,
3935 watch_for_old_known_clients => \&watch_for_old_known_clients,
3936 create_packages_list_db => \&run_create_packages_list_db,
3937 create_fai_server_db => \&run_create_fai_server_db,
3938 create_fai_release_db => \&run_create_fai_release_db,
3939 recreate_packages_db => \&run_recreate_packages_db,
3940 session_run_result => \&session_run_result,
3941 session_run_debug => \&session_run_debug,
3942 session_run_done => \&session_run_done,
3943 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!" },
3944 }
3945 );
3948 POE::Kernel->run();
3949 exit;