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

Last change on this file since 2255 was 2175, checked in by nanardon, 5 years ago

Always create internal at db load

  • Property svn:keywords set to Id Rev
File size: 11.6 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: 2073 $ =~ /^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        # Empty line are skipped (or space only)
121        $line =~ /^\s*$/ and next;
122        chomp($line);
123        my ($attr, $value) = $line =~ /^\s*(\S+):\s*(.*)\s*$/ or
124            die "Malformed input file\n";
125        $value =~ s/\s*$//;
126        $value =~ s/\\n/\n/g;
127        if ($attributes{$attr}) {
128            if (ref $attributes{$attr}) {
129                push(@{ $attributes{$attr} }, $value);
130            } else {
131                my $temp = $attributes{$attr};
132                $attributes{$attr} = [ $temp, $value ];
133            }
134        } else {
135            $attributes{$attr} = $value eq '' ? undef : $value;
136            # Don't remember why this is here
137            #$attr eq 'exported' && !defined $attributes{$attr} and $attributes{$attr} = 1;
138        }
139    }
140    %attributes
141}
142
143=head2 dump_read_temp_file($writecb, $readcb)
144
145Create a temporary file, call C<$writecb()> function, run editor and if file get
146modified call C<$readcb>.
147
148=cut
149
150sub dump_read_temp_file {
151    my ($writecb, $readcb) = @_;
152
153    my ($fh, $filename) = tempfile(CLEANUP => 0);
154    $writecb->($fh) or return;
155    $fh = undef; # closing file
156    my $res;
157   
158    my @stat = stat($filename);
159    while (1) {
160        my $cmd = ($ENV{EDITOR} || 'vi') . " $filename";
161        warn "Running $cmd\n";
162        if (system($cmd) == -1 ) {
163            warn "Cannot run editor $!\n";
164            last;
165        }
166        if ((stat($filename))[9] == $stat[9]) {
167            warn "No change existing\n";
168            last;
169        }
170
171        open($fh, '<', $filename) or return;
172        $res = $readcb->($fh);
173        $fh = undef; # closing again
174        $res < 2 and last;
175    }
176    unlink($filename);
177    $res;
178}
179
180=head2 loadCSV($fh, $callback, $initcallback)
181
182Parse CVS files and return an array for each parsed line.
183
184C<%options> may contains
185
186=over 4
187
188=item C<initcb> An function call after parsing first line
189
190=item C<cb> A function call for each line
191
192=back
193
194=cut
195
196sub loadCSV {
197    my ($fh, %opt) = @_;
198
199    my $csv = Text::CSV->new({
200            blank_is_undef => 1,
201            binary => 1,
202    });
203
204    binmode($fh, ":encoding(UTF-8)");
205
206    # First line contains attribute
207    my $columns = $csv->getline( $fh );
208
209    $csv->column_names($columns);
210
211    if ($opt{initcb}) {
212        $opt{initcb}->($csv);
213    }
214
215    my $all = [];
216    my $linecount = 1;
217    while ( my $row = $csv->getline_hr( $fh ) ) {
218        $linecount++;
219        if ($opt{cb}) {
220            if (! $opt{cb}->($row, $linecount)) {
221                return;
222            }
223        }
224        push(@{ $all }, $row);
225    }
226    $csv->eof () or do {
227        return;
228    };
229
230    return $all;
231}
232
233=head2 check_oid_validity($name)
234
235Check C<$name> is suitable to be used as object identifier. Return the error
236text, undef if no error.
237
238=cut
239
240sub check_oid_validity {
241    my ($name) = @_;
242    return "leadind space" if ($name =~ /^\s/);
243    return "trailing space" if ($name =~ /\s$/);
244    return "containing space" if ($name =~ /\s/);
245
246    return;
247}
248
249=head2 check_ug_validity($name)
250
251Check C<$name> is suitable to used as user or group identifier.
252
253=cut
254
255sub check_ug_validity {
256    my ($name) = @_;
257    return "Empty name is not a valid name !"
258        if (!$name);
259    return "first caractere must be a-z"
260        if ($name !~ /^[a-z]/);
261    return "must contain only a-z,0-9"
262        if ($name !~ /^[a-z,0-9,_,\-,\.]+$/);
263    return "lenght must be < 19 characters"
264        if (length($name) >= 20);
265
266    return check_oid_validity($name);
267}
268
269=head2 switch_user($runas)
270
271Switch effective id of the process to user named C<$runas>
272
273=cut
274
275sub switch_user {
276    my ($runas) = @_;
277
278    if ($< == 0 || $> == 0) {
279        my @info = getpwnam($runas) or do {
280            warn "Can find user $runas";
281            return;
282        };
283        $> = $info[3];
284        return;
285    } else {
286        warn "we are not root";
287    }
288}
289
290=head2 run_via_sudo($runas)
291
292Rerun current programme as C<$runas> user using sudo
293
294=cut
295
296sub run_via_sudo {
297    my ($runas) = @_;
298
299    my @info = getpwnam($runas) or do {
300        warn "Can find user $runas";
301        return;
302    }; 
303    if ($< != $info[3]) {
304        exec('sudo', '-u', $runas, $0, @ARGV) or "Can run $!";
305    }
306}
307
308=head2 genpassword(%options)
309
310Generate a random password, options are:
311
312=over 4
313
314=item length
315
316The minimum password length (default is 6)
317
318=item nonalpha
319
320Include non alpha-numeric caracters
321
322=item syllables
323
324Use a set of syllables instead letter
325
326=item checkpassword
327
328A sub reference to check if generated password is valid
329
330=back
331
332=cut
333
334sub genpassword {
335    my (%options) = @_;
336
337    $options{checkpassword} ||= sub {
338        my ($cpass) = @_;
339        return fascist_check($cpass) eq 'ok';
340    };
341
342    my @non_alpha = (qw$; : / ! ( ) [ ] { } + = @ - " ' % * & . ? < >$, ',', '$');
343    my @letters = ('a' .. 'z', 'A' .. 'Z', 0 .. 9);
344
345    my @consonants = qw(b d f g j k l m n r s t v x z ch);
346    my @vowels = qw(a e i o u ou oi io ia iu);
347
348    $options{length} ||= 8 + int(rand(3));
349
350    while (1) {
351        if ($options{syllables}) {
352            $options{length} = int($options{length} / 2);
353            $options{length} = 3 if ($options{length} < 3);
354        } else {
355            $options{length} = 6 if ($options{length} < 6);
356        }
357
358        my @chars;
359
360        if ($options{nonalpha}) {
361            for(0 .. (0 + int(rand(3)))) {
362                push(@chars, (@non_alpha[rand(scalar(@non_alpha))]));
363            }
364        }
365
366        foreach (1 .. ($options{length} - scalar(@chars))) {
367            if ($options{syllables}) {
368                my $c = @consonants[rand(scalar(@consonants))];
369                my $v = @vowels[rand(scalar(@vowels))];
370                push(@chars, "$c$v");
371            } else {
372                push(@chars, (@letters[rand(scalar(@letters))]));
373            }
374        }
375
376
377        my $pass = join('', sort { rand() <=> rand() } @chars);
378        if (length($pass) >= 6 && $options{checkpassword}->($pass)) {
379            return $pass;
380        }
381    }
382}
383
384=head2 Crypt($password, $method)
385
386Build an encrypted password using standard crypt(), $method is the encrypted method to use:
387
388=over 4
389
390=item DES: the old DES method, do not use
391
392=item 1 or md5
393
394=item 5 or sha-256
395
396=item 6 or sha-512
397
398=back
399
400=cut
401
402sub Crypt {
403    my ($clearpassword, $method) = @_;
404
405    $method ||= '';
406    my $methNumber = {
407        'des'     => -1,
408        'md5'     =>  1,
409        'sha-256' =>  5,
410        'sha-512' =>  6,
411    }->{lc($method)} || $method || 1;
412
413
414    if ($methNumber > 0) {
415        # Good we're secure
416        my @salt_char = (('a' .. 'z'), ('A' .. 'Z'), (0 .. 9), '/', '.');
417        my $salt = join('', map { $salt_char[rand(scalar(@salt_char))] } (1 .. 8));
418
419        return crypt($clearpassword, '$' . $methNumber . '$' . $salt);
420    } else {
421        # Grumpf DES
422        my @salt_char = (('a' .. 'z'), ('A' .. 'Z'), (0 .. 9));
423        my $salt = join('', map { $salt_char[rand(scalar(@salt_char))] } (1,2));
424
425        return crypt($clearpassword, $salt);
426    }
427}
428
429=head2 buildLogin([$cb, ] @names)
430
431Try to find a proper login from @names.
432
433Optionnal $cb is a callback to check the solution over exiting data. If return false, another solution is tried
434
435Example:
436
437    buildLogin(sub { $_[0] ? 1 : 0 }, "sn", "givenName")
438
439=cut
440
441sub buildLogin {
442    my (@names) = @_;
443    my $cb = undef;
444
445    if (ref $names[0]) {
446        $cb = shift(@names);
447    } else {
448        $cb = sub { 1 }; # always validating
449    }
450
451    # Cleaning names:
452    foreach (@names) {
453        $_ ||= '';
454        $_ = lc(to_ascii($_));
455        s/[^\w]//g;
456    }
457
458    @names = grep { $_ } @names;
459    my $base = shift(@names) or return;
460    my $rest = join('', grep { $_ } @names);
461
462    if ((my $l = length($base)) > 10) {
463        my $len = 8;
464        while ($len < $l) {
465            my $try  = substr($base, 0, $len);
466            if ($cb->($try)) {
467                return $try;
468            }
469            $len++;
470        }
471    } elsif ($cb->($base)) {
472        return $base;
473    }
474
475    my $try = $base;
476    while (my $len = length($try) - length($base) + 1) {
477        if ($len > length($rest)) {
478            last;
479        }
480        $try = $base . substr($rest, 0, $len);
481        if ($cb->($try)) {
482            return $try;
483        }
484    }
485    return;
486}
487
488=head2 yesno($value, $default)
489
490Check is a paramter mean yes or no
491
492=cut
493
494sub yesno {
495    my ($value, $default) = @_;
496
497    if ($value =~ /^(yes|1|true)$/i) {
498        return 1;
499    }
500    if ($value =~ /^(no|0|false)$/i) {
501        return 0;
502    }
503    if ($value =~ /^\d+$/ and $value != 0) {
504        return 1;
505    }
506    return $default || $value;
507}
508
5091;
510
511__END__
512
513=head1 SEE ALSO
514
515L<sudo>
516
517=head1 AUTHOR
518
519Olivier Thauvin, E<lt>olivier.thauvin@aerov.jussieu.frE<gt>
520
521=head1 COPYRIGHT AND LICENSE
522
523Copyright (C) 2008, 2009, 2010, 2011, 2012 CNRS SA/CETP/LATMOS
524
525This library is free software; you can redistribute it and/or modify
526it under the same terms as Perl itself, either Perl version 5.10.0 or,
527at your option, any later version of Perl 5 you may have available.
528
529=cut
Note: See TracBrowser for help on using the repository browser.