3674eac768114130f3e07050a1751aa1eb665b24
[kivitendo-erp.git] / scripts / task_server.pl
1 #!/usr/bin/perl
2
3 use strict;
4
5 my $exe_dir;
6
7 BEGIN {
8   use FindBin;
9
10   unshift(@INC, $FindBin::Bin . '/../modules/override'); # Use our own versions of various modules (e.g. YAML).
11   push   (@INC, $FindBin::Bin . '/..');                  # '.' will be removed from @INC soon.
12 }
13
14 use CGI qw( -no_xhtml);
15 use Cwd;
16 use Daemon::Generic;
17 use Data::Dumper;
18 use DateTime;
19 use English qw(-no_match_vars);
20 use File::Spec;
21 use List::MoreUtils qw(any);
22 use List::Util qw(first);
23 use POSIX qw(setlocale setuid setgid);
24 use SL::Auth;
25 use SL::DBUpgrade2;
26 use SL::DB::AuthClient;
27 use SL::DB::BackgroundJob;
28 use SL::BackgroundJob::ALL;
29 use SL::Form;
30 use SL::Helper::DateTime;
31 use SL::InstanceConfiguration;
32 use SL::LXDebug;
33 use SL::LxOfficeConf;
34 use SL::Locale;
35 use SL::Mailer;
36 use SL::System::Process;
37 use SL::System::TaskServer;
38 use Template;
39
40 our %lx_office_conf;
41
42 sub debug {
43   return if !$lx_office_conf{task_server}->{debug};
44   $::lxdebug->message(LXDebug::DEBUG1(), join(' ', "task server:", @_));
45 }
46
47 sub enabled_clients {
48   return SL::DB::Manager::AuthClient->get_all(where => [ '!task_server_user_id' => undef ]);
49 }
50
51 sub initialize_kivitendo {
52   my ($client) = @_;
53
54   chdir $exe_dir;
55
56   package main;
57
58   $::lxdebug       = LXDebug->new;
59   $::locale        = Locale->new($::lx_office_conf{system}->{language});
60   $::form          = Form->new;
61   $::auth          = SL::Auth->new;
62
63   return if !$client;
64
65   $::auth->set_client($client->id);
66
67   $::form->{__ERROR_HANDLER} = sub { die @_ };
68
69   $::instance_conf = SL::InstanceConfiguration->new;
70   $::request       = SL::Request->new(
71     cgi            => CGI->new({}),
72     layout         => SL::Layout::None->new,
73   );
74
75   die 'cannot reach auth db'               unless $::auth->session_tables_present;
76
77   $::auth->restore_session;
78   $::auth->create_or_refresh_session;
79
80   my $login = $client->task_server_user->login;
81
82   die "cannot find user $login"            unless %::myconfig = $::auth->read_user(login => $login);
83   die "cannot find locale for user $login" unless $::locale   = Locale->new($::myconfig{countrycode} || $::lx_office_conf{system}->{language});
84 }
85
86 sub cleanup_kivitendo {
87   eval { SL::DB->client->dbh->rollback; };
88
89   $::auth->save_session;
90   $::auth->expire_sessions;
91   $::auth->reset;
92
93   $::form     = undef;
94   $::myconfig = ();
95   $::request  = undef;
96   $::auth     = undef;
97 }
98
99 sub clean_before_sleeping {
100   SL::DBConnect::Cache->disconnect_all_and_clear;
101   SL::DB->db_cache->clear;
102
103   File::Temp::cleanup();
104 }
105
106 sub drop_privileges {
107   my $user = $lx_office_conf{task_server}->{run_as};
108   return unless $user;
109
110   my ($uid, $gid);
111   while (my @details = getpwent()) {
112     next unless $details[0] eq $user;
113     ($uid, $gid) = @details[2, 3];
114     last;
115   }
116   endpwent();
117
118   if (!$uid) {
119     print "Error: Cannot drop privileges to ${user}: user does not exist\n";
120     exit 1;
121   }
122
123   if (!setgid($gid)) {
124     print "Error: Cannot drop group privileges to ${user} (group ID $gid): $!\n";
125     exit 1;
126   }
127
128   if (!setuid($uid)) {
129     print "Error: Cannot drop user privileges to ${user} (user ID $uid): $!\n";
130     exit 1;
131   }
132 }
133
134 sub notify_on_failure {
135   my (%params) = @_;
136
137   my $cfg = $lx_office_conf{'task_server/notify_on_failure'} || {};
138
139   return if any { !$cfg->{$_} } qw(send_email_to email_from email_subject email_template);
140
141   chdir $exe_dir;
142
143   return debug("Template " . $cfg->{email_template} . " missing!") unless -f $cfg->{email_template};
144
145   my $email_to = $cfg->{send_email_to};
146   if ($email_to !~ m{\@}) {
147     my %user = $::auth->read_user(login => $email_to);
148     return debug("cannot find user for notification $email_to") unless %user;
149
150     $email_to = $user{email};
151     return debug("user for notification " . $user{login} . " doesn't have a valid email address") unless $email_to =~ m{\@};
152   }
153
154   my $template  = Template->new({
155     INTERPOLATE => 0,
156     EVAL_PERL   => 0,
157     ABSOLUTE    => 1,
158     CACHE_SIZE  => 0,
159     ENCODING    => 'utf8',
160   });
161
162   return debug("Could not create Template instance") unless $template;
163
164   $params{client} = $::auth->client;
165
166   eval {
167     my $body;
168     $template->process($cfg->{email_template}, \%params, \$body);
169
170     Mailer->new(
171       from         => $cfg->{email_from},
172       to           => $email_to,
173       subject      => $cfg->{email_subject},
174       content_type => 'text/plain',
175       charset      => 'utf-8',
176       message      => $body,
177     )->send;
178
179     1;
180   } or do {
181     debug("Sending a failure notification failed with an exception: $@");
182   };
183 }
184
185 sub gd_preconfig {
186   my $self = shift;
187
188   # Initialize character type locale to be UTF-8 instead of C:
189   foreach my $locale (qw(de_DE.UTF-8 en_US.UTF-8)) {
190     last if setlocale('LC_CTYPE', $locale);
191   }
192
193   SL::LxOfficeConf->read($self->{configfile});
194
195   die "Missing section [task_server] in config file" unless $lx_office_conf{task_server};
196
197   if ($lx_office_conf{task_server}->{login} || $lx_office_conf{task_server}->{client}) {
198     print STDERR <<EOT;
199 ERROR: The keys 'login' and/or 'client' are still present in the
200 section [task_server] in the configuration file. These keys are
201 deprecated. You have to configure the clients for which to run the
202 task server in the web admin interface.
203
204 The task server will refuse to start until the keys have been removed from
205 the configuration file.
206 EOT
207     exit 2;
208   }
209
210   initialize_kivitendo();
211
212   my $dbupdater_auth = SL::DBUpgrade2->new(form => $::form, auth => 1)->parse_dbupdate_controls;
213   if ($dbupdater_auth->unapplied_upgrade_scripts($::auth->dbconnect)) {
214     print STDERR <<EOT;
215 The authentication database requires an upgrade. Please login to
216 kivitendo's administration interface in order to apply it. The task
217 server cannot start until the upgrade has been applied.
218 EOT
219     exit 2;
220   }
221
222   drop_privileges();
223
224   return ();
225 }
226
227 sub run_once_for_all_clients {
228   initialize_kivitendo();
229
230   my $clients = enabled_clients();
231
232   foreach my $client (@{ $clients }) {
233     debug("Running for client ID " . $client->id . " (" . $client->name . ")");
234
235     my $ok = eval {
236       initialize_kivitendo($client);
237
238       my $jobs = SL::DB::Manager::BackgroundJob->get_all_need_to_run;
239
240       if (@{ $jobs }) {
241         debug(" Executing the following jobs: " . join(' ', map { $_->package_name } @{ $jobs }));
242       } else {
243         debug(" No jobs to execute found");
244       }
245
246       foreach my $job (@{ $jobs }) {
247         # Provide fresh global variables in case legacy code modifies
248         # them somehow.
249         initialize_kivitendo($client);
250
251         my $history = $job->run;
252
253         debug("   Executed job " . $job->package_name .
254               "; result: " . (!$history ? "no return value" : $history->has_failed ? "failed" : "succeeded") .
255               ($history && $history->has_failed ? "; error: " . $history->error_col : ""));
256
257         notify_on_failure(history => $history) if $history && $history->has_failed;
258       }
259
260       1;
261     };
262
263     if (!$ok) {
264       my $error = $EVAL_ERROR;
265       debug("Exception during execution: ${error}");
266       notify_on_failure(exception => $error);
267     }
268
269     cleanup_kivitendo();
270   }
271 }
272
273 sub gd_run {
274   while (1) {
275     $SIG{'ALRM'} = 'IGNORE';
276
277     run_once_for_all_clients();
278
279     debug("Sleeping");
280
281     clean_before_sleeping();
282
283     my $seconds = 60 - (localtime)[0];
284     if (!eval {
285       $SIG{'ALRM'} = sub {
286         $SIG{'ALRM'} = 'IGNORE';
287         debug("Got woken up by SIGALRM");
288         die "Alarm!\n"
289       };
290       sleep($seconds < 30 ? $seconds + 60 : $seconds);
291       1;
292     }) {
293       die $@ unless $@ eq "Alarm!\n";
294     }
295   }
296 }
297
298 $exe_dir = SL::System::Process->exe_dir;
299 chdir($exe_dir) || die "Cannot change directory to ${exe_dir}\n";
300
301 mkdir SL::System::TaskServer::PID_BASE() if !-d SL::System::TaskServer::PID_BASE();
302
303 my $file = first { -f } ("${exe_dir}/config/kivitendo.conf", "${exe_dir}/config/lx_office.conf", "${exe_dir}/config/kivitendo.conf.default");
304
305 die "No configuration file found." unless $file;
306
307 $file = File::Spec->abs2rel(Cwd::abs_path($file), Cwd::abs_path($exe_dir));
308
309 newdaemon(configfile => $file,
310           progname   => 'kivitendo-background-jobs',
311           pidbase    => SL::System::TaskServer::PID_BASE() . '/',
312           );
313
314 1;