Class: Iptables::Decoder

Inherits:
Object
  • Object
show all
Defined in:
lib/iptables.rb

Overview

This is the internal Decoder class used by methods in the main class.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(opts = {}) ⇒ Decoder

Initialize the decoder object

Parameters:

  • opts (Hash) (defaults to: {})

    a hash of options

Options Hash (opts):

  • :debug (Bool)

    If true, turns on debugging output

  • :iptables_compatibilty (String)

    version of iptables to be compatible with. Since some versions differ wildly, this might be necessary.



38
39
40
41
42
43
# File 'lib/iptables.rb', line 38

def initialize(opts = {})
  @opts = {
    :debug => false,
    :iptables_compatibility => nil,
  }.merge(opts)
end

Instance Attribute Details

#optsObject (readonly)

Returns the value of attribute opts.



29
30
31
# File 'lib/iptables.rb', line 29

def opts
  @opts
end

#r opts(opts) ⇒ Hash

Returns Options hash set on initialization.

Returns:

  • (Hash)

    Options hash set on initialization



29
# File 'lib/iptables.rb', line 29

attr_reader :opts

Instance Method Details

#debug(text) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Prints debug output to STDOUT if debug switch is true

Parameters:

  • text (String)

    text to output for debugging



307
308
309
# File 'lib/iptables.rb', line 307

def debug(text)
  puts "D, #{text}" if @opts[:debug]
end

#decode(text) ⇒ Hash

Decodes iptables-save input into a normalized hash

Parameters:

  • text (String)

    the raw output of iptables-save

Returns:

  • (Hash)

    returns a hash containing the parsed rules

Raises:



50
51
52
53
54
55
56
57
58
# File 'lib/iptables.rb', line 50

def decode(text)
  {
    :metadata => {
      :ruby_iptables_version => VERSION,
      :iptables_compatibility => opts[:iptables_compatibility],
    },
    :result => parse_iptables_save(text),
  }
end

#iptables_backwards_negatesObject



295
296
297
298
299
300
301
# File 'lib/iptables.rb', line 295

def iptables_backwards_negates
  if opts[:iptables_compatibility] == '1.3.5'
    %w{p s d i o ctorigsrc ctorigdst ctreplsrc ctrepldst espspi length sports dports ports mss}
  else
    []
  end
end

#parse_append_line(line) ⇒ Hash

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Parses an append line return a hash

Parameters:

  • text (String)

    a single iptables-save append line

Returns:

  • (Hash)

    a hash containing data for the parsed rule



115
116
117
118
119
120
121
122
123
124
# File 'lib/iptables.rb', line 115

def parse_append_line(line)
  ss = shellsplit(line)
  sh = switch_hash(ss)
  rh = rule(sh)
  {
    :shell_split => ss,
    :swtch_hash => sh,
    :rule => rh,
  }
end

#parse_iptables_save(text) ⇒ Hash

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Takes raw iptables-save input, returns a data hash

Parameters:

  • text (String)

    the raw output of iptables-save

Returns:

  • (Hash)

    returns a hash containing the parsed rules

Raises:

  • (Iptables::NoTable)

    raised if a rule is passed without a prior table declaration



67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
# File 'lib/iptables.rb', line 67

def parse_iptables_save(text)
  # Set the table to nil to begin with so we can detect append lines with no
  # prior table decleration.
  table = nil

  # Input line number for debugging later
  original_line_number = 0

  # Hash for storing the final result
  hash = {}

  text.each_line do |line|

    # If we find a table declaration, change table
    if line =~ /^\*([a-z]+)$/
      table = $1
      debug("Found table [#{table}] on line [#{original_line_number}]")
    end

    # If we find an append line, parse it
    if line =~ /^-A (\S+)/
      raise NoTable, "Found an append line [#{line}] on line [#{input_line}], but no table yet" if table.nil?

      chain = $1
      line_hash = parse_append_line(line)

      line_hash[:source] = {
        :original_line => line,
        :original_line_number => original_line_number,
      }

      hash[table] ||= {}
      hash[table][chain] ||= {}
      hash[table][chain][:rules] ||= []
      hash[table][chain][:rules] << line_hash
    end

    original_line_number += 1
  end

  hash
end

#rule(switch_hash) ⇒ Hash

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Takes a switch_hash and returns the rule as a hash

