source: trunk/LATMOS-Accounts/lib/LATMOS/Accounts/Utils.pm @ 1991

Last change on this file since 1991 was 1991, checked in by nanardon, 7 years ago

Use hash for loadCSV options to allow more parameters

  • Property svn:keywords set to Id Rev
File size: 10.2 KB
Line 
1package LATMOS::Accounts::Utils;
2use 5.010000;
3use strict;
4use warnings;
5use Exporter ();
6use vars qw(@ISA @EXPORT_OK @EXPORT);
7use utf8;
8use LATMOS::Accounts::Log;
9use File::Temp qw(tempfile);
10use Crypt::Cracklib;
11use Text::CSV;
12
13our $VERSION = (q$Rev$ =~ /^Rev: (\d+) /)[0];
14
15=head1 NAME
16
17LATMOS::Accounts::Utils - Utils functions
18
19=head1 FUNCTIONS
20
21=cut
22
23@ISA = qw(Exporter);
24@EXPORT = qw(to_ascii exec_command switch_user run_via_sudo buildLogin loadCSV yesno);
25@EXPORT_OK = qw(to_ascii exec_command switch_user run_via_sudo buildLogin loadCSV yesno);
26
27=head2 to_ascii($text)
28
29Replace in C<$text> non ascii caracters from iso-8859-15 table to ascii
30equivalent caracter.
31
32=cut
33
34sub to_ascii {
35    my ($text) = @_;
36    return $text unless(defined($text));
37    utf8::decode($text) unless(utf8::is_utf8($text));
38    $text =~ s/œ/oe/g;
39    $text =~ s/Ê/ae/g;
40    $text =~ tr {uàâÀÂÄÀçéÚêëÉÈÊËïîÏÎÞöÎÖÔÌûÛÜć}
41                {uaaaAAAceeeeEEEEiiIIoooOOuuUUc};
42    $text =~ s/([^[:ascii:]])/_/g;
43    $text
44} 
45
46=head2 exec_command($command, $env)
47
48Execute C<$command> and redirect output to log system.
49
50C<$env> is a hashref containing environment variable to set, all variables are
51prefixed by 'LA_'.
52
53=cut
54
55sub exec_command {
56    my ($command, $env) = @_;
57    my $rout = undef;
58    $rout = \$_[2] if(@_ > 2);
59
60    my @exec = ref $command
61        ? (@$command)
62        : ($command);
63    la_log(LA_DEBUG, 'running command `%s\'', join(' ', @exec));
64
65    pipe(my $rh, my $wh);
66    my $pid = fork;
67    if (!defined($pid)) {
68        la_log(LA_ERR, "Can't launch script: cannot fork");
69    } elsif ($pid) {
70        # Father
71        close($wh);
72        my $header;
73        while (<$rh>) {
74            if ($rout) {
75                $$rout .= $_;
76            } else {
77                chomp;
78                if (!$header) {
79                    $header = 1;
80                    la_log(LA_NOTICE, "exec `%s'", join(' ', @exec));
81                }
82                la_log(LA_NOTICE, "output: %s", $_);
83            }
84        }
85        waitpid($pid, 0);
86        if (my $exitstatus = $?) {
87            la_log(LA_ERR, 'command %s exit with status %d',
88                join(' ', @exec), $exitstatus);
89            return;
90        } else {
91            return 1;
92        }
93    } else {
94        # Child
95        close($rh);
96        ( $ENV{LA_MODULE} ) = caller();
97        foreach (keys %{ $env || {} }) {
98            $ENV{"LA_$_"} = $env->{$_};
99        }
100        open(STDOUT, ">&=" . fileno($wh));
101        open(STDERR, ">&=" . fileno($wh));
102        exec(@exec);
103        exit($!);
104    }
105    1
106}
107
108=head2 parse_obj_file($handle)
109
110Read file content from C<$handle> and return hash containing parsed attributes
111
112=cut
113
114sub parse_obj_file {
115    my ($handle) = @_;
116
117    my %attributes;
118    while (my $line = <$handle>) {
119        $line =~ /^#/ and next;
120        chomp($line);
121        my ($attr, $value) = $line =~ /^\s*(\S+):\s*(.*)\s*$/ or
122            die "Malformed input file\n";
123        $value =~ s/\s*$//;
124        $value =~ s/\\n/\n/g;
125        if ($attributes{$attr}) {
126            if (ref $attributes{$attr}) {
127                push(@{ $attributes{$attr} }, $value);
128            } else {
129                my $temp = $attributes{$attr};
130                $attributes{$attr} = [ $temp, $value ];
131            }
132        } else {
133            $attributes{$attr} = $value eq '' ? undef : $value;
134            # Don't remember why this is here
135            #$attr eq 'exported' && !defined $attributes{$attr} and $attributes{$attr} = 1;
136        }
137    }
138    %attributes
139}
140
141=head2 dump_read_temp_file($writecb, $readcb)
142
143Create a temporary file, call C<$writecb()> function, run editor and if file get
144modified call C<$readcb>.
145
146=cut
147
148sub dump_read_temp_file {
149    my ($writecb, $readcb) = @_;
150
151    my ($fh, $filename) = tempfile(CLEANUP => 0);
152    $writecb->($fh) or return;
153    $fh = undef; # closing file
154    my $res;
155   
156    my @stat = stat($filename);
157    while (1) {
158        my $cmd = ($ENV{EDITOR} || 'vi') . " $filename";
159        warn "Running $cmd\n";
160        if (system($cmd) == -1 ) {
161            warn "Cannot run editor $!\n";
162            last;
163        }
164        if ((stat($filename))[9] == $stat[9]) {
165            warn "No change existing\n";
166            last;
167        }
168
169        open($fh, '<', $filename) or return;
170        $res = $readcb->($fh);
171        $fh = undef; # closing again
172        $res < 2 and last;
173    }
174    unlink($filename);
175    $res;
176}
177
178=head2 loadCSV($fh, $callback, $initcallback)
179
180Parse CVS files and return an array for each parsed line.
181
182C<%options> may contains
183
184=over 4
185
186=item C<initcb> An function call after parsing first line
187
188=item C<cb> A function call for each line
189
190=back
191
192=cut
193
194sub loadCSV {
195    my ($fh, %opt) = @_;
196
197    my $csv = Text::CSV->new({
198            blank_is_undef => 1,
199            binary => 1,
200    });
201
202    binmode($fh, ":encoding(UTF-8)");
203
204    # First line contains attribute
205    my $columns = $csv->getline( $fh );
206
207    $csv->column_names($columns);
208
209    if ($opt{initcb}) {
210        $opt{initcb}->($csv);
211    }
212
213    my $all = [];
214    my $linecount = 1;
215    while ( my $row = $csv->getline_hr( $fh ) ) {
216        $linecount++;
217        if ($opt{cb}) {
218            if (! $opt{cb}->($row, $linecount)) {
219                return;
220            }
221        }
222        push(@{ $all }, $row);
223    }
224    $csv->eof () or do {
225        return;
226    };
227
228    return $all;
229}
230
231=head2 check_oid_validity($name)
232
233Check C<$name> is suitable to be used as object identifier. Return the error
234text, undef if no error.
235
236=cut
237
238sub check_oid_validity {
239    my ($name) = @_;
240    return "leadind space" if ($name =~ /^\s/);
241    return "trailing space" if ($name =~ /\s$/);
242    return "containing space" if ($name =~ /\s/);
243
244    return;
245}
246
247=head2 check_ug_validity($name)
248
249Check C<$name> is suitable to used as user or group identifier.
250
251=cut
252
253sub check_ug_validity {
254    my ($name) = @_;
255    return "Empty name is not a valid name !"
256        if (!$name);
257    return "first caractere must be a-z"
258        if ($name !~ /^[a-z]/);
259    return "must contain only a-z,0-9"
260        if ($name !~ /^[a-z,0-9,_,\-,\.]+$/);
261
262    return check_oid_validity($name);
263}
264
265=head2 switch_user($runas)
266
267Switch effective id of the process to user named C<$runas>
268
269=cut
270
271sub switch_user {
272    my ($runas) = @_;
273
274    if ($< == 0 || $> == 0) {
275        my @info = getpwnam($runas) or do {
276            warn "Can find user $runas";
277            return;
278        };
279        $> = $info[3];
280        return;
281    } else {
282        warn "we are not root";
283    }
284}
285
286=head2 run_via_sudo($runas)
287
288Rerun current programme as C<$runas> user using sudo
289
290=cut
291
292sub run_via_sudo {
293    my ($runas) = @_;
294
295    my @info = getpwnam($runas) or do {
296        warn "Can find user $runas";
297        return;
298    }; 
299    if ($< != $info[3]) {
300        exec('sudo', '-u', $runas, $0, @ARGV) or "Can run $!";
301    }
302}
303
304=head2 genpassword(%options)
305
306Generate a random password, options are:
307
308=over 4
309
310=item length
311
312The minimum password length (default is 6)
313
314=item nonalpha
315
316Include non alpha-numeric caracters
317
318=item syllables
319
320Use a set of syllables instead letter
321
322=back
323
324=cut
325
326sub genpassword {
327    my (%options) = @_;
328    my @non_alpha = (qw$; : / ! ( ) [ ] { } + = @ - " ' % * & . ? < >$, ',', '$');
329    my @letters = ('a' .. 'z', 'A' .. 'Z', 0 .. 9);
330
331    my @consonants = qw(b d f g j k l m n r s t v x z ch);
332    my @vowels = qw(a e i o u ou oi io ia iu);
333
334    $options{length} ||= 8 + int(rand(3));
335
336    while (1) {
337        if ($options{syllables}) {
338            $options{length} = int($options{length} / 2);
339            $options{length} = 3 if ($options{length} < 3);
340        } else {
341            $options{length} = 6 if ($options{length} < 6);
342        }
343
344        my @chars;
345
346        if ($options{nonalpha}) {
347            for(0 .. (0 + int(rand(3)))) {
348                push(@chars, (@non_alpha[rand(scalar(@non_alpha))]));
349            }
350        }
351
352        foreach (1 .. ($options{length} - scalar(@chars))) {
353            if ($options{syllables}) {
354                my $c = @consonants[rand(scalar(@consonants))];
355                my $v = @vowels[rand(scalar(@vowels))];
356                push(@chars, "$c$v");
357            } else {
358                push(@chars, (@letters[rand(scalar(@letters))]));
359            }
360        }
361
362
363        my $pass = join('', sort { rand() <=> rand() } @chars);
364        if (length($pass) >= 6 && fascist_check($pass) eq 'ok') {
365            return $pass;
366        }
367    }
368}
369
370=head2 buildLogin([$cb, ] @names)
371
372Try to find a proper login from @names.
373
374Optionnal $cb is a callback to check the solution over exiting data. If return false, another solution is tried
375
376Example:
377
378    buildLogin(sub { $_[0] ? 1 : 0 }, "sn", "givenName")
379
380=cut
381
382sub buildLogin {
383    my (@names) = @_;
384    my $cb = undef;
385
386    if (ref $names[0]) {
387        $cb = shift(@names);
388    } else {
389        $cb = sub { 1 }; # always validating
390    }
391
392    # Cleaning names:
393    foreach (@names) {
394        $_ ||= '';
395        $_ = lc(to_ascii($_));
396        s/[^\w]//g;
397    }
398
399    @names = grep { $_ } @names;
400    my $base = shift(@names) or return;
401    my $rest = join('', grep { $_ } @names);
402
403    if ((my $l = length($base)) > 10) {
404        my $len = 8;
405        while ($len < $l) {
406            my $try  = substr($base, 0, $len);
407            if ($cb->($try)) {
408                return $try;
409            }
410            $len++;
411        }
412    } elsif ($cb->($base)) {
413        return $base;
414    }
415
416    my $try = $base;
417    while (my $len = length($try) - length($base) + 1) {
418        if ($len > length($rest)) {
419            last;
420        }
421        $try = $base . substr($rest, 0, $len);
422        if ($cb->($try)) {
423            return $try;
424        }
425    }
426    return;
427}
428
429=head2 yesno($value, $default)
430
431Check is a paramter mean yes or no
432
433=cut
434
435sub yesno {
436    my ($value, $default) = @_;
437
438    if ($value =~ /^(yes|1|true)$/i) {
439        return 1;
440    }
441    if ($value =~ /^(no|0|false)$/i) {
442        return 0;
443    }
444    if ($value =~ /^\d+$/ and $value != 0) {
445        return 1;
446    }
447    return $default || $value;
448}
449
4501;
451
452__END__
453
454=head1 SEE ALSO
455
456L<sudo>
457
458=head1 AUTHOR
459
460Olivier Thauvin, E<lt>olivier.thauvin@aerov.jussieu.frE<gt>
461
462=head1 COPYRIGHT AND LICENSE
463
464Copyright (C) 2008, 2009, 2010, 2011, 2012 CNRS SA/CETP/LATMOS
465
466This library is free software; you can redistribute it and/or modify
467it under the same terms as Perl itself, either Perl version 5.10.0 or,
468at your option, any later version of Perl 5 you may have available.
469
470=cut
Note: See TracBrowser for help on using the repository browser.