1

i have some config files (exports from firewall reports in fact) using that kind of structure :

 policies {
        apply-groups default-log;
        from-zone Trust to-zone DMZ {
            policy policy-66 {
                match {
                    source-address g_DMZ_SRV_;
                    destination-address g_DMZ_SRV;
                    application any;
                }
                then {
                    permit;
                }
            }
            policy policy-9 {
                match {
                    source-address g_h_OpenMail-Server;
                    destination-address g_in_DMZ_Exchange;
                    application t_1023;
                }
                then {
                    permit;
                }
            }
        }
        from-zone DMZ to-zone Blabla {
            policy policy-68 {
                match {
                    source-address g_DMZ_SRV_2_;
                    destination-address g_DMZ_SRV_3;
                    application T_22-ssh;
                }
                then {
                    permit;
                }
            }
            policy policy-95 {
                match {
                    source-address g_h_OpenMail-Server-2;
                    source-address 1.2.0.3;
                    destination-address g_in_DMZ_Exchange-1;
                    destination-address 10.25.32.64;
                    application t_1024;
                }
                then {
                    permit;
                }
            }
        }

}

and I would like to parse it in Perl, in order to build a hash for example (or simply put conditions to treat the data) that I could exploit afterwards, e.g something like:

Trust-to-DMZ
      policy-66
            source => g_DMZ_SRV
            destination => blabla
      policy-44
            source => source1
                      source2
                      source3
            destination => dest1
            ports => port1
DMZ-to-Trust
      policy-XX

i wanted wanted to know :

  1. if you knew some modules helpîng in such a task (i suppose i could use Text::Balanced, i found some examples in a few other posts)

  2. if there was some methods/best practices in doing this to avoid dirty work ?

i suppose i could "count" the number of braces and do loops in loops.. but it would be dirty.

isn't there an easier solution or module doing this automatically ? (like modules exist for XML files for example, XML::Simple putting the contents of an XML into a hash, i would expect something similar for that kind of stuff ?)

otherwise i'll start coding something dirty and post my progress here

thanks!

edit on June 8th, just so you know, it works with a dirty dirty dirty code like that (I'm not a developer, apologies), which is not really what I want as it's not adaptable.. and clearly dirty as hell you have been warned! :) so don't look at it if you don't want blood in your eyes

use warnings;
use lib '/opt/csm/64-bit/cpan/5.16.3-2013.03/lib';
use Data::Dumper;

my ( $policies_flag, $fromzone_flag, $policy_flag, $match_flag, $zone_flag ) = ( 0,0,0,0,0 );
my ( $details_flag, $clos_flag, $then_flag, $permit_flag, $clos2_flag, $final_flag ) = ( 0,0,0,0,0,0 );

my $fromzone;
my $tozone;

my %pols;
my $clos_counter;

die "Usage: $0 <path_to_file>" if $ARGV[0] eq '';

open D, '<', $ARGV[0] or die "cannot open $ARGV[0] for read\n";
@data = <D>;
close D;


