source: trunk/LATMOS-Accounts/lib/LATMOS/Accounts/Task/Buildnet.pm @ 2299

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

Ensure dhcp host are unique

File size: 33.8 KB
Line 
1package LATMOS::Accounts::Task::Buildnet;
2
3use strict;
4use warnings;
5use base qw(LATMOS::Accounts::Task);
6use LATMOS::Accounts::Log;
7use LATMOS::Accounts::Utils;
8use FindBin qw($Bin);
9use POSIX qw(strftime);
10use Net::IP;
11use File::Path;
12use File::Temp qw(tempfile);
13use Net::IPv4Addr;
14use Net::IPv6Addr;
15
16=head1 NAME
17
18LATMOS::Accounts::Task::Buildnet - Task to generate network configuration files
19
20=head1 DESCRIPTION
21
22This contains functions to generate network config file from C<Netzone> and
23C<Nethost> object.
24
25These config file can be:
26
27=over 4
28
29=item DNS zone files
30
31A standard DNS zone generated from a header and entries found in bases
32
33=item DNS reverse zone files
34
35A reverse DNS zone genarated from a header and entries found in bases
36
37=item A DHCP host list
38
39A file well formated host list to be included in dhcpd config file.
40
41=back
42
43=cut
44
45sub order { 2 }
46
47# Always try because depend also on files:
48
49sub runDelay { 10 * 60 }
50
51sub init {
52    my ($self) = @_;
53    my $LA = LATMOS::Accounts->new($self->{config}, noacl => 1);
54
55    $self->{_base} = $LA->base();
56    $self->{_base} && $self->{_base} or die "Cannot load base";
57    $self->{_base}->type eq 'sql' or die "This module work only with SQL base type\n";
58    $self->{_la} = $LA;
59}
60
61sub run {
62    my ($self) = @_;
63
64    if (my $cmd = $self->_la->val('_network_', 'pre')) {
65        exec_command(
66            $cmd,
67            {
68                TEMPLATE_DIR => $self->_la->val('_network_', 'template_dir', ''),
69                OUTPUT_DIR => $self->_la->val('_network_', 'output_dir', ''),
70                DIRECTORY => $self->_la->val('_network_', 'output_dir', ''),
71                HOOK_TYPE => 'PRE',
72            },
73        );
74    }
75
76    foreach my $zone ($self->_base->search_objects('netzone', 'exported=*')) {
77        my $ozone = $self->_base->get_object('netzone', $zone)
78            or next;
79        # check file need regeneration:
80        $self->_check_zone_need_update($ozone) or do {
81            la_log(LA_DEBUG, "No need to rebuild %s", $ozone->id);
82            next;
83        };
84        $self->gen_zone($zone) or do {
85            $self->_base->rollback;
86            return;
87        };
88        $self->_base->commit;
89    }
90
91    if (my $cmd = $self->_la->val('_network_', 'post')) {
92        exec_command(
93            $cmd,
94            {
95                TEMPLATE_DIR => $self->_la->val('_network_', 'template_dir', ''),
96                OUTPUT_DIR => $self->_la->val('_network_', 'output_dir', ''),
97                DIRECTORY => $self->_la->val('_network_', 'output_dir', ''),
98                HOOK_TYPE => 'POST',
99            },
100        );
101    }
102
103    1;
104}
105
106sub _la {
107    $_[0]->{_la}
108}
109
110sub _base {
111    my ($self) = @_;
112    return $self->{_base} if ($self->{_base});
113    my $base = $self->SUPER::base;
114    return $self->{_base} = $base
115}
116
117sub _bnet_state {
118    my ($self) = @_;
119    return $self->{_bnet_state} if ($self->{_bnet_state});
120    # where trace goes:
121    my $state_file =  $self->_la->val('_default_', 'state_dir', '/');
122    $state_file .= '/buildnet_state.ini';
123    la_log(LA_DEBUG, "Status file is %s", $state_file);
124    if ($state_file && ! -w $state_file) {
125        # don't exists, we have to create it
126        open(my $handle, '>', $state_file) or do {
127            la_log(LA_ERR, "Cannot open build net status file %s",
128                $state_file);
129            return;
130        };
131        print $handle "[_default_]\n";
132        close($handle);
133    }
134    $self->{_bnet_state} = Config::IniFiles->new(
135        -file => $state_file
136    );
137}
138
139sub _write_state_file {
140    la_log(LA_DEBUG, "Writting status file");
141    $_[0]->_bnet_state->RewriteConfig;
142}
143
144sub _template_file {
145    my ($self, $ozone) = @_;
146
147    my $template =  join('/', grep { $_ } $self->_la->val('_network_', 'template_dir'),
148        $ozone->get_attributes('templateD'));
149    la_log(LA_DEBUG, "Template for %s is %s", $ozone->id, $template);
150    $template;
151}
152
153sub _output_file {
154    my ($self, $ozone) = @_;
155
156    my $path = join(
157        '/',
158        $self->_la->val('_network_', 'output_dir',
159            ($self->_la->val('_default_', 'state_dir'), $ozone->get_attributes('type'))
160        )
161    );
162
163    if (! -d $path) {
164        la_log(LA_INFO, 'Creating directory %s', $path);
165        mkpath($path) or return;
166    }
167    my $output = join('/', $path, $ozone->get_attributes('outputD'));
168    la_log(LA_DEBUG, 'output file for %s is %s', $ozone->id, $output);
169    $output;
170}
171
172=head2 get_zone_rev ($zone)
173
174Return next zone revision (DNS).
175
176This revision is formated from date + incremental serial number. If day change,
177serial start to 1. If serial goes goes over 99, head build from date is
178increment.
179
180The code ensure returned number is always highter that current one.
181
182=cut
183
184sub get_zone_rev {
185    my ($self, $ozone) = @_;
186    my $date = strftime('%Y%m%d01', localtime);
187    my $oldrev = $ozone->get_attributes('zoneRevision') || 0;
188    my $rev;
189    if ($oldrev >= $date) {
190        # same date, increment subrev
191        $rev = $oldrev + 1;
192    } else {
193        # date has changed, subrev is 1
194        $rev = $date;
195    }
196    la_log(LA_DEBUG, 'new dns revision for %s is %s', $ozone->id, $rev);
197    $ozone->set_c_fields(zoneRevision => $rev) or do {
198        return;
199    };
200    $rev
201}
202
203
204sub _check_zone_need_update {
205    my ($self, $ozone) = @_;
206
207    # If env var is set, do it anyway
208    if ($ENV{LA_BNET_FORCE}) { return 1 }
209
210    if ($ozone->get_attributes('rev') >
211        $self->_bnet_state->val($ozone->id, 'dbrev', 0)) {
212        return 1;
213    }
214
215    return 1 if (! -f $self->_output_file($ozone));
216
217
218    if ($ozone->get_attributes('templateD')) {
219        my $template = $self->_template_file($ozone);
220        my $output = $self->_output_file($ozone);
221        my @tstat = stat($template) or return;
222        my @ostat = stat($output);
223        if (($ostat[9] || 0) <= ($tstat[9] || 0)) {
224            return 1;
225        }
226    }
227
228    return;
229}
230
231sub _set_last_build {
232    my ($self, $ozone) = @_;
233
234    my $lctime = scalar(localtime);
235    la_log(LA_DEBUG, 'Update last build for zone %s (%s)', $ozone->id, $lctime);
236    $ozone->set_c_fields('lastBuild' => $lctime);
237}
238
239sub _pre_zone {
240    my ($self, $ozone) = @_;
241
242    if (!$ozone->get_attributes('templateD')) {
243        la_log(LA_ERR, "No template file for zone %s, aborting", $ozone->id);
244        return;
245    }
246
247    my $textzone = $self->_comment_zone($ozone);
248    my $tzone = $self->_read_template($ozone);
249    if (defined($tzone)) {
250        $textzone .= $tzone;
251    } else {
252        return;
253    }
254
255    $self->_set_last_build($ozone);
256
257    return $textzone;
258}
259
260=head2 gen_zone($zone, $header)
261
262Generate zone file C<$zone> with header C<$header>
263
264=cut
265
266sub gen_zone {
267    my ($self, $zone) = @_;
268
269    my $timeS = time;
270    my $ozone = $self->_base->get_object('netzone', $zone)
271        or return;
272
273    la_log(LA_INFO, "Start building zone %s (%s)", $zone,
274        $ozone->get_attributes('type'));
275
276    my $header = $self->_pre_zone($ozone);
277
278    my $type = $ozone->get_attributes('type');
279    my $res =
280        $type eq 'dns'     ? $self->_gen_dns_zone($ozone, $header) :
281        $type eq 'reverse' ? $self->_gen_reverse_zone($ozone, $header) :
282        $type eq 'dhcp'    ? $self->_gen_dhcp_zone($ozone, $header) :
283        $type eq 'puppet'  ? $self->_gen_puppet_zone($ozone, $header) :
284        $type eq 'radius'  ? $self->_gen_radius_zone($ozone, $header) :
285        undef;
286
287    if (!defined($res)) {
288    }
289
290    my $textzone =
291        $header .
292        "\n" .
293        $self->_comment($ozone, "Comming from database:\n") .
294        $res .
295        $self->_comment($ozone, "End of data from database\n");
296
297
298    if ($type =~ /^(dns|reverse)$/) {
299        if (!$self->_checkzone_output($ozone, $textzone)) {
300            la_log(LA_ERR, "Output of DNS zone %s not ok, not updating this zone",
301                $ozone->id);
302            return;
303        }
304    }
305
306    if (open(my $handle, '>', $self->_output_file($ozone))) {
307        print $handle $textzone;
308        close($handle);
309        la_log(LA_INFO,
310            "zone %s written into %s (%d second)",
311            $ozone->id,
312            $self->_output_file($ozone),
313            time - $timeS,
314        );
315    } else {
316       la_log(LA_ERR, "Can't open output file for zone %s", $ozone->id);
317       return;
318    }
319
320    if (my $cmd = $self->_la->val('_network_', 'post_file',
321            $self->_la->val('_network_', 'post_zone'))) {
322        exec_command(
323            $cmd,
324            {
325                TEMPLATE_DIR => $self->_la->val('_network_', 'template_dir', ''),
326                OUTPUT_DIR => $self->_la->val('_network_', 'output_dir', ''),
327                DIRECTORY => $self->_la->val('_network_', 'output_dir', ''),
328                TEMPLATE_FILE => $ozone->get_attributes('templateD'),
329                OUTPUT_FILE => $ozone->get_attributes('outputD'),
330                HOOK_TYPE => 'POSTFILE',
331            },
332        );
333    }
334
335    $self->_bnet_state->newval($ozone->id, 'dbrev',
336        $ozone->get_attributes('rev'));
337    la_log LA_DEBUG, "Zone rev build point is %d for %s",
338    $ozone->get_attributes('rev'),
339    $ozone->id;
340    $self->_bnet_state->SetParameterComment(
341        $ozone->id, 'dbrev',
342        scalar(localtime));
343    $self->_write_state_file;
344
345    1;
346}
347
348sub _checkzone_output {
349    my ($self, $ozone, $output) = @_;
350
351    if (!$self->_la->val('_network_', 'checkzone')) {
352        return 1;
353    }
354
355    my ($fh, $filename) = tempfile();
356
357    print $fh $output;
358    close($fh);
359
360    my $named_checkzone = $self->_la->val('_network_', 'named-checkzone',
361        '/usr/sbin/named-checkzone');
362
363    my $msg;
364    my $res = exec_command(sprintf(
365            "%s -k fail '%s' '%s'",
366            $named_checkzone,
367            $ozone->id,
368            $filename,
369        ), undef, $msg);
370    if (!$res) {
371        la_log(LA_ERR, "Error on zone %s: ", $ozone->id);
372        la_log(LA_ERR, "  msg: $_") foreach (split(/\n/, $msg));
373    } else {
374        unlink($filename);
375    }
376    $res
377}
378
379sub _comment {
380    my ($self, $ozone, $message, @args) = @_;
381    my $com_prefix =
382        $ozone->get_attributes('type') =~ /^(dhcp|puppet|radius)$/ ? '#' : ';';
383
384    if ($message) {
385        return(sprintf("$com_prefix $message", @args));
386    } else {
387        $com_prefix
388    }
389}
390
391sub _comment_zone {
392    my ($self, $ozone) = @_;
393
394    my @output = ();
395    my $com_prefix =
396        $ozone->get_attributes('type') =~ /^(dhcp|puppet|radius)$/ ? '# ' : '; ';
397    push @output, sprintf('Zone %s, type %s', $ozone->id,
398        $ozone->get_attributes('type'));
399    push @output, $ozone->get_attributes('description')
400        if ($ozone->get_attributes('description'));
401    push @output, sprintf('Generated by %s', q$Id: BuildNet.pm 6283 2011-05-20 10:16:51Z nanardon $ );
402    push @output, sprintf('          the %s', scalar(localtime) );
403    push @output, sprintf('Network: %s', join(', ', $ozone->get_attributes('net')))
404        if ($ozone->get_attributes('net'));
405    push @output, sprintf('Exclude Network: %s', join(', ',
406            $ozone->get_attributes('netExclude')))
407        if ($ozone->get_attributes('netExclude'));
408    if ($ozone->get_attributes('type') =~ /^(dhcp|radius)$/) {
409        my @dynFrom = grep { $_ } $ozone->get_attributes('dynFrom');
410        push(@output, sprintf('This zone include host from zone: %s', join(', ', sort @dynFrom)))
411            if (@dynFrom);
412        push(@output, 'This zone include dynamic IP address')
413            if ($ozone->get_attributes('allow_dyn'));
414    }
415
416    return to_ascii(join('', map { $com_prefix . $_ . "\n" } @output) . "\n");
417}
418
419sub _comment_nethost {
420    my ($self, $nethost) = @_;
421
422    my $displayeduser;
423    if (my $owner = $nethost->get_attributes('user')) {
424        if (my $user = $self->_base->get_object('user', $owner)) {
425            $displayeduser = $user->get_attributes('displayName');
426        }
427    }
428    if ((!$displayeduser) && (my $owner = $nethost->get_attributes('owner'))) {
429        if (my $user = $self->_base->get_object('user', $owner)) {
430            $displayeduser = $user->get_attributes('displayName');
431        }
432    }
433    my @desc = (
434        $displayeduser,
435        $nethost->get_attributes('site'),
436        $nethost->get_attributes('physicalDeliveryOfficeName'),
437        $nethost->get_attributes('description'),
438    );
439
440    return to_ascii(join(', ', grep { $_ } @desc) || '');
441}
442
443
444# return undef on fatal error, depending zone type
445sub _read_template {
446    my ($self, $ozone) = @_;
447
448    my $revision = $self->get_zone_rev($ozone) or return;
449    if (open(my $handle, '<', $self->_template_file($ozone))) {
450        my $textzone = '';
451        while (my $line = <$handle>) {
452            $line =~ s/(\d+\s*;\s*)?\@REVISION@/$revision/;
453            $textzone .= $line;
454        }
455        close($handle);
456        return $textzone;
457    } else {
458        if ($ozone->get_attributes('type') =~ /^(dns|reverse)$/) {
459            la_log(LA_ERR, "Can't open template file for zone %s: %s", $ozone->id, $!);
460            return;
461        } else {
462            return '';
463        }
464    }
465}
466
467sub _gen_dns_zone {
468    my ($self, $ozone) = @_;
469
470    my $dbzone = '';
471    if ($ozone->get_attributes('net')) {
472        my $findhost = $self->_base->db->prepare_cached(q{
473            select name, value::inet as value from nethost join nethost_attributes_ips on
474            nethost.ikey = nethost_attributes_ips.okey
475            where value::inet <<= any(?) and exported = true
476            except
477            select name, value::inet from nethost join nethost_attributes_ips on
478            nethost.ikey = nethost_attributes_ips.okey
479            where value::inet <<= any(?)
480            order by value, name
481        });
482        $findhost->execute(
483            [ $ozone->get_attributes('net') ],
484            [ $ozone->get_attributes('netExclude') ],
485        ) or do {
486            la_log LA_ERR, "Cannot fetch host list: %s",
487                $self->_base->db->errstr;
488            return;
489        };
490        my %lists;
491        my %names;
492        # Storing all name in %names to check later if CNAME does not conflict
493        while (my $res = $findhost->fetchrow_hashref) {
494            $lists{$res->{name}} ||= {};
495            push(@{$lists{$res->{name}}{ip}}, $res->{value});
496            my $host_o = $self->_base->get_object('nethost', $res->{name});
497            foreach (grep { $_ } $host_o->get_attributes('otherName')) {
498                $names{$_} = 1;
499            }
500        }
501
502        foreach my $res (sort keys %lists) {
503            my $host_o = $self->_base->get_object('nethost', $res) or do {
504                la_log LA_ERR, "Cannot fetch host %s", $res->{name};
505                return;
506            };
507            my $desc = $self->_comment_nethost($host_o);
508            $dbzone .= $desc
509                ? '; ' . $desc . "\n"
510                : '';
511            foreach my $ip (@{$lists{$res}{ip}}) {
512                $dbzone .= sprintf(
513                    "%-30s IN    %-4s     %s\n",
514                    $res,
515                    ($ip =~ /:/ ? 'AAAA' : 'A'),
516                    $ip
517                );
518            }
519            foreach (grep { $_ } $host_o->get_attributes('otherName')) {
520                foreach my $ip (@{$lists{$res}{ip}}) {
521                    $dbzone .= sprintf(
522                        "%-30s IN    %-4s     %s\n",
523                        $_,
524                        ($ip =~ /:/ ? 'AAAA' : 'A'),
525                        $ip
526                    );
527                }
528            }
529            foreach (grep { $_ } $host_o->get_attributes('cname')) {
530                # It is deny to have both:
531                # foo IN A
532                # foo IN CNAME
533                if ($names{$_}) {
534                    my $msg .= sprintf(
535                        'Cname %s to %s exclude because %s is already an A record',
536                        $_, $res, $_
537                    );
538                    la_log(LA_ERR, sprintf("$msg (zone %s)", $ozone->id));
539                    $dbzone .= "; $msg\n";
540                } else {
541                    $dbzone .= sprintf("%-30s IN    CNAME    %s\n", $_, $res,);
542                }
543            }
544            # SSH finger print
545            foreach my $name (grep { $_ } ($res, $host_o->get_attributes('otherName'))) {
546                foreach my $sshfp (grep { $_ } $host_o->get_attributes('sshfp')) {
547                    $dbzone .= sprintf(
548                        "%-30s IN    SSHFP    %s\n",
549                        $name,
550                        $sshfp,
551                    );
552                }
553            }
554        }
555    }
556
557    return $dbzone;
558}
559
560sub _gen_reverse_zone {
561    my ($self, $ozone) = @_;
562
563    my $domain = $ozone->get_attributes('domain') || '';
564    my $dbzone = '';
565    if ($ozone->get_attributes('net')) {
566        my $findhost = $self->_base->db->prepare_cached(q{
567            select * from (
568            select * from nethost join nethost_attributes_ips on
569            nethost.ikey = nethost_attributes_ips.okey
570            where value::inet <<= ? and exported = true
571            except
572            select * from nethost join nethost_attributes_ips on
573            nethost.ikey = nethost_attributes_ips.okey
574            where value::inet <<= any(?)
575            ) as q
576            order by value::inet
577
578        });
579        $findhost->execute(
580            $ozone->get_attributes('net'),
581            [ $ozone->get_attributes('netExclude') ],
582        ) or do {
583            la_log LA_ERR, "Cannot fetch host list: %s",
584                $self->_base->db->errstr;
585            return;
586        };
587
588        # reverse is complicated:
589        my ($net) = $ozone->get_attributes('net') or do {
590        $self->base->log('Cannot fetch attribute "net" for zone %s', $ozone->id);
591        return;
592    };
593        my $netip = Net::IP->new($net) or do {
594                $self->base->log(LA_ERR, "Cannot build reverse zone %s: wrong net %s", $ozone->id, $net);
595                return;
596        };
597        my $mask = $netip->prefixlen;
598
599        while (my $res = $findhost->fetchrow_hashref) {
600            my $host_o = $self->_base->get_object('nethost', $res->{name}) or do {
601                la_log LA_ERR, "Cannot fetch host %s", $res->{name};
602                return;
603            };
604            my $desc = $self->_comment_nethost($host_o);
605            my $reverse = $host_o->get_attributes('reverse') || ($res->{name} . '.');
606            $dbzone .= $desc
607                ? '; ' . $desc . "\n"
608                : '';
609            my $revip;
610            my $fmt;
611            if ($res->{value} =~ /:/) {
612                # IPv6
613                my $m = $mask/4;
614                $revip = Net::IPv6Addr->new($res->{value})->to_string_ip6_int;
615                $revip =~ s/\.([0-9,a-f]\.?){$m}\.IP6\.INT\.$//i;
616                $fmt = "%-72s IN    PTR    %s%s\n";
617            } else {
618                # ipv4
619                if ($mask > 24) {
620                    $self->_base->log(LA_ERR, 'Mask for zone %s cannot be %d', $ozone->id, $mask);
621                    return;
622                }
623                my @ippart = split(/\./, $res->{value});
624                splice(@ippart, 0, $mask/8); # get rid of start of ip
625                my @nippart;
626                while (@ippart) { unshift(@nippart, shift(@ippart)) }
627                $revip = join('.', @nippart);
628                $fmt = "%-12s IN    PTR    %s%s\n";
629            }
630 
631            $dbzone .= sprintf($fmt, $revip,
632                $reverse =~ /\.$/
633                    ? ($reverse, ($domain ? "$domain." : '.'))
634                    : ($reverse, '.'));
635        }
636    }
637
638    return $dbzone;
639}
640
641sub _gen_dhcp_zone {
642    my ($self, $ozone) = @_;
643
644    my $outzone = $ozone;
645    my $output = '';
646
647    my $ZoneId = sprintf('%04x', $outzone->get_attributes('ikey'));
648
649    my @net;
650    if ($outzone->get_attributes('net')) {
651        @net = (map { Net::IP->new($_) } $outzone->get_attributes('net')) or do {
652            la_log(LA_DEBUG, 'Cannot get Net::IP for zone %s (ip: %s)', $outzone->id,
653                join(', ', $outzone->get_attributes('net')));
654            next;
655        };
656    }
657
658    {
659        my $find = $self->_base->db->prepare(q{
660            select * from nethost where exported = true and ikey in(
661            select okey from nethost_attributes where attr = 'macaddr'
662            intersect (
663                select nethost_attributes_ips.okey from nethost_attributes_ips join
664                netzone_attributes
665                on netzone_attributes.attr = 'net' and
666                netzone_attributes.value::inet >>= nethost_attributes_ips.value::inet
667                join netzone on netzone.ikey = netzone_attributes.okey
668                where netzone.name = $1
669
670                except
671                select nethost_attributes_ips.okey from nethost_attributes_ips join
672                netzone_attributes
673                on netzone_attributes.attr = 'netExclude' and
674                netzone_attributes.value::inet >>= nethost_attributes_ips.value::inet
675                join netzone on netzone.ikey = netzone_attributes.okey
676                where netzone.name = $1
677                )
678            )
679            order by name
680
681            });
682        $find->execute($ozone->id) or do {
683            la_log LA_ERR, "Cannot fetch host list: %s",
684                $self->_base->db->errstr;
685            return;
686        };
687        while (my $res = $find->fetchrow_hashref) {
688            my $nethost = $res->{name};
689
690            my $obj = $self->_base->get_object('nethost', $nethost) or do {
691                la_log LA_ERR, "Cannot fetch host %s", $res->{name};
692                return;
693            };
694
695            my @retainip;
696            if (@net) {
697                foreach my $inet (@net) {
698                    push(@retainip, grep { $_ && $inet->overlaps(Net::IP->new($_)) } $obj->get_attributes('ip'));
699                }
700            }
701
702            $obj->get_attributes('noDynamic') && !@retainip and next;
703
704            my $desc = $self->_comment_nethost($obj);
705            foreach my $mac (sort grep { $_ } $obj->get_attributes('macaddr')) {
706                my %done;
707                foreach my $retainip (sort @retainip) {
708                    $output .= $desc
709                       ? '# ' . $desc . "\n"
710                        : '';
711                    my ($fixedparam, $label, $netLockIp) = $retainip =~ /^\d+(\.\d+){3}$/
712                        ? ('fixed-address', '', '127.0.0.1')
713                        : ('fixed-address6', '-6', '::1');
714                    # If two ip are given for same protocol keep only first
715                    $done{$fixedparam} and next;
716                    $done{$fixedparam} = 1;
717                    my $fmac = $mac;
718                    $fmac =~ s/://g;
719                    $output .= sprintf("host %s-%s-%s%s {\n", $nethost, lc($fmac), $ZoneId, $label);
720                    $output .= sprintf("    hardware ethernet %s;\n", $mac);
721                    if ($obj->get_attributes('netLocked')) {
722                        $output .= sprintf("    $fixedparam %s;\n", $netLockIp);
723                    } else {
724                        $output .= sprintf("    $fixedparam %s;\n", $retainip)
725                            if ($retainip);
726                    }
727                    $output .= "}\n\n";
728                }
729            }
730        }
731    }
732    if ($ozone->get_attributes('allow_dyn')) {
733        $output .= "\n# Host without IP:\n";
734        my @dynfrom = grep { $_ } $ozone->get_attributes('dynFrom');
735        my $find = $self->_base->db->prepare(q{
736            select * from nethost where exported = true and ikey in(
737            select okey from nethost_attributes where attr = 'macaddr'
738            } . (@dynfrom ? q{
739            intersect
740            (
741                select ikey from nethost where ikey not in
742                    (select okey from nethost_attributes_ips)
743                union
744
745                (
746                select nethost_attributes_ips.okey from nethost_attributes_ips join
747                netzone_attributes
748                on netzone_attributes.attr = 'net' and
749                   netzone_attributes.value::inet >>=
750                   nethost_attributes_ips.value::inet
751                   join netzone on netzone.ikey = netzone_attributes.okey
752                   where netzone.name = any(?)
753                except
754                select nethost_attributes_ips.okey from nethost_attributes_ips join
755                netzone_attributes
756                on netzone_attributes.attr = 'netExclude' and
757                   netzone_attributes.value::inet >>=
758                   nethost_attributes_ips.value::inet
759                   join netzone on netzone.ikey = netzone_attributes.okey
760                   where netzone.name = any(?)
761                )
762            )} : '') . q{
763            except
764            select nethost_attributes_ips.okey from nethost_attributes_ips join
765            netzone_attributes
766            on netzone_attributes.attr = 'net' and
767            netzone_attributes.value::inet >>= nethost_attributes_ips.value::inet
768            join netzone on netzone.ikey = netzone_attributes.okey
769            where netzone.name = ?
770            )
771            order by name
772
773            });
774        $find->execute((@dynfrom ? ([ @dynfrom ], [ @dynfrom ]) : ()), $ozone->id) or do {
775            la_log LA_ERR, "Cannot fetch host list: %s",
776                $self->_base->db->errstr;
777            return;
778        };
779        while (my $res = $find->fetchrow_hashref) {
780            my $nethost = $res->{name};
781
782            my $obj = $self->_base->get_object('nethost', $nethost);
783
784            $obj->get_attributes('noDynamic') and next;
785
786            my $desc = $self->_comment_nethost($obj);
787            foreach my $mac (sort grep { $_ } $obj->get_attributes('macaddr')) {
788                $output .= $desc
789                    ? '# ' . $desc . "\n"
790                    : '';
791                my $fmac = $mac;
792                $fmac =~ s/://g;
793                if ($obj->get_attributes('netLocked')) {
794                    my ($ipv4, $ipv6);
795                    foreach ($ozone->get_attributes('net')) {
796                        if ($_ =~ /^\d+(\.\d+){3}\/(\d+)$/) {
797                            $ipv4 = 1;
798                        } else {
799                            $ipv6 = 1;
800                        }
801                    }
802
803                    if ($ipv4) {
804                        $output .= sprintf("host %s-%s-%s {\n", $nethost, lc($fmac), $ZoneId);
805                        $output .= sprintf("    hardware ethernet %s;\n", $mac);
806                        $output .= sprintf("    fixed-address 127.0.0.1;\n");
807                        $output .= "}\n\n";
808                    }
809                    if ($ipv6) {
810                        $output .= sprintf("host %s-%s-%s {\n", $nethost, lc($fmac), $ZoneId);
811                        $output .= sprintf("    hardware ethernet %s;\n", $mac);
812                        $output .= sprintf("    fixed-address6 ::1\n");
813                        $output .= "}\n\n";
814                    }
815                } else {
816                    $output .= sprintf("host %s-%s-%s {\n", $nethost, lc($fmac), $ZoneId);
817                    $output .= sprintf("    hardware ethernet %s;\n", $mac);
818                    $output .= "}\n\n";
819                }
820            }
821        }
822    }
823
824    $output
825}
826
827sub _gen_radius_zone {
828    my ($self, $ozone) = @_;
829
830    my $outzone = $ozone;
831    my $output = '';
832
833    my @net;
834    if ($outzone->get_attributes('net')) {
835        @net = (map { Net::IP->new($_) } $outzone->get_attributes('net')) or do {
836            la_log(LA_DEBUG, 'Cannot get Net::IP for zone %s (ip: %s)', $outzone->id,
837                join(', ', $outzone->get_attributes('net')));
838            next;
839        };
840    }
841
842    my %hosts;
843
844    {
845        my $find = $self->_base->db->prepare(q{
846            select * from nethost where exported = true and ikey in(
847            select okey from nethost_attributes where attr = 'macaddr'
848            intersect (
849                select nethost_attributes_ips.okey from nethost_attributes_ips join
850                netzone_attributes
851                on netzone_attributes.attr = 'net' and
852                netzone_attributes.value::inet >>= nethost_attributes_ips.value::inet
853                join netzone on netzone.ikey = netzone_attributes.okey
854                where netzone.name = $1
855
856                except
857                select nethost_attributes_ips.okey from nethost_attributes_ips join
858                netzone_attributes
859                on netzone_attributes.attr = 'netExclude' and
860                netzone_attributes.value::inet >>= nethost_attributes_ips.value::inet
861                join netzone on netzone.ikey = netzone_attributes.okey
862                where netzone.name = $1
863                )
864            )
865            order by name
866
867            });
868        $find->execute($ozone->id) or do {
869            la_log LA_ERR, "Cannot fetch host list: %s",
870                $self->_base->db->errstr;
871            return;
872        };
873        while (my $res = $find->fetchrow_hashref) {
874            $hosts{ $res->{name} } = 1;
875        }
876    }
877    if ($ozone->get_attributes('allow_dyn')) {
878        my @dynfrom = grep { $_ } $ozone->get_attributes('dynFrom');
879        my $find = $self->_base->db->prepare(q{
880            select * from nethost where exported = true and ikey in(
881            select okey from nethost_attributes where attr = 'macaddr'
882            } . (@dynfrom ? q{
883            intersect
884            (
885                select ikey from nethost where ikey not in
886                    (select okey from nethost_attributes_ips)
887                union
888
889                (
890                select nethost_attributes_ips.okey from nethost_attributes_ips join
891                netzone_attributes
892                on netzone_attributes.attr = 'net' and
893                   netzone_attributes.value::inet >>=
894                   nethost_attributes_ips.value::inet
895                   join netzone on netzone.ikey = netzone_attributes.okey
896                   where netzone.name = any(?)
897                except
898                select nethost_attributes_ips.okey from nethost_attributes_ips join
899                netzone_attributes
900                on netzone_attributes.attr = 'netExclude' and
901                   netzone_attributes.value::inet >>=
902                   nethost_attributes_ips.value::inet
903                   join netzone on netzone.ikey = netzone_attributes.okey
904                   where netzone.name = any(?)
905                )
906            )} : '') . q{
907            except
908            select nethost_attributes_ips.okey from nethost_attributes_ips join
909            netzone_attributes
910            on netzone_attributes.attr = 'net' and
911            netzone_attributes.value::inet >>= nethost_attributes_ips.value::inet
912            join netzone on netzone.ikey = netzone_attributes.okey
913            where netzone.name = ?
914            )
915            order by name
916
917            });
918        $find->execute((@dynfrom ? ([ @dynfrom ], [ @dynfrom ]) : ()), $ozone->id) or do {
919            la_log LA_ERR, "Cannot fetch host list: %s",
920                $self->_base->db->errstr;
921            return;
922        };
923        while (my $res = $find->fetchrow_hashref) {
924            $hosts{ $res->{name} } = 1;
925        }
926    }
927
928    my @parameters = grep { $_ } $outzone->get_attributes('hostParams');
929    foreach my $nethost (sort keys %hosts) {
930        my $obj = $self->_base->get_object('nethost', $nethost);
931
932        $obj->get_attributes('netLocked') and next;
933        $obj->get_attributes('noDynamic') and next;
934
935        $output .= "# Host: $nethost\n";
936        my $desc = $self->_comment_nethost($obj);
937        $output .= $desc
938            ? '# ' . $desc . "\n"
939            : '';
940        foreach my $mac (sort grep { $_ } $obj->get_attributes('macaddr')) {
941            my $fmac = $mac;
942            $fmac =~ s/://g;
943            $output .= sprintf("%s ClearText-Password := \"%s\"\n", lc($fmac), lc($fmac));
944            $output .= join (",\n", map { "    $_" } @parameters) . "\n" if(@parameters);
945            $output .= "\n";
946        }
947        $output .= "\n";
948    }
949
950    $output;
951}
952
953sub _gen_puppet_zone {
954    my ($self, $ozone) = @_;
955
956    my $output = '';
957
958    if ($ozone->get_attributes('net')) {
959        my $findhost = $self->_base->db->prepare_cached(q{
960            select * from (
961            select * from nethost join nethost_attributes_ips on
962            nethost.ikey = nethost_attributes_ips.okey
963            where value::inet <<= ? and exported = true
964            except
965            select * from nethost join nethost_attributes_ips on
966            nethost.ikey = nethost_attributes_ips.okey
967            where value::inet <<= any(?)
968            ) as q
969            order by value::inet
970
971        });
972        $findhost->execute(
973            $ozone->get_attributes('net'),
974            [ $ozone->get_attributes('netExclude') ],
975        ) or do {
976            la_log LA_ERR, "Cannot fetch host list: %s",
977                $self->_base->db->errstr;
978            return;
979        };
980
981        my @puppetclasses = $ozone->get_attributes('puppetClass');
982        while (my $res = $findhost->fetchrow_hashref) {
983            my $nethost = $res->{name};
984
985            my $obj = $self->_base->get_object('nethost', $nethost) or do {
986                la_log LA_ERR, "Cannot fetch host %s", $res->{name};
987                return;
988            };
989
990            # merging classes from host and zone
991            my %classes = map { $_ => 1 } grep { $_ } (
992                $obj->get_attributes('puppetClass'),
993                ($obj->get_attributes('noInheritPuppet') ? () : @puppetclasses),
994            );
995            my $desc = $self->_comment_nethost($obj);
996            $output .= sprintf("node '%s' {\n", $obj->id);
997            $output .= $desc
998                ? sprintf("    # %s\n", $desc)
999                : '';
1000            $output .= join('', map { "    class '$_'\n" } sort keys %classes);
1001            $output .= "}\n\n";
1002        }
1003    }
1004
1005    $output
1006}
1007
1008sub reset_savepoint {
1009    my ($self) = @_;
1010    foreach my $zone ($self->_base->search_objects('netzone')) {
1011        my $ozone = $self->_base->get_object('netzone', $zone)
1012            or next;
1013
1014        $self->_bnet_state->newval($ozone->id, 'dbrev', 0);
1015        la_log LA_DEBUG, "Zone savepoint reset for %s", $ozone->id;
1016        $self->_bnet_state->SetParameterComment(
1017            $ozone->id, 'dbrev',
1018            'Reset savepoint the ' . scalar(localtime));
1019    }
1020
1021    $self->_write_state_file;
1022}
1023
10241;
1025
1026__END__
1027
1028=head1 AUTHOR
1029
1030Olivier Thauvin, E<lt>olivier.thauvin@latmos.ipsl.frE<gt>
1031
1032=head1 COPYRIGHT AND LICENSE
1033
1034Copyright (C) 2008, 2009, 2010, 2011, 2012 CNRS SA/CETP/LATMOS
1035
1036This library is free software; you can redistribute it and/or modify
1037it under the same terms as Perl itself, either Perl version 5.10.0 or,
1038at your option, any later version of Perl 5 you may have available.
1039
1040=cut
Note: See TracBrowser for help on using the repository browser.