Parameters:

  • switch_hash (Hash)

    a semi-parsed hash of the rule append line

Returns:

  • (Hash)

    a parsed rule in hash format



131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
# File 'lib/iptables.rb', line 131

def rule(switch_hash)
  h = {
    :chain => nil,
    :parameters => {},
    :target => nil,
    :matches => [],
    :target_options => {},
  }

  # States
  match = false
  match_current = {}
  target = false

  switch_hash.each do |sh|
    sw = sh[:switch]
    if sw == "A"
      h[:chain] = sh[:values].first
      next
    end

    # Outside of match and target, these letters are the basic parameters
    if !match and !target and ["p", "s", "d", "i", "o", "f"].include? sw
      h[:parameters]["#{sh[:negate]? '!' : ''}#{sw}"] = sh[:values]
      next
    end

    # If option is 'm' then we are in a match
    if sw == 'm'
      if match and !match_current.empty?
        # We were already in a match, stow it
        h[:matches] << match_current
        match_current = {}
      end

      # Clear the current match
      match_current = {}
      match_current[:name] = sh[:values].first

      # Reset states
      match = true
      target = false

      next
    end

    # If option is 'j' then its a target, and anything else is a target_option
    if sw == "j"
      if match and !match_current.empty?
        # We were already in a match, stow it
        h[:matches] << match_current
        match_current = {}
      end

      h[:target] = sh[:values].first

      # Reset states
      target = true
      match = false

      next
    end

    if match
      match_current[:options] ||= {}
      match_current[:options]["#{sh[:negate]? '!' : ''}#{sw}"] = sh[:values]

      next
    end

    if target
      h[:target_options]["#{sh[:negate]? '!' : ''}#{sw}"] = sh[:values]
      next
    end
  end

  # Stow away any incomplete matches
  if match and !match_current.empty?
    h[:matches] << match_current
  end

  h
end

#shellsplit(line) ⇒ Array

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Break rule line into pices like a shell.

The code itself is taken from Ruby core, and supplanted here to work with older rubies.

Parameters:

  • line (String)

    a list of shell arguments and values

Returns:

  • (Array)

    an array of shell arguments and values split in a shell safe way.

Raises:

  • (ArgumentError)

    raised on unmatched double quote

See Also:



280
281
282
283
284
285
286
287
288
289
290
291
292
293
# File 'lib/iptables.rb', line 280

def shellsplit(line)
  words = []
  field = ''
  line.scan(/\G\s*(?>([^\s\\\'\"]+)|'([^\']*)'|"((?:[^\"\\]|\\.)*)"|(\\.?)|(\S))(\s|\z)?/m) do
    |word, sq, dq, esc, garbage, sep|
    raise ArgumentError, "Unmatched double quote: #{line.inspect}" if garbage
    field << (word || sq || (dq || esc).gsub(/\\(.)/, '\\1'))
    if sep
      words << field
      field = ''
    end
  end
  words
end

#switch_hash(split) ⇒ Hash

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Takes an argument array, and returns swtiches and values. It returns a hash with switches on the LHS, and values on the right. Values appear as arrays.

For switches without values, the RHS will just be the boolean true.

Parameters:

  • split (Array)

    a list of arguments and values split in a shell-safe way

Returns:

  • (Hash)

    a semi-parsed hash of arguments, values and negation status

Raises:

  • (Iptables::UnparseableSplit)

    raised when the split cannot be parsed into the correct format, usually because the input format is incorrect.



226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
# File 'lib/iptables.rb', line 226

def switch_hash(split)
  result = []

  current = nil

  debug("processing #{split.inspect}")

  split.each do |p|
    debug "p: #{p}"
    debug "pre current: #{current.inspect}" if current
    if p =~ /^--?(.+)/
      if current and !current.empty?
        if (current[:negate] and current[:switch]) or !current[:negate]
          result << current
          current = {}
        end
      else
        current = {}
      end
      current[:switch] = $1
    elsif p == '!'
      if current and !current.empty?
        unless current[:switch] \
          and iptables_backwards_negates.include? current[:switch]
          result << current
          current = {}
        end
      end
      current[:negate] = true
    else
      raise UnparseableSplit, "Found a value without corresponding arg" unless current
      current[:values] ||= []
      current[:values] << p
    end
    debug "post current: #{current.inspect}" if current
    debug "result: #{result.inspect}"
  end
  result << current

  result
end