OUTER: foreach my $str (@data) {

     next if $str =~ /^$/;
     next if $str =~ /apply-groups/;
     chomp $str;

if ( $str =~ /\s*policies\s+\{/ ) {
        $policies_flag = 1;
        next OUTER;
}


# policies
if ($policies_flag == 1) {

    if ($str =~ /from-zone\s\S+\sto-zone\s\S+\s\{$/) {
        next if $str =~ /(<|>)/;
        ( $fromzone, $tozone ) = ( split(/\s+/,$str) )[2,4];
        $fromzone_flag = 1;
        next OUTER;
    }

    # from-zone
    if ($fromzone_flag == 1) {

        if ($str =~ /policy\s+\S+\s+\{/) {
            $policy_flag = 1;
            $clos_counter=0;
            ( $policy_name ) = ( split(/\s+/, $str) )[2];
            $pols{$policy_name}{from_zone} = "$fromzone";
            $pols{$policy_name}{to_zone} = "$tozone";
            next OUTER;
        }

        # pol
        if ($policy_flag == 1) {

            if ($str =~ /match\s+\{/) {
                $match_flag = 1;
                next OUTER;
            }

        }

        # match
        if ($match_flag == 1) {

            if ($str =~ /\S+\s+\S+;$/) {
                $details_flag = 1;

                if ($str =~ /source-address/) {
                    ( $sources ) = ( split(/\s+/, $str) )[2];
                    $sources =~ s/;//;
                    push( @{$pols{$policy_name}{sources}}, "$sources");
                } elsif ($str =~ /destination-address/) {
                    ( $dests ) = ( split(/\s+/, $str) )[2];
                    $dests =~ s/;//;
                    push( @{$pols{$policy_name}{destinations}}, "$dests");
                } elsif ($str =~ /application/) {
                    ( $ports ) = ( split(/\s+/, $str) )[2];
                    $ports =~ s/;//;
                    push( @{$pols{$policy_name}{ports}}, "$ports");
                }

                next OUTER;
            }

        }

        # rest
        if ($details_flag == 1) {

            if ($str =~ /\s*\}\s*$/) {
                if ($clos_counter == 0) {
                    $clos_flag = 1;
                    $clos_counter++;
                    next OUTER;
                }
            }

        }

        # then
        if ($clos_flag == 1) {

            if ($str =~ /\s*then\s+\{$/) {
                $then_flag = 1;
                next OUTER;
            }

        }

        # permit
        if ($then_flag == 1) {

            if ($str =~ /\s*permit;$/) {
                $permit_flag = 1;
                $pols{$policy_name}{action} = ( split(/\s+/,$str) )[1];
                next OUTER;
            }

        }

        # clos2
        if ($permit_flag == 1) {

            if ($str =~ /\s*\}\s*$/) {
                if ($clos_counter == 1) {
                    $clos2_flag = 1;
                    $clos_counter++;
                    next OUTER;
                }
            }

        }

        # final close
        if ($clos2_flag == 1) {

            if ($str =~ /\s*\}\s*$/) {
                if ($clos_counter == 2) {
                    $final_flag = 1;
                    $clos_counter++;
                    next OUTER;
                }
            }

        }

        # ultimate zone
        if ($final_flag == 1) {

            if ($str =~ /\s*\}\s*$/) {
                if ($clos_counter == 3) {
                    $zone_flag = 1;
                    $clos_counter++;
                    next OUTER;
                }
            }

        }

        # ulti pols
        if ($zone_flag == 1) {

            if ($str =~ /\s*\}\s*$/) {
                if ($clos_counter == 4) {
                    $clos_counter++;
                    last OUTER;
                }
            }

        }


    }

}

}

print Dumper(\%pols);

which gives:

$VAR1 = {
      'policy-68' => {
                       'ports' => [
                                    'T_22-ssh'
                                  ],
                       'sources' => [
                                      'g_DMZ_SRV_2_'
                                    ],
                       'to_zone' => 'Blabla',
                       'from_zone' => 'DMZ',
                       'action' => 'permit;',
                       'destinations' => [
                                           'g_DMZ_SRV_3'
                                         ]
                     },
      'policy-9' => {
                      'ports' => [
                                   't_1023'
                                 ],
                      'sources' => [
                                     'g_h_OpenMail-Server'
                                   ],
                      'to_zone' => 'DMZ',
                      'from_zone' => 'Trust',
                      'action' => 'permit;',
                      'destinations' => [
                                          'g_in_DMZ_Exchange'
                                        ]
                    },
      'policy-66' => {
                       'ports' => [
                                    'any'
                                  ],
                       'sources' => [
                                      'g_DMZ_SRV_'
                                    ],
                       'to_zone' => 'DMZ',
                       'from_zone' => 'Trust',
                       'action' => 'permit;',
                       'destinations' => [
                                           'g_DMZ_SRV'
                                         ]
                     },
      'policy-95' => {
                       'ports' => [
                                    't_1024'
                                  ],
                       'sources' => [
                                      'g_h_OpenMail-Server-2',
                                      '1.2.0.3'
                                    ],
                       'to_zone' => 'Blabla',
                       'from_zone' => 'DMZ',
                       'action' => 'permit;',
                       'destinations' => [
                                           'g_in_DMZ_Exchange-1',
                                           '10.25.32.64'
                                         ]
                     }
    };
