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