| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | =pod | 
| 2 |  |  |  |  |  |  |  | 
| 3 |  |  |  |  |  |  | =head1 NAME | 
| 4 |  |  |  |  |  |  |  | 
| 5 |  |  |  |  |  |  | WGmeta::Parser::Conf - Parser for Wireguard configurations | 
| 6 |  |  |  |  |  |  |  | 
| 7 |  |  |  |  |  |  | =head1 SYNOPSIS | 
| 8 |  |  |  |  |  |  |  | 
| 9 |  |  |  |  |  |  | use Wireguard::WGmeta::Parser::Conf; | 
| 10 |  |  |  |  |  |  | use Wireguard::WGmeta::Util; | 
| 11 |  |  |  |  |  |  |  | 
| 12 |  |  |  |  |  |  | # Parse a wireguard configuration file | 
| 13 |  |  |  |  |  |  | my $config_contents = read_file('/path/to/config.conf', 'interface_name'); | 
| 14 |  |  |  |  |  |  |  | 
| 15 |  |  |  |  |  |  | # Define callbacks | 
| 16 |  |  |  |  |  |  | my $on_every_value_callback = sub($attribute, $value, $is_wg_meta){ | 
| 17 |  |  |  |  |  |  | # do you magic | 
| 18 |  |  |  |  |  |  | return $attribute, $value; | 
| 19 |  |  |  |  |  |  | }; | 
| 20 |  |  |  |  |  |  | my $on_every_section_callback = sub($identifier, $section_type, $is_disabled){ | 
| 21 |  |  |  |  |  |  | # do you magic | 
| 22 |  |  |  |  |  |  | return $identifier; | 
| 23 |  |  |  |  |  |  | }; | 
| 24 |  |  |  |  |  |  |  | 
| 25 |  |  |  |  |  |  | # And finally parse the configuration | 
| 26 |  |  |  |  |  |  | my $parsed_config = parse_raw_wg_config($config_contents, $on_every_value_callback, $on_every_section_callback); | 
| 27 |  |  |  |  |  |  |  | 
| 28 |  |  |  |  |  |  |  | 
| 29 |  |  |  |  |  |  | =head1 DESCRIPTION | 
| 30 |  |  |  |  |  |  |  | 
| 31 |  |  |  |  |  |  | Parser for Wireguard I<.conf> files with support for custom attributes. A possible implementation is present in L. | 
| 32 |  |  |  |  |  |  |  | 
| 33 |  |  |  |  |  |  | =head1 METHODS | 
| 34 |  |  |  |  |  |  |  | 
| 35 |  |  |  |  |  |  | =cut | 
| 36 |  |  |  |  |  |  |  | 
| 37 |  |  |  |  |  |  | package Wireguard::WGmeta::Parser::Conf; | 
| 38 | 5 |  |  | 5 |  | 38 | use strict; | 
|  | 5 |  |  |  |  | 9 |  | 
|  | 5 |  |  |  |  | 170 |  | 
| 39 | 5 |  |  | 5 |  | 29 | use warnings FATAL => 'all'; | 
|  | 5 |  |  |  |  | 10 |  | 
|  | 5 |  |  |  |  | 167 |  | 
| 40 | 5 |  |  | 5 |  | 28 | use experimental 'signatures'; | 
|  | 5 |  |  |  |  | 10 |  | 
|  | 5 |  |  |  |  | 27 |  | 
| 41 |  |  |  |  |  |  |  | 
| 42 | 5 |  |  | 5 |  | 535 | use constant INTERNAL_KEY_PREFIX => 'int_'; | 
|  | 5 |  |  |  |  | 10 |  | 
|  | 5 |  |  |  |  | 432 |  | 
| 43 |  |  |  |  |  |  |  | 
| 44 | 5 |  |  | 5 |  | 37 | use base 'Exporter'; | 
|  | 5 |  |  |  |  | 70 |  | 
|  | 5 |  |  |  |  | 4811 |  | 
| 45 |  |  |  |  |  |  | our @EXPORT = qw(parse_raw_wg_config INTERNAL_KEY_PREFIX); | 
| 46 |  |  |  |  |  |  |  | 
| 47 |  |  |  |  |  |  | our $VERSION = "0.3.2"; | 
| 48 |  |  |  |  |  |  |  | 
| 49 |  |  |  |  |  |  | =head3 parse_raw_wg_config($file_content, $on_every_value, $on_new_section [, $skip, $wg_meta_prefix, $wg_disabled_prefix]) | 
| 50 |  |  |  |  |  |  |  | 
| 51 |  |  |  |  |  |  | Parses a Wireguard configuration | 
| 52 |  |  |  |  |  |  |  | 
| 53 |  |  |  |  |  |  | =over 1 | 
| 54 |  |  |  |  |  |  |  | 
| 55 |  |  |  |  |  |  | =item * | 
| 56 |  |  |  |  |  |  |  | 
| 57 |  |  |  |  |  |  | C<$file_content> Content of Wireguard configuration. Warning, if have to ensure that its a valid file! | 
| 58 |  |  |  |  |  |  |  | 
| 59 |  |  |  |  |  |  | =item * | 
| 60 |  |  |  |  |  |  |  | 
| 61 |  |  |  |  |  |  | C<$on_every_value> A reference to a callback function, fired at every key/value pair. Expected signature: | 
| 62 |  |  |  |  |  |  |  | 
| 63 |  |  |  |  |  |  | my $on_every_value_callback = sub($attribute, $value, $is_wg_meta){ | 
| 64 |  |  |  |  |  |  | # do you magic | 
| 65 |  |  |  |  |  |  | return $attribute, $value; | 
| 66 |  |  |  |  |  |  | }; | 
| 67 |  |  |  |  |  |  |  | 
| 68 |  |  |  |  |  |  | =item * | 
| 69 |  |  |  |  |  |  |  | 
| 70 |  |  |  |  |  |  | C<$on_new_section> Callback for every section. Expected signature: | 
| 71 |  |  |  |  |  |  |  | 
| 72 |  |  |  |  |  |  | my $on_every_section_callback = sub($identifier, $section_type, $is_disabled){ | 
| 73 |  |  |  |  |  |  | # do you magic | 
| 74 |  |  |  |  |  |  | return $identifier; | 
| 75 |  |  |  |  |  |  | }; | 
| 76 |  |  |  |  |  |  |  | 
| 77 |  |  |  |  |  |  | =item * | 
| 78 |  |  |  |  |  |  |  | 
| 79 |  |  |  |  |  |  | C<[$skip = 0]> When you want to skip some lines at the beginning | 
| 80 |  |  |  |  |  |  |  | 
| 81 |  |  |  |  |  |  | =item * | 
| 82 |  |  |  |  |  |  |  | 
| 83 |  |  |  |  |  |  | C<[$wg_meta_prefix = '#+']> wg-meta prefix. Must start with '#' or ';' | 
| 84 |  |  |  |  |  |  |  | 
| 85 |  |  |  |  |  |  | =item * | 
| 86 |  |  |  |  |  |  |  | 
| 87 |  |  |  |  |  |  | C<[$disabled_prefix = '#-']> disabled prefix. Must start with '#' or ';' | 
| 88 |  |  |  |  |  |  |  | 
| 89 |  |  |  |  |  |  | =back | 
| 90 |  |  |  |  |  |  |  | 
| 91 |  |  |  |  |  |  | B | 
| 92 |  |  |  |  |  |  |  | 
| 93 |  |  |  |  |  |  | A reference to a hash similar as described in L. | 
| 94 |  |  |  |  |  |  |  | 
| 95 |  |  |  |  |  |  | =cut | 
| 96 | 42 |  |  | 42 | 1 | 76 | sub parse_raw_wg_config($file_content, $on_every_value, $on_new_section, $skip = 0, $wg_meta_prefix = '#+', $wg_disabled_prefix = '#-') { | 
|  | 42 |  |  |  |  | 73 |  | 
|  | 42 |  |  |  |  | 67 |  | 
|  | 42 |  |  |  |  | 68 |  | 
|  | 42 |  |  |  |  | 62 |  | 
|  | 42 |  |  |  |  | 72 |  | 
|  | 42 |  |  |  |  | 83 |  | 
|  | 42 |  |  |  |  | 57 |  | 
| 97 | 42 |  |  |  |  | 89 | my $IDENT_KEY = ''; | 
| 98 | 42 |  |  |  |  | 65 | my $IS_ACTIVE_COUNTER = 0; | 
| 99 | 42 |  |  |  |  | 63 | my $IS_ROOT = 1; | 
| 100 | 42 |  |  |  |  | 63 | my $SECTION_TYPE = 'Root'; | 
| 101 | 42 |  |  |  |  | 72 | my $IS_WG_META = 0; | 
| 102 |  |  |  |  |  |  |  | 
| 103 | 42 |  |  |  |  | 87 | my $parsed_config = {}; | 
| 104 | 42 |  |  |  |  | 103 | my @peer_order; | 
| 105 |  |  |  |  |  |  | my @root_order; | 
| 106 |  |  |  |  |  |  |  | 
| 107 | 42 |  |  |  |  | 67 | my $section_data = {}; | 
| 108 | 42 |  |  |  |  | 76 | my @section_order; | 
| 109 | 42 |  |  |  |  | 68 | my $generic_autokey = 0; | 
| 110 | 42 |  |  |  |  | 73 | my $line_count = 0; | 
| 111 |  |  |  |  |  |  |  | 
| 112 |  |  |  |  |  |  | my $section_handler = sub { | 
| 113 | 157 | 100 |  | 157 |  | 305 | if ($IS_ROOT) { | 
| 114 | 42 |  |  |  |  | 108 | $parsed_config = $section_data; | 
| 115 | 42 |  |  |  |  | 98 | $section_data = {}; | 
| 116 |  |  |  |  |  |  | } | 
| 117 |  |  |  |  |  |  | else { | 
| 118 | 115 | 100 |  |  |  | 260 | my $is_disabled = $IS_ACTIVE_COUNTER == 1 ? 1 : 0; | 
| 119 | 115 |  |  |  |  | 210 | my $identifier = &{$on_new_section}($section_data->{$IDENT_KEY}, $SECTION_TYPE, $is_disabled); | 
|  | 115 |  |  |  |  | 280 |  | 
| 120 | 115 | 50 |  |  |  | 275 | die "`$identifier` is already present" if exists($parsed_config->{$identifier}); | 
| 121 | 115 |  |  |  |  | 331 | $section_data->{INTERNAL_KEY_PREFIX . 'order'} = [ @section_order ]; | 
| 122 | 115 |  |  |  |  | 238 | $section_data->{'disabled'} = $is_disabled; | 
| 123 | 115 |  |  |  |  | 254 | $section_data->{INTERNAL_KEY_PREFIX . 'type'} = $SECTION_TYPE; | 
| 124 | 115 |  |  |  |  | 712 | $parsed_config->{$identifier} = { %$section_data }; | 
| 125 | 115 |  |  |  |  | 265 | push @peer_order, $identifier; | 
| 126 | 115 |  |  |  |  | 346 | $section_data = {}; | 
| 127 |  |  |  |  |  |  | } | 
| 128 |  |  |  |  |  |  |  | 
| 129 | 157 |  |  |  |  | 319 | @section_order = (); | 
| 130 | 157 |  |  |  |  | 260 | $IDENT_KEY = 'PublicKey'; | 
| 131 | 157 |  |  |  |  | 230 | $IS_ACTIVE_COUNTER--; | 
| 132 | 157 |  |  |  |  | 227 | $IS_ROOT = 0; | 
| 133 | 42 |  |  |  |  | 211 | }; | 
| 134 |  |  |  |  |  |  |  | 
| 135 | 42 |  |  |  |  | 536 | for my $line (split "\n", $file_content) { | 
| 136 | 719 |  |  |  |  | 1028 | $line_count++; | 
| 137 | 719 | 50 |  |  |  | 1332 | next if $line_count <= $skip; | 
| 138 |  |  |  |  |  |  |  | 
| 139 |  |  |  |  |  |  | # Strip-of any leading or trailing whitespace | 
| 140 | 719 |  |  |  |  | 3676 | $line =~ s/^\s+|\s+$//g; | 
| 141 |  |  |  |  |  |  |  | 
| 142 | 719 | 100 |  |  |  | 1540 | if ((substr $line, 0, 2) eq $wg_disabled_prefix) { | 
| 143 | 7 |  |  |  |  | 14 | $line = substr $line, 2; | 
| 144 | 7 | 100 |  |  |  | 14 | $IS_ACTIVE_COUNTER = 2 if $IS_ACTIVE_COUNTER != 1; | 
| 145 |  |  |  |  |  |  | } | 
| 146 | 719 | 100 |  |  |  | 1319 | if ((substr $line, 0, 2) eq $wg_meta_prefix) { | 
| 147 |  |  |  |  |  |  | # Also slice-off wg-meta prefixes | 
| 148 | 88 |  |  |  |  | 268 | $line = substr $line, 2; | 
| 149 | 88 |  |  |  |  | 130 | $IS_WG_META = 1; | 
| 150 |  |  |  |  |  |  | } | 
| 151 |  |  |  |  |  |  | else { | 
| 152 | 631 |  |  |  |  | 885 | $IS_WG_META = 0; | 
| 153 |  |  |  |  |  |  | } | 
| 154 |  |  |  |  |  |  |  | 
| 155 |  |  |  |  |  |  | # skip empty lines | 
| 156 | 719 | 100 |  |  |  | 1344 | next unless $line; | 
| 157 |  |  |  |  |  |  |  | 
| 158 |  |  |  |  |  |  | # Simply decide if we are in an interface or peer section | 
| 159 | 623 | 100 |  |  |  | 1357 | if ((substr $line, 0, 11) eq '[Interface]') { | 
| 160 | 42 |  |  |  |  | 122 | &$section_handler(); | 
| 161 | 42 |  |  |  |  | 79 | $SECTION_TYPE = 'Interface'; | 
| 162 | 42 |  |  |  |  | 77 | $IDENT_KEY = 'PrivateKey'; | 
| 163 | 42 |  |  |  |  | 94 | next; | 
| 164 |  |  |  |  |  |  | } | 
| 165 | 581 | 100 |  |  |  | 1128 | if ((substr $line, 0, 6) eq '[Peer]') { | 
| 166 | 73 |  |  |  |  | 176 | &$section_handler(); | 
| 167 | 73 |  |  |  |  | 119 | $SECTION_TYPE = 'Peer'; | 
| 168 | 73 |  |  |  |  | 115 | $IDENT_KEY = 'PublicKey'; | 
| 169 | 73 |  |  |  |  | 167 | next; | 
| 170 |  |  |  |  |  |  | } | 
| 171 | 508 |  |  |  |  | 750 | my ($definitive_key, $definitive_value, $discard); | 
| 172 | 508 | 100 |  |  |  | 988 | unless ((substr $line, 0, 1) eq '#') { | 
| 173 | 491 |  |  |  |  | 918 | my ($raw_key, $raw_value) = _split_and_trim($line, '='); | 
| 174 | 491 |  |  |  |  | 1333 | ($definitive_key, $definitive_value, $discard) = &$on_every_value($raw_key, $raw_value, $IS_WG_META); | 
| 175 | 491 | 100 |  |  |  | 1205 | next if $discard == 1; | 
| 176 |  |  |  |  |  |  |  | 
| 177 |  |  |  |  |  |  | # Update identity key if changed | 
| 178 | 474 | 100 |  |  |  | 978 | $IDENT_KEY = $definitive_key if $raw_key eq $IDENT_KEY; | 
| 179 |  |  |  |  |  |  | } | 
| 180 |  |  |  |  |  |  | else { | 
| 181 |  |  |  |  |  |  | # Handle "normal" comments | 
| 182 | 17 |  |  |  |  | 46 | $definitive_key = "comment_$generic_autokey"; | 
| 183 | 17 |  |  |  |  | 30 | $definitive_value = $line; | 
| 184 |  |  |  |  |  |  | } | 
| 185 | 491 |  |  |  |  | 1051 | $section_data->{$definitive_key} = $definitive_value; | 
| 186 | 491 | 50 |  |  |  | 1064 | $IS_ROOT ? push @root_order, $definitive_key : push @section_order, $definitive_key; | 
| 187 | 491 |  |  |  |  | 987 | $generic_autokey++; | 
| 188 |  |  |  |  |  |  | } | 
| 189 |  |  |  |  |  |  | # and finalize | 
| 190 | 42 |  |  |  |  | 167 | &$section_handler(); | 
| 191 | 42 |  |  |  |  | 101 | $parsed_config->{INTERNAL_KEY_PREFIX . 'section_order'} = \@peer_order; | 
| 192 | 42 |  |  |  |  | 96 | $parsed_config->{INTERNAL_KEY_PREFIX . 'root_order'} = \@root_order; | 
| 193 |  |  |  |  |  |  |  | 
| 194 | 42 |  |  |  |  | 409 | return $parsed_config; | 
| 195 |  |  |  |  |  |  | } | 
| 196 |  |  |  |  |  |  |  | 
| 197 | 491 |  |  | 491 |  | 654 | sub _split_and_trim($line, $separator) { | 
|  | 491 |  |  |  |  | 723 |  | 
|  | 491 |  |  |  |  | 736 |  | 
|  | 491 |  |  |  |  | 630 |  | 
| 198 | 491 |  |  |  |  | 4512 | return map {s/^\s+|\s+$//g; | 
|  | 982 |  |  |  |  | 5066 |  | 
| 199 | 982 |  |  |  |  | 2779 | $_} split $separator, $line, 2; | 
| 200 |  |  |  |  |  |  | } | 
| 201 |  |  |  |  |  |  |  | 
| 202 |  |  |  |  |  |  | 1; |