Commit | Line | Data |
---|---|---|
6e805a57 | 1 | #!/usr/bin/perl -w |
cdf5b41f JL |
2 | |
3 | use Config::General; | |
4 | use Data::Dumper; | |
5 | use Getopt::Std; | |
6 | use IO::Handle; | |
7 | use POSIX qw(setsid setuid setgid); | |
8 | use POSIX ":sys_wait_h"; | |
9 | ||
6e805a57 LLL |
10 | use strict; |
11 | ||
cdf5b41f JL |
12 | |
13 | ################# | |
6e805a57 | 14 | ## Global vars ## |
cdf5b41f JL |
15 | ################# |
16 | ||
6e805a57 | 17 | my $confile = ''; |
cdf5b41f JL |
18 | my %conf; |
19 | my %running; | |
20 | my $reload_conf=0; | |
21 | my $debug=0; | |
22 | #For each process, uid and gid to set after fork | |
23 | my %gid; | |
24 | my %uid; | |
25 | ||
26 | #For sheduler | |
27 | #Number of launch, for each daemon | |
28 | my %launches; | |
29 | my %ratio_launch_prio; | |
30 | my %last_schedule; | |
cdf5b41f JL |
31 | |
32 | ############### | |
33 | ## Functions ## | |
34 | ############### | |
35 | ||
36 | sub 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 | |
43 | sub 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 | ||
53 | sub debug_msg(#){ | |
6e805a57 LLL |
54 | my $text=shift; |
55 | print STDERR $text unless ($debug == 0); | |
cdf5b41f | 56 | } |
6e805a57 | 57 | |
cdf5b41f JL |
58 | |
59 | sub 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 |
108 | sub 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 | 117 | sub 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 | 150 | sub 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 | 169 | sub 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 | 181 | sub 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 ! | |
198 | sub 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 | |
227 | sub 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 | ||
247 | sub 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 |
275 | sub 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 | ||
302 | sub 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 | |
378 | sub 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 | ||
387 | sub 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(); |