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

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

Limit sAMAccountName to 19 caracters

  • Property svn:keywords set to Id Rev
File size: 11.4 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        # 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=back
327
328=cut
329
330sub genpassword {
331    my (%options) = @_;
332    my @non_alpha = (qw$; : / ! ( ) [ ] { } + = @ - " ' % * & . ? < >$, ',', '$');
333    my @letters = ('a' .. 'z', 'A' .. 'Z', 0 .. 9);
334
335    my @consonants = qw(b d f g j k l m n r s t v x z ch);
336    my @vowels = qw(a e i o u ou oi io ia iu);
337
338    $options{length} ||= 8 + int(rand(3));
339
340    while (1) {
341        if ($options{syllables}) {
342            $options{length} = int($options{length} / 2);
343            $options{length} = 3 if ($options{length} < 3);
344        } else {
345            $options{length} = 6 if ($options{length} < 6);
346        }
347
348        my @chars;
349
350        if ($options{nonalpha}) {
351            for(0 .. (0 + int(rand(3)))) {
352                push(@chars, (@non_alpha[rand(scalar(@non_alpha))]));
353            }
354        }
355
356        foreach (1 .. ($options{length} - scalar(@chars))) {
357            if ($options{syllables}) {
358                my $c = @consonants[rand(scalar(@consonants))];
359                my $v = @vowels[rand(scalar(@vowels))];
360                push(@chars, "$c$v");
361            } else {
362                push(@chars, (@letters[rand(scalar(@letters))]));
363            }
364        }
365
366
367        my $pass = join('', sort { rand() <=> rand() } @chars);
368        if (length($pass) >= 6 && fascist_check($pass) eq 'ok') {
369            return $pass;
370        }
371    }
372}
373
374=head2 Crypt($password, $method)
375
376Build an encrypted password using standard crypt(), $method is the encrypted method to use:
377
378=over 4
379
380=item DES: the old DES method, do not use
381
382=item 1 or md5
383
384=item 5 or sha-256
385
386=item 6 or sha-512
387
388=back
389
390=cut
391
392sub Crypt {
393    my ($clearpassword, $method) = @_;
394
395    $method ||= '';
396    my $methNumber = {
397        'des'     => -1,
398        'md5'     =>  1,
399        'sha-256' =>  5,
400        'sha-512' =>  6,
401    }->{lc($method)} || $method || 1;
402
403
404    if ($methNumber > 0) {
405        # Good we're secure
406        my @salt_char = (('a' .. 'z'), ('A' .. 'Z'), (0 .. 9), '/', '.');
407        my $salt = join('', map { $salt_char[rand(scalar(@salt_char))] } (1 .. 8));
408
409        return crypt($clearpassword, '$' . $methNumber . '$' . $salt);
410    } else {
411        # Grumpf DES
412        my @salt_char = (('a' .. 'z'), ('A' .. 'Z'), (0 .. 9));
413        my $salt = join('', map { $salt_char[rand(scalar(@salt_char))] } (1,2));
414
415        return crypt($clearpassword, $salt);
416    }
417}
418
419=head2 buildLogin([$cb, ] @names)
420
421Try to find a proper login from @names.
422
423Optionnal $cb is a callback to check the solution over exiting data. If return false, another solution is tried
424
425Example:
426
427    buildLogin(sub { $_[0] ? 1 : 0 }, "sn", "givenName")
428
429=cut
430
431sub buildLogin {
432    my (@names) = @_;
433    my $cb = undef;
434
435    if (ref $names[0]) {
436        $cb = shift(@names);
437    } else {
438        $cb = sub { 1 }; # always validating
439    }
440
441    # Cleaning names:
442    foreach (@names) {
443        $_ ||= '';
444        $_ = lc(to_ascii($_));
445        s/[^\w]//g;
446    }
447
448    @names = grep { $_ } @names;
449    my $base = shift(@names) or return;
450    my $rest = join('', grep { $_ } @names);
451
452    if ((my $l = length($base)) > 10) {
453        my $len = 8;
454        while ($len < $l) {
455            my $try  = substr($base, 0, $len);
456            if ($cb->($try)) {
457                return $try;
458            }
459            $len++;
460        }
461    } elsif ($cb->($base)) {
462        return $base;
463    }
464
465    my $try = $base;
466    while (my $len = length($try) - length($base) + 1) {
467        if ($len > length($rest)) {
468            last;
469        }
470        $try = $base . substr($rest, 0, $len);
471        if ($cb->($try)) {
472            return $try;
473        }
474    }
475    return;
476}
477
478=head2 yesno($value, $default)
479
480Check is a paramter mean yes or no
481
482=cut
483
484sub yesno {
485    my ($value, $default) = @_;
486
487    if ($value =~ /^(yes|1|true)$/i) {
488        return 1;
489    }
490    if ($value =~ /^(no|0|false)$/i) {
491        return 0;
492    }
493    if ($value =~ /^\d+$/ and $value != 0) {
494        return 1;
495    }
496    return $default || $value;
497}
498
4991;
500
501__END__
502
503=head1 SEE ALSO
504
505L<sudo>
506
507=head1 AUTHOR
508
509Olivier Thauvin, E<lt>olivier.thauvin@aerov.jussieu.frE<gt>
510
511=head1 COPYRIGHT AND LICENSE
512
513Copyright (C) 2008, 2009, 2010, 2011, 2012 CNRS SA/CETP/LATMOS
514
515This library is free software; you can redistribute it and/or modify
516it under the same terms as Perl itself, either Perl version 5.10.0 or,
517at your option, any later version of Perl 5 you may have available.
518
519=cut
Note: See TracBrowser for help on using the repository browser.