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

Last change on this file since 2504 was 2504, checked in by nanardon, 2 years ago

add --insert to la-cli/create command

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