olivierg
  • 628
  • 6
  • 21
  • This sounds like a Marpa task to me. But I would have to read a lot of documentation to build it. – simbabque Jun 06 '17 at 20:53
  • are you talking about Marpa::R2 ? and no worries i'm not expecting people to code something instead of me, just wondering if someone did that already and if modules did exist in order to do it "simple" (in the same way as XML::Simple does with XML files into hashes for example), i will have a look at Marpa, thanks for the tip! – olivierg Jun 06 '17 at 20:55
  • What tool exactly generates this output? Maybe there is something on cpan? If not, build it and put it there. – simbabque Jun 06 '17 at 20:56
  • it's an export configuration from Juniper ScreenOS firewalls (there is a juniper command called "show configuration" displaying this),thanks for the hint i will look if something exists. – olivierg Jun 06 '17 at 20:58
  • i found this too, it's interesting : http://www.perlmonks.org/?node_id=1112435 – olivierg Jun 06 '17 at 20:58
  • You might also want to read this post by tchrist: https://stackoverflow.com/a/4234491/1331451 – simbabque Jun 06 '17 at 21:04
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/146008/discussion-between-simbabque-and-olivierg). – simbabque Jun 06 '17 at 21:04
  • 1
    This might be relevant: http://ns2html.sourceforge.net/ – Sinan Ünür Jun 06 '17 at 22:19
  • thanks. I managed to do what i want, but in a very, very dirty way just using flags, regexes and nested conditions (i'll post it during the evening or tomorrow if anyone wants), but that's "specific" to my configuration file and not adaptable to any "generic" input using nested curly braces (which is what i originally wanted). i'll use my dirty method in the meantime while i'll look for a cleaner solution – olivierg Jun 07 '17 at 18:09
  • I edited my post with a working solution, but dirty as hell (don't look at it if you don't want to scream) – olivierg Jun 08 '17 at 07:30

2 Answers2

1

In my perhaps not totally unbiased opinion, Marpa::R2 is a good way to solve your type of problem.

Jeffrey Kegler
  • 831
  • 1
  • 6
  • 8
1

Ron Savage of the Marpa community came up with the following:


    my($parser) = Text::Balanced::Marpa -> new
    (
        open  => ['{'],
        close => ['}'],
    );

    my($text)   = read_text('policies.txt');
    my($result) = $parser -> parse(text => \$text);

    print "Parse result: $result (0 is success)\n";
    #print join("\n", @{$parser -> tree -> tree2string}), "\n";

    my($indent);

    for my $node ($parser -> tree -> traverse($parser -> tree -> POST_ORDER) )
    {
        $indent = '  ' x $node -> depth;
        $text   = ${$node -> meta}{text} =~ s/\n|[{}]//gr;

        say $indent, $text if ($text);
    }

Output:

    Parse result: 0 (0 is success)
  policies 
            apply-groups default-log;        from-zone Trust to-zone DMZ 
                  policy policy-66 
                        match 
                              source-address g_DMZ_SRV_;                    destination-address g_DMZ_SRV;                    application any;                
                        then 
                              permit;                

                  policy policy-9 
                        match 
                              source-address g_h_OpenMail-Server;                    destination-address g_in_DMZ_Exchange;                    application t_1023;                
                        then 
                              permit;                


            from-zone DMZ to-zone Blabla 
                  policy policy-68 
                        match 
                              source-address g_DMZ_SRV_2_;                    destination-address g_DMZ_SRV_3;                    application T_22-ssh;                
                        then 
                              permit;                

                  policy policy-95 
                        match 
                              source-address g_h_OpenMail-Server-2;                    source-address 1.2.0.3;                    destination-address g_in_DMZ_Exchange-1;                    destination-address 10.25.32.64;                    application t_1024;                
                        then 
                              permit;                

This gist contains the above code and output. Stackoverflow's formatter insists on indenting everything itself, whether it understands it or not --- the gist can be relied on to contain a pre-damage copy.

Jeffrey Kegler
  • 831
  • 1
  • 6
  • 8