handle require_once and include_once, allow single quotes
[old-projects.git] / muxdaemon / muxdaemon.pl
CommitLineData
6e805a57 1#!/usr/bin/perl -w
cdf5b41f
JL
2
3use Config::General;
4use Data::Dumper;
5use Getopt::Std;
6use IO::Handle;
7use POSIX qw(setsid setuid setgid);
8use POSIX ":sys_wait_h";
9
6e805a57
LLL
10use strict;
11
cdf5b41f
JL
12
13#################
6e805a57 14## Global vars ##
cdf5b41f
JL
15#################
16
6e805a57 17my $confile = '';
cdf5b41f
JL
18my %conf;
19my %running;
20my $reload_conf=0;
21my $debug=0;
22#For each process, uid and gid to set after fork
23my %gid;
24my %uid;
25
26#For sheduler
27#Number of launch, for each daemon
28my %launches;
29my %ratio_launch_prio;
30my %last_schedule;
cdf5b41f
JL
31
32###############
33## Functions ##
34###############
35
36sub sigHUP_handler{
6e805a57
LLL
37 &debug_msg("got SIGHUP\n");
38 $reload_conf=1;
cdf5b41f 39}
6e805a57 40
cdf5b41f
JL
41
42#Read Configuration and init global vars
43sub read_conf {
6e805a57
LLL
44 my $file = shift;
45 my $config = new Config::General( -file =>"$file",
46 -AllowMultiOptions =>"yes",
47 -LowerCaseNames =>"yes",);
48 %conf = $config->getall;
49 &debug_msg(Dumper(\%conf));
cdf5b41f
JL
50}
51
52
53sub debug_msg(#){
6e805a57
LLL
54 my $text=shift;
55 print STDERR $text unless ($debug == 0);
cdf5b41f 56}
6e805a57 57
cdf5b41f
JL
58
59sub init {
6e805a57
LLL
60
61 if ($confile eq ""){
62 my $path = `pwd`;
63 chomp($path);
64 if ($path){
65 $confile="$path/muxdaemon.conf";
66 }else{
67 print STDERR "Error : unable to get working directory, $!\n";
68 exit(1);
69 }
70 }
71
72 my %opts;
73 if ( not getopts('dhf:', \%opts) or $opts{'h'}) {
74 print STDERR "\nThis is mail processing Daemon :\n",
75 "-h - this help message\n",
76 "-f <file> - use <file> as config file\n",
77 "-d - debug mode\n";
78 exit(1);
79 }
80
81 if ($opts{'f'}){
82 if (-r "$opts{'f'}") {
83 print STDERR "Using $opts{'f'} as config file\n";
84 &read_conf("$opts{'f'}");
85 } else {
86 print STDERR "$opts{'f'} : not a valid config file\n";
87 exit(1);
88 }
89 } else {
90 print STDERR "Using $confile as config file\n";
91 if (-r "$confile") {
92 &read_conf("$confile");
93 } else {
94 print STDERR "No valid configuration file found, aborting\n";
95 exit(1);
96 }
97 }
98
99 if ($opts{'d'}){
100 print STDERR "Running in debug mode ...\n";
101 $debug = 1;
102 }else{
103 $debug = 0;
104 }
cdf5b41f
JL
105}
106
f566ccb9
JL
107#init useful variables for scheduler
108sub init_sched(){
6e805a57
LLL
109 foreach my $arg (keys %{$conf{'client'}}){
110 $launches{$arg} = 0;
111 $ratio_launch_prio{$arg} = 0;
112 $last_schedule{$arg} = 0;
113 }
cdf5b41f
JL
114}
115
6e805a57 116#check configuration file
f566ccb9 117sub check_conf{
6e805a57
LLL
118 &debug_msg("Salut\n");
119 print STDERR "Checking configuration file for wrong user/groups, schedules, ....\n";
120 foreach my $arg (keys %{$conf{'client'}}){
121 #La conf du client courant
122 my %hash = %{$conf{'client'}{$arg}};
123 if ($hash{'run_as_user'}) {
124 my $id = getpwnam($hash{'run_as_user'});
125 if ($id){
126 $conf{'client'}{$arg}{'uid'} = $id;
127 } else {
128 print STDERR " Error, user $hash{'run_as_user'} does not exist, please check...\n";
129 exit(1);
130 }
131 }
132 if ($hash{'run_as_group'}){
133 my $id = getgrnam("$hash{'run_as_group'}");
134 if ($id){
135 $conf{'client'}{$arg}{'gid'} = $id;
136 } else {
137 print STDERR " Error, group $hash{'run_as_group'} does not exist, please check...\n";
138 exit(1);
139 }
140 }
141 if ($hash{'allow_many'} eq "yes" and $hash{'min_schedule'}){
142 print STDERR " Error, allow_many and min_schedule defined for $arg\n";
143 exit(1);
144 }
145 }
cdf5b41f
JL
146}
147
f566ccb9
JL
148
149#make the program run in daemon mode
cdf5b41f 150sub daemonize {
6e805a57
LLL
151 print STDERR "Forking ... \n";
152 chdir '/' or die "Can't chdir to /: $!";
153 open STDIN, '/dev/null' or die "Can't read /dev/null: $!";
154 open STDOUT, '>>/dev/null' or die "Can't write to /dev/null: $!";
155 if ($conf{'error_log'}){
156 open STDERR, ">>$conf{'error_log'}" or die "Can't write to $conf{'error_log'}: $!";
157 }else{
158 open STDERR, '>>/dev/null' or die "Can't write to /dev/null: $!";
159 }
160 defined(my $pid = fork) or die "Can't fork: $!";
161 exit if $pid;
162 setsid or die "Can't start a new session: $!";
163 #umask 0;
164 &debug_msg("Daemon started\n");
cdf5b41f
JL
165}
166
f566ccb9
JL
167
168#returns current load or any good value
cdf5b41f 169sub get_load {
6e805a57
LLL
170 my $load = 100;
171 if (open(LOAD, '</proc/loadavg')) {
172 my $line = <LOAD>;
173 ($load) = ($line =~ m/(\d+\.\d+)/);
174 close(LOAD);
175 }
176 return $load;
cdf5b41f
JL
177}
178
179#Return the list of client for which max_load is => ld
f566ccb9 180#param : load
cdf5b41f 181sub get_possible_client(#){
6e805a57
LLL
182 my $ld = shift;
183 my @list = ();
184
185 foreach my $arg (keys %{$conf{'client'}}){
186 my %hash = %{$conf{'client'}{$arg}};
187 if ($hash{'max_load'} >= $ld){
188 push(@list,$arg);
189 }
190 }
191 return \@list;
cdf5b41f 192}
6e805a57 193
cdf5b41f
JL
194
195#Return the client that should be executed according to priorities
196#given in conf file
197# Refaire avec calcul du min de launches/prio !
198sub get_next(#){
6e805a57
LLL
199 my $ref = shift;
200 my @possible_client = @{$ref};
201 #List des proc executes assez souvent
202 my $client = '';
203 my $min_ratio = -1;
204
205 foreach my $arg (@possible_client) {
206 if ($ratio_launch_prio{$arg} < $min_ratio or $min_ratio == -1){
207 $min_ratio=$ratio_launch_prio{$arg};
208 $client=$arg;
209 }
210 }
211
212 if ($client) {
213 #Update ratio for him
214 $launches{$client}++;
215 $ratio_launch_prio{$client}=$launches{$client}/$conf{'client'}{$client}{'priority'};
216 return $client;
217 }else{
218 &debug_msg("No client runnable in get_next\n");
219 return;
220 }
221 return;
cdf5b41f
JL
222}
223
224#Elimine les clients présent dans la liste qui sont en cours d'éxécution et
225#qui n'ont pas le allow_many (pour pas les éxécuter deux fois)
226#Elimine aussi les clients qui ont terminé depuis moins que min_schedule
227sub trim_possible_client(#){
6e805a57
LLL
228 my $ref=shift;
229 my @out_list;
230
231
232 foreach my $arg (@$ref){
233 if (${${$conf{'client'}}{$arg}}{'allow_many'} eq "no"){
234 #Exec only one
235 if ((time - $last_schedule{$arg}) >= ${${$conf{'client'}}{$arg}}{'min_schedule'}){
236 push(@out_list,$arg) unless grep($_ eq $arg, values %running);
237 }
238 }else{
239 push(@out_list,$arg);
240 }
241 }
242
243 return \@out_list;
cdf5b41f
JL
244}
245
246
247sub change_uid_gid(#){
6e805a57
LLL
248 my $client = shift;
249 if ($conf{'client'}{$client}{'gid'}){
250 &debug_msg("Changing gid to $conf{'client'}{$client}{'gid'} for $client ...");
251# $( = $conf{'client'}{$client}{'gid'};
252# $) = $conf{'client'}{$client}{'gid'};
253 if (POSIX::setgid($conf{'client'}{$client}{'gid'})){
254 &debug_msg("done\n");
255 } else {
256 &debug_msg("error, $!\n");
257 exit(1);
258 }
259 }
260 if ($conf{'client'}{$client}{'uid'}){
261 &debug_msg("Changing uid to $conf{'client'}{$client}{'uid'} for $client ...");
262# $< = $conf{'client'}{$client}{'uid'};
263# $> = $conf{'client'}{$client}{'uid'};
264 if (POSIX::setuid($conf{'client'}{$client}{'uid'})){
265 &debug_msg("done\n");
266 } else {
267 &debug_msg("error, $!\n");
268 exit(1);
269 }
270 }
cdf5b41f
JL
271}
272
273
274#Sends mail on the input of the command (pipe)
6e805a57
LLL
275sub send_data(#) {
276 # TODO check that the child has properly finished before moving
277 # the file tu cur/
278 my $client = shift;
279 my $fich = shift;
280 &debug_msg("run $conf{'client'}{$client}{'command'} with file $fich for $client\n");
281 my $pid = fork;
282 if (!defined $pid) {
283 die "cannot fork $!";
284 } elsif ($pid == 0) {
285 # fils
286 if (open(STDIN, '<', $fich)) {
287 &change_uid_gid($client);
288 &debug_msg("sending output to $conf{'client'}{$client}{'command'} with file $fich for $client $fich openned\n");
289 exec $conf{'client'}{$client}{'command'} or die "exec error $!";
290 } else {
291 die "Error opening $fich : $!\n";
292 }
293 } else {
294 # père
295 wait;
296 &debug_msg("sending output to $conf{'client'}{$client}{'command'} with file $fich for $client done\n");
297 # return true if the program exited normaly
298 return ($? >> 8) == 0;
299 }
300}
301
302sub list_files(#) {
303 my $client = shift;
304 my %hash = %{$conf{'client'}{$client}};
305 if ($hash{'mbox'} =~ /\/$/) { #ok, maildir format
306 my @tomove;
307 my @args;
308 my $path = $hash{'mbox'};
309 $path =~ s/\/$//igo; # suppress the final /
310 &debug_msg("Sending datas for $hash{'mbox'}\n");
311 # read the dir
312 if (opendir MAILDIR, "$path/new") {
313 my $nb_left = $hash{'nb_mails'};
314 while (my $fich = readdir MAILDIR) {
315 # skip . and ..
316 next if ($fich eq "." or $fich eq "..");
317 # read the dir until the max number of mail is done
318 &debug_msg("nb_left : $nb_left\n");
319 last if ($nb_left-- == 0);
320 &debug_msg("File : $fich\n");
321 # if add_to_cmdline is true, add to the list
322 if ($hash{'add_to_cmdline'}) {
323 push @args, $fich;
324 } else {
325 # else run the command for each file
326 if (&send_data($client, "$path/new/$fich")) {
327 push @tomove, $fich;
328 }
329 }
330 &debug_msg("Done file : $fich, $?\n");
331 }
332 closedir MAILDIR;
333 } else {
334 print STDERR "Error opening $path/new : $!\n";
335 }
336 # if add_to_cmdline is true and there are files to treat,
337 # run the command
338 if ($hash{'add_to_cmdline'} and scalar @args > 0) {
339 chdir "$path/new" or die "Cannot chdir to $path/new : $!";
340 &debug_msg("run $hash{'command'} with files @args for $client\n");
341 my @cmd = split(/ /, $hash{'command'});
342 push @cmd, @args;
343 &debug_msg("@cmd\n");
344 my $pid = fork;
345 if (!defined $pid) {
346 die "cannot fork $!";
347 } elsif ($pid == 0) {
348 # fils
349 &change_uid_gid($client);
350 exec @cmd or die "exec error $!";
351 } else {
352 # père
353 wait;
354 my $status = $? >> 8;
355 if ($status == 0) {
356 # if the command didn't fail, mark the files to be moved
357 @tomove = @args;
358 }
359 }
360 }
361 # if there is files to move, moves them
362 foreach my $fich (@tomove) {
363 &debug_msg("$path/new/$fich -> $path/cur/$fich ... ");
364 if (rename("$path/new/$fich", "$path/cur/$fich")){
365 &debug_msg("ok\n");
366 } else {
367 print STDERR "Error moving $path/new/$fich, keeped : $!\n";
368 }
369 }
370 } else {
371 &debug_msg("Mbox format not yet supported.\n");
372 exit(1);
373 }
cdf5b41f 374}
6e805a57
LLL
375
376
cdf5b41f
JL
377
378sub launch(#){
6e805a57
LLL
379 my $client = shift;
380 &change_uid_gid($client);
381 #Launch the command of a simple client
382 &debug_msg("Son $client, launching $conf{'client'}{$client}{'command'}\n");
383 exec($conf{'client'}{$client}{'command'});
cdf5b41f
JL
384}
385
386
387sub main_loop {
6e805a57
LLL
388
389 my $ld;
390 my $nchild = 0;
391
392 while (1) {
393
394 if ($reload_conf == 1){
395 #We have to load conf again
396 $reload_conf=0;
397 &debug_msg("Reloading configuration .... \n");
398 &debug_msg(" Waiting all child to terminate ...");
399 while ((my $kid=wait) != -1){
400 $nchild--;
401 delete($running{$kid});
402 }
403 &debug_msg("done\n");
404 &debug_msg(" Restarting ...");
405 &init;
406 &init_sched;
407 &debug_msg("done\n");
408 }
409
410
411 if ($nchild >= $conf{'max_client'}){
412 #We have to wait for one child (Blocking)"
413 &debug_msg("Waiting for childs to terminate\n");
414 my $kid=wait;
415 if ($kid>0){
416 &debug_msg("Kid=$kid est mort, c'était un $running{$kid}\n");
417 $nchild--;
418 delete($running{$kid});
419 }
420 } else {
421 #Rip child terminated
422 &debug_msg("Checking dead childs....\n");
423 while ( (my $kid = waitpid(-1, WNOHANG)) > 0 ){
424 &debug_msg("Kid=$kid est mort, c'était un $running{$kid}\n");
425 $nchild--;
426 delete($running{$kid});
427 }
428
429 #Look for a client to execute
430 my $ld = &get_load;
431 &debug_msg("Load = $ld, processing\n");
432 my $possible_client = &get_possible_client($ld);
433 my @list = @{$possible_client};
434 &debug_msg("Avant Trim : \n");
435 &debug_msg(Dumper($possible_client));
436 $possible_client = &trim_possible_client($possible_client);
437 &debug_msg("Après Trim : \n");
438 &debug_msg(Dumper($possible_client));
439 my $client = &get_next($possible_client);
440 if ($client) {
441 &debug_msg("$client was chosen, launching\n");
442 $last_schedule{$client} = time ;
443 my $pid = fork;
444 if (!defined $pid) {
445 print STDERR "cannot fork: $!\n";
446 } elsif ($pid != 0) {
447 #Pere, on marque qu'on l'a lancé
448 $running{$pid} = $client;
449 $nchild++;
450 &debug_msg("$client launched as PID $pid\n");
451 &debug_msg(Dumper(\%running));
452 &debug_msg(Dumper(\%launches));
453 } else {
454 #Fils
455 if ($conf{'client'}{$client}{'mbox'}) {
456 #We have to send data ourselves
457 &list_files($client);
458 } else {
459 #We only have to execute a command
460 &launch($client);
461 }
462 exit(0);
463 }
464 } else {
465 &debug_msg("No client runable\n");
466 sleep($conf{'sleep_poll'});
467 }
468 sleep($conf{'active_poll'});
469 }
470 }
cdf5b41f
JL
471}
472
473
474
475##########
476## Main ##
477##########
478
479$SIG{HUP}=\&sigHUP_handler;
480&init();
cdf5b41f 481&check_conf();
f566ccb9 482&init_sched();
cdf5b41f 483&main_loop();