1 ## @file
2 # @brief Implementation of a GOsa-SI event module.
3 # @details A GOsa-SI event module containing all functions to handle incoming messages from clients.
5 package clMessages;
6 use Exporter;
7 @ISA = qw(Exporter);
8 my @events = (
9 "confirm_usr_msg",
10 "PROGRESS",
11 "FAIREBOOT",
12 "TASKSKIP",
13 "TASKBEGIN",
14 "TASKEND",
15 "TASKERROR",
16 "HOOK",
17 "GOTOACTIVATION",
18 "LOGIN",
19 "LOGOUT",
20 "CURRENTLY_LOGGED_IN",
21 "save_fai_log",
22 );
23 @EXPORT = @events;
25 use strict;
26 use warnings;
27 use Data::Dumper;
28 use GOSA::GosaSupportDaemon;
29 use MIME::Base64;
32 BEGIN {}
34 END {}
37 ## @method get_events()
38 # @details A brief function returning a list of functions which are exported by importing the module.
39 # @return List of all provided functions
40 sub get_events {
41 return \@events;
42 }
44 ## @method confirm_usr_msg()
45 # @details Confirmed messages are set in the messaging_db from d (deliverd) to s(seen).
46 # @param msg - STRING - xml message with tags 'message', 'subject' and 'usr'
47 # @param msg_hash - HASHREF - message information parsed into a hash
48 # @param session_id - INTEGER - POE session id of the processing of this message
49 sub confirm_usr_msg {
50 my ($msg, $msg_hash, $session_id) = @_;
51 my $message = @{$msg_hash->{'message'}}[0];
52 my $subject = @{$msg_hash->{'subject'}}[0];
53 my $usr = @{$msg_hash->{'usr'}}[0];
55 # set update for this message
56 my $sql = "UPDATE $main::messaging_tn SET flag='s' WHERE (message='$message' AND subject='$subject' AND message_to='$usr')";
57 &main::daemon_log("$session_id DEBUG: $sql", 7);
58 my $res = $main::messaging_db->exec_statement($sql);
61 return;
62 }
65 ## @method save_fai_log()
66 # @details Creates under /var/log/fai/ the directory '$macaddress' and stores within all FAI log files from client.
67 # @param msg - STRING - xml message with tags 'macaddress' and 'save_fai_log'
68 # @param msg_hash - HASHREF - message information parsed into a hash
69 # @param session_id - INTEGER - POE session id of the processing of this message
70 sub save_fai_log {
71 my ($msg, $msg_hash, $session_id) = @_;
72 my $header = @{$msg_hash->{'header'}}[0];
73 my $source = @{$msg_hash->{'source'}}[0];
74 my $macaddress = @{$msg_hash->{'macaddress'}}[0];
75 my $all_logs = @{$msg_hash->{$header}}[0];
77 # if there is nothing to log
78 if( ref($all_logs) eq "HASH" ) { return; }
80 my $client_fai_log_dir = $main::client_fai_log_dir;
81 if (not -d $client_fai_log_dir) {
82 mkdir($client_fai_log_dir, 0755)
83 }
85 $client_fai_log_dir = File::Spec->catfile( $client_fai_log_dir, $macaddress );
86 if (not -d $client_fai_log_dir) {
87 mkdir($client_fai_log_dir, 0755)
88 }
90 my $time = &get_time;
91 $time = substr($time, 0, 8)."_".substr($time, 8, 6);
92 $client_fai_log_dir = File::Spec->catfile( $client_fai_log_dir, "install_$time" );
93 mkdir($client_fai_log_dir, 0755);
95 my @all_logs = split(/log_file:/, $all_logs);
96 foreach my $log (@all_logs) {
97 if (length $log == 0) { next; };
98 my ($log_file, $log_string) = split(":", $log);
99 my $client_fai_log_file = File::Spec->catfile( $client_fai_log_dir, $log_file);
101 open(my $LOG_FILE, ">$client_fai_log_file");
102 print $LOG_FILE &decode_base64($log_string);
103 close($LOG_FILE);
105 }
106 return;
107 }
109 ## @method LOGIN()
110 # @details Reported user from client is added to login_users_db.
111 # @param msg - STRING - xml message with tag 'LOGIN'
112 # @param msg_hash - HASHREF - message information parsed into a hash
113 # @param session_id - INTEGER - POE session id of the processing of this message
114 sub LOGIN {
115 my ($msg, $msg_hash, $session_id) = @_;
116 my $header = @{$msg_hash->{'header'}}[0];
117 my $source = @{$msg_hash->{'source'}}[0];
118 my $login = @{$msg_hash->{$header}}[0];
120 my %add_hash = ( table=>$main::login_users_tn,
121 primkey=> ['client', 'user'],
122 client=>$source,
123 user=>$login,
124 timestamp=>&get_time,
125 );
126 my ($res, $error_str) = $main::login_users_db->add_dbentry( \%add_hash );
127 if ($res != 0) {
128 &main::daemon_log("$session_id ERROR: cannot add entry to known_clients: $error_str");
129 return;
130 }
132 return;
133 }
136 ## @method LOGOUT()
137 # @details Reported user from client is deleted from login_users_db.
138 # @param msg - STRING - xml message with tag 'LOGOUT'
139 # @param msg_hash - HASHREF - message information parsed into a hash
140 # @param session_id - INTEGER - POE session id of the processing of this message
141 sub LOGOUT {
142 my ($msg, $msg_hash, $session_id) = @_;
143 my $header = @{$msg_hash->{'header'}}[0];
144 my $source = @{$msg_hash->{'source'}}[0];
145 my $login = @{$msg_hash->{$header}}[0];
147 my $sql_statement = "DELETE FROM $main::login_users_tn WHERE (client='$source' AND user='$login')";
148 my $res = $main::login_users_db->del_dbentry($sql_statement);
149 &main::daemon_log("$session_id INFO: delete user '$login' at client '$source' from login_user_db", 5);
151 return;
152 }
155 ## @method CURRENTLY_LOGGED_IN()
156 # @details Reported users from client are updated in login_users_db. Users which are no longer logged in are deleted from DB.
157 # @param msg - STRING - xml message
158 # @param msg_hash - HASHREF - message information parsed into a hash
159 # @param session_id - INTEGER - POE session id of the processing of this message
160 sub CURRENTLY_LOGGED_IN {
161 my ($msg, $msg_hash, $session_id) = @_;
162 my ($sql_statement, $db_res);
163 my $header = @{$msg_hash->{'header'}}[0];
164 my $source = @{$msg_hash->{'source'}}[0];
165 my $login = @{$msg_hash->{$header}}[0];
167 if(ref $login eq "HASH") {
168 &main::daemon_log("$session_id INFO: no logged in users reported from host '$source'", 5);
169 return;
170 }
172 # fetch all user currently assigned to the client at login_users_db
173 my %currently_logged_in_user = ();
174 $sql_statement = "SELECT * FROM $main::login_users_tn WHERE client='$source'";
175 $db_res = $main::login_users_db->select_dbentry($sql_statement);
176 while( my($hit_id, $hit) = each(%{$db_res}) ) {
177 $currently_logged_in_user{$hit->{'user'}} = 1;
178 }
179 &main::daemon_log("$session_id DEBUG: logged in users from login_user_db: ".join(", ", keys(%currently_logged_in_user)), 7);
181 # update all reported users in login_user_db
182 my @logged_in_user = split(/\s+/, $login);
183 &main::daemon_log("$session_id DEBUG: logged in users reported from client: ".join(", ", @logged_in_user), 7);
184 foreach my $user (@logged_in_user) {
185 my %add_hash = ( table=>$main::login_users_tn,
186 primkey=> ['client', 'user'],
187 client=>$source,
188 user=>$user,
189 timestamp=>&get_time,
190 );
191 my ($res, $error_str) = $main::login_users_db->add_dbentry( \%add_hash );
192 if ($res != 0) {
193 &main::daemon_log("$session_id ERROR: cannot add entry to known_clients: $error_str");
194 return;
195 }
197 delete $currently_logged_in_user{$user};
198 }
200 # if there is still a user in %currently_logged_in_user
201 # although he is not reported by client
202 # then delete it from $login_user_db
203 foreach my $obsolete_user (keys(%currently_logged_in_user)) {
204 &main::daemon_log("$session_id WARNING: user '$obsolete_user' is currently not logged ".
205 "in at client '$source' but still found at login_user_db", 3);
206 my $sql_statement = "DELETE FROM $main::login_users_tn WHERE client='$source' AND user='$obsolete_user'";
207 my $res = $main::login_users_db->del_dbentry($sql_statement);
208 &main::daemon_log("$session_id WARNING: delete user '$obsolete_user' at client '$source' from login_user_db", 3);
209 }
211 return;
212 }
215 ## @method GOTOACTIVATION()
216 # @details Client is set at job_queue_db to status 'processing' and 'modified'.
217 # @param msg - STRING - xml message with tag 'macaddress'
218 # @param msg_hash - HASHREF - message information parsed into a hash
219 # @param session_id - INTEGER - POE session id of the processing of this message
220 sub GOTOACTIVATION {
221 my ($msg, $msg_hash, $session_id) = @_;
222 my $header = @{$msg_hash->{'header'}}[0];
223 my $source = @{$msg_hash->{'source'}}[0];
224 my $macaddress = @{$msg_hash->{'macaddress'}}[0];
226 # test whether content is an empty hash or a string which is required
227 my $content = @{$msg_hash->{$header}}[0];
228 if(ref($content) eq "HASH") { $content = ""; }
230 # clean up header
231 $header =~ s/CLMSG_//g;
233 my $sql_statement = "UPDATE $main::job_queue_tn ".
234 "SET status='processing', progress='goto-activation', modified='1' ".
235 "WHERE status='processing' AND macaddress LIKE '$macaddress'";
236 &main::daemon_log("$session_id DEBUG: $sql_statement", 7);
237 my $res = $main::job_db->update_dbentry($sql_statement);
238 &main::daemon_log("$session_id INFO: $header at '$macaddress'", 5);
239 return;
240 }
243 ## @method PROGRESS()
244 # @details Message reports installation progress of the client. Installation job at job_queue_db is going to be updated.
245 # @param msg - STRING - xml message with tags 'macaddress' and 'PROGRESS'
246 # @param msg_hash - HASHREF - message information parsed into a hash
247 # @param session_id - INTEGER - POE session id of the processing of this message
248 sub PROGRESS {
249 my ($msg, $msg_hash, $session_id) = @_;
250 my $header = @{$msg_hash->{'header'}}[0];
251 my $source = @{$msg_hash->{'source'}}[0];
252 my $macaddress = @{$msg_hash->{'macaddress'}}[0];
254 # test whether content is an empty hash or a string which is required
255 my $content = @{$msg_hash->{$header}}[0];
256 if(ref($content) eq "HASH") { $content = ""; }
258 # clean up header
259 $header =~ s/CLMSG_//g;
261 my $sql_statement = "UPDATE $main::job_queue_tn ".
262 "SET progress='$content', modified='1' ".
263 "WHERE status='processing' AND macaddress LIKE '$macaddress'";
264 &main::daemon_log("$session_id DEBUG: $sql_statement", 7);
265 my $res = $main::job_db->update_dbentry($sql_statement);
266 &main::daemon_log("$session_id INFO: $header at '$macaddress' - $content%", 5);
268 return;
269 }
272 ## @method FAIREBOOT()
273 # @details Message reports a FAI reboot. Job at job_queue_db is going to be updated.
274 # @param msg - STRING - xml message with tag 'macaddress' and 'FAIREBOOT'
275 # @param msg_hash - HASHREF - message information parsed into a hash
276 # @param session_id - INTEGER - POE session id of the processing of this message
277 sub FAIREBOOT {
278 my ($msg, $msg_hash, $session_id) = @_;
279 my $header = @{$msg_hash->{'header'}}[0];
280 my $source = @{$msg_hash->{'source'}}[0];
281 my $macaddress = @{$msg_hash->{'macaddress'}}[0];
283 # test whether content is an empty hash or a string which is required
284 my $content = @{$msg_hash->{$header}}[0];
285 if(ref($content) eq "HASH") { $content = ""; }
287 # clean up header
288 $header =~ s/CLMSG_//g;
290 my $sql_statement = "UPDATE $main::job_queue_tn ".
291 "SET status='processing', result='$header "."$content', modified='1' ".
292 "WHERE status='processing' AND macaddress LIKE '$macaddress'";
293 &main::daemon_log("$session_id DEBUG: $sql_statement", 7);
294 my $res = $main::job_db->update_dbentry($sql_statement);
295 &main::daemon_log("$session_id INFO: $header at '$macaddress' - '$content'", 5);
297 return;
298 }
301 ## @method TASKSKIP()
302 # @details Message reports a skipped FAI task. Job at job_queue_db is going to be updated.
303 # @param msg - STRING - xml message with tag 'macaddress'.
304 # @param msg_hash - HASHREF - message information parsed into a hash
305 # @param session_id - INTEGER - POE session id of the processing of this message
306 sub TASKSKIP {
307 my ($msg, $msg_hash, $session_id) = @_;
308 my $header = @{$msg_hash->{'header'}}[0];
309 my $source = @{$msg_hash->{'source'}}[0];
310 my $macaddress = @{$msg_hash->{'macaddress'}}[0];
312 # test whether content is an empty hash or a string which is required
313 my $content = @{$msg_hash->{$header}}[0];
314 if(ref($content) eq "HASH") { $content = ""; }
316 # clean up header
317 $header =~ s/CLMSG_//g;
319 my $sql_statement = "UPDATE $main::job_queue_tn ".
320 "SET status='processing', result='$header "."$content', modified='1' ".
321 "WHERE status='processing' AND macaddress LIKE '$macaddress'";
322 &main::daemon_log("$session_id DEBUG: $sql_statement", 7);
323 my $res = $main::job_db->update_dbentry($sql_statement);
324 &main::daemon_log("$session_id INFO: $header at '$macaddress' - '$content'", 5);
326 return;
327 }
330 ## @method TASKBEGIN()
331 # @details Message reports a starting FAI task. If the task is equal to 'finish', 'faiend' or 'savelog', job at job_queue_db is being set to status 'done' and FAI state is being set to 'localboot'. If task is equal to 'chboot', 'test' or 'confdir', just do nothing. In all other cases, job at job_queue_db is going to be updated or created if not exists.
332 # @param msg - STRING - xml message with tag 'macaddress'.
333 # @param msg_hash - HASHREF - message information parsed into a hash
334 # @param session_id - INTEGER - POE session id of the processing of this message
335 sub TASKBEGIN {
336 my ($msg, $msg_hash, $session_id) = @_;
337 my $header = @{$msg_hash->{'header'}}[0];
338 my $source = @{$msg_hash->{'source'}}[0];
339 my $target = @{$msg_hash->{'target'}}[0];
340 my $macaddress = @{$msg_hash->{'macaddress'}}[0];
342 # test whether content is an empty hash or a string which is required
343 my $content = @{$msg_hash->{$header}}[0];
344 if(ref($content) eq "HASH") { $content = ""; }
346 # clean up header
347 $header =~ s/CLMSG_//g;
349 # TASKBEGIN eq finish or faiend
350 if (($content eq 'finish')
351 || ($content eq 'faiend')
352 || ($content eq 'savelog')
353 ) {
354 my $sql_statement = "UPDATE $main::job_queue_tn ".
355 "SET status='done', result='$header "."$content', modified='1' ".
356 "WHERE status='processing' AND macaddress LIKE '$macaddress'";
357 &main::daemon_log("$session_id DEBUG: $sql_statement", 7);
358 my $res = $main::job_db->update_dbentry($sql_statement);
359 &main::daemon_log("$session_id INFO: $header at '$macaddress' - '$content'", 5);
361 # set fai_state to localboot
362 &main::change_fai_state('localboot', \@{$msg_hash->{'macaddress'}}, $session_id);
364 # TASKBEGIN eq chboot
365 } elsif (($content eq 'chboot')
366 || ($content eq 'test')
367 || ($content eq 'confdir')
368 ) {
369 # just ignor this client message
370 # do nothing
372 # other TASKBEGIN msgs
373 } else {
374 # select processing jobs for host
375 my $sql_statement = "SELECT * FROM $main::job_queue_tn WHERE status='processing' AND macaddress LIKE '$macaddress'";
376 &main::daemon_log("$session_id DEBUG: $sql_statement", 7);
377 my $res = $main::job_db->select_dbentry($sql_statement);
379 # there is exactly one job entry in queue for this host
380 if (keys(%$res) == 1) {
381 &main::daemon_log("$session_id DEBUG: there is already one processing job in queue for host '$macaddress', run an update for this entry", 7);
382 my $sql_statement = "UPDATE $main::job_queue_tn ".
383 "SET result='$header $content', modified='1' ".
384 "WHERE status='processing' AND macaddress LIKE '$macaddress'";
385 my $err = $main::job_db->update_dbentry($sql_statement);
386 if (not defined $err) {
387 &main::daemon_log("$session_id ERROR: cannot update job_db entry: ".Dumper($err), 1);
388 }
390 # there is no entry or more than one enties
391 } else {
392 # in case of more than one running jobs in queue, delete all jobs
393 if (keys(%$res) > 1) {
394 &main::daemon_log("$session_id DEBUG: there are more than one processing job in queue for host '$macaddress', ".
395 "delete entries", 7);
397 # set job to status 'done', job will be deleted automatically
398 my $sql_statement = "UPDATE $main::job_queue_tn ".
399 "SET status='done', modified='1'".
400 "WHERE status='processing' AND macaddress LIKE '$macaddress'";
401 &main::daemon_log("$session_id DEBUG: $sql_statement", 7);
402 my $res = $main::job_db->update_dbentry( $sql_statement );
404 }
406 # in case of no and more than one running jobs in queue, add one single job
407 # resolve plain name for host $macaddress
408 my $plain_name;
409 my $ldap_handle = &main::get_ldap_handle($session_id);
410 if( not defined $ldap_handle ) {
411 &main::daemon_log("$session_id ERROR: cannot connect to ldap", 1);
412 $plain_name = "none";
414 # try to fetch a 'real name'
415 } else {
416 my $mesg = $ldap_handle->search(
417 base => $main::ldap_base,
418 scope => 'sub',
419 attrs => ['cn'],
420 filter => "(macAddress=$macaddress)");
421 if($mesg->code) {
422 &main::daemon_log($mesg->error, 1);
423 $plain_name = "none";
424 } else {
425 my $entry= $mesg->entry(0);
426 $plain_name = $entry->get_value("cn");
427 }
428 }
431 &main::daemon_log("$session_id DEBUG: add job to queue for host '$macaddress'", 7);
432 my $func_dic = {table=>$main::job_queue_tn,
433 primkey=>['macaddress', 'headertag'],
434 timestamp=>&get_time,
435 status=>'processing',
436 result=>"$header $content",
437 progress=>'none',
438 headertag=>'trigger_action_reinstall',
439 targettag=>$target,
440 xmlmessage=>'none',
441 macaddress=>$macaddress,
442 plainname=>$plain_name,
443 modified=>'1',
444 siserver=>$source,
445 };
446 my ($err, $error_str) = $main::job_db->add_dbentry($func_dic);
447 if ($err != 0) {
448 &main::daemon_log("$session_id ERROR: cannot add entry to job_db: $error_str", 1);
449 }
450 }
451 }
453 return;
454 }
457 ## @method TASKEND()
458 # @details Message reports a finished FAI task. If task is equal to 'savelog', job at job_queue_db is going to be set to status 'done'. Otherwise, job is going to be updated.
459 # @param msg - STRING - xml message with tag 'macaddress'.
460 # @param msg_hash - HASHREF - message information parsed into a hash
461 # @param session_id - INTEGER - POE session id of the processing of this message
462 sub TASKEND {
463 my ($msg, $msg_hash, $session_id) = @_;
464 my $header = @{$msg_hash->{'header'}}[0];
465 my $target = @{$msg_hash->{'target'}}[0];
466 my $source = @{$msg_hash->{'source'}}[0];
467 my $macaddress = @{$msg_hash->{'macaddress'}}[0];
469 # test whether content is an empty hash or a string which is required
470 my $content = @{$msg_hash->{$header}}[0];
471 if(ref($content) eq "HASH") { $content = ""; }
473 # clean up header
474 $header =~ s/CLMSG_//g;
476 if ($content eq "savelog 0") {
477 &main::daemon_log("$session_id DEBUG: got savelog from host '$target' - job done", 7);
479 # set job to status 'done', job will be deleted automatically
480 my $sql_statement = "UPDATE $main::job_queue_tn ".
481 "SET status='done', modified='1'".
482 "WHERE status='processing' AND macaddress LIKE '$macaddress'";
483 &main::daemon_log("$session_id DEBUG: $sql_statement", 7);
484 my $res = $main::job_db->update_dbentry( $sql_statement );
486 } else {
487 my $sql_statement = "UPDATE $main::job_queue_tn ".
488 "SET status='processing', result='$header "."$content', modified='1' ".
489 "WHERE status='processing' AND macaddress LIKE '$macaddress'";
490 &main::daemon_log("$session_id DEBUG: $sql_statement", 7);
491 my $res = $main::job_db->update_dbentry($sql_statement);
492 &main::daemon_log("$session_id INFO: $header at '$macaddress' - '$content'", 5);
493 }
495 return;
496 }
499 ## @method TASKERROR()
500 # @details Message reports a FAI error. Job at job_queue_db is going to be updated.
501 # @param msg - STRING - xml message with tag 'macaddress' and 'TASKERROR'
502 # @param msg_hash - HASHREF - message information parsed into a hash
503 # @param session_id - INTEGER - POE session id of the processing of this message
504 sub TASKERROR {
505 my ($msg, $msg_hash, $session_id) = @_;
506 my $header = @{$msg_hash->{'header'}}[0];
507 my $source = @{$msg_hash->{'source'}}[0];
508 my $macaddress = @{$msg_hash->{'macaddress'}}[0];
510 # clean up header
511 $header =~ s/CLMSG_//g;
513 # test whether content is an empty hash or a string which is required
514 my $content = @{$msg_hash->{$header}}[0];
515 if(ref($content) eq "HASH") { $content = ""; }
517 # set fai_state to localboot
518 &main::change_fai_state('error', \@{$msg_hash->{'macaddress'}}, $session_id);
520 my $sql_statement = "UPDATE $main::job_queue_tn ".
521 "SET status='processing', result='$header "."$content', modified='1' ".
522 "WHERE status='processing' AND macaddress LIKE '$macaddress'";
523 &main::daemon_log("$session_id DEBUG: $sql_statement", 7);
524 my $res = $main::job_db->update_dbentry($sql_statement);
525 &main::daemon_log("$session_id INFO: $header at '$macaddress' - '$content'", 5);
527 return;
528 }
531 ## @method HOOK()
532 # @details Message reports a FAI hook. Job at job_queue_db is going to be updated.
533 # @param msg - STRING - xml message with tag 'macaddress' and 'HOOK'
534 # @param msg_hash - HASHREF - message information parsed into a hash
535 # @param session_id - INTEGER - POE session id of the processing of this message
536 sub HOOK {
537 my ($msg, $msg_hash, $session_id) = @_;
538 my $header = @{$msg_hash->{'header'}}[0];
539 my $source = @{$msg_hash->{'source'}}[0];
540 my $macaddress = @{$msg_hash->{'macaddress'}}[0];
542 # clean up header
543 $header =~ s/CLMSG_//g;
545 # test whether content is an empty hash or a string which is required
546 my $content = @{$msg_hash->{$header}}[0];
547 if(not ref($content) eq "STRING") { $content = ""; }
549 my $sql_statement = "UPDATE $main::job_queue_tn ".
550 "SET status='processing', result='$header "."$content', modified='1' ".
551 "WHERE status='processing' AND macaddress LIKE '$macaddress'";
552 &main::daemon_log("$session_id DEBUG: $sql_statement", 7);
553 my $res = $main::job_db->update_dbentry($sql_statement);
554 &main::daemon_log("$session_id INFO: $header at '$macaddress' - '$content'", 5);
556 return;
557 }
559 =pod
561 =head1 NAME
563 clMessages - Implementation of a GOsa-SI event module for GOsa-SI-server.
565 =head1 SYNOPSIS
567 use GOSA::GosaSupportDaemon;
568 use MIME::Base64;
570 =head1 DESCRIPTION
572 This GOsa-SI event module containing all functions to handle messages coming from GOsa-SI-clients.
574 This module will be automatically imported by GOsa-SI if it is under F</usr/lib/gosa-si/server/E<lt>PACKAGEMODULEE<gt>/> .
576 =head1 METHODS
578 =over 4
580 =item get_events ( )
582 =item confirm_usr_msg ( )
584 =item PROGRESS ( )
586 =item FAIREBOOT ( )
588 =item TASKSKIP ( )
590 =item TASKBEGIN ( )
592 =item TASKEND ( )
594 =item TASKERROR ( )
596 =item HOOK ( )
598 =item GOTOACTIVATION ( )
600 =item LOGIN ( )
602 =item LOGOUT ( )
604 =item CURRENTLY_LOGGED_IN ( )
606 =item save_fai_log ( )
608 =back
610 =head1 BUGS
612 Please report any bugs, or post any suggestions, to the GOsa mailing list E<lt>gosa-devel@oss.gonicus.deE<gt> or to L<https://oss.gonicus.de/labs/gosa>
614 =head1 COPYRIGHT
616 This code is part of GOsa (L<http://www.gosa-project.org>)
618 Copyright (C) 2003-2008 GONICUS GmbH
620 This program is distributed in the hope that it will be useful,
621 but WITHOUT ANY WARRANTY; without even the implied warranty of
622 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
623 GNU General Public License for more details.
625 =cut
628 1;