IOU tools
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

iou_export 7.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. # To use python v2.7 change the first line to:
  4. #!/usr/bin/env python
  5. # Copyright (C) 2015 Bernhard Ehlers
  6. #
  7. # This program is free software: you can redistribute it and/or modify
  8. # it under the terms of the GNU General Public License as published by
  9. # the Free Software Foundation, either version 2 of the License, or
  10. # (at your option) any later version.
  11. #
  12. # This program is distributed in the hope that it will be useful,
  13. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. # GNU General Public License for more details.
  16. #
  17. # You should have received a copy of the GNU General Public License
  18. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  19. # This utility is a stripped down version of dynamips' nvram_export,
  20. # ported from C to Python, see https://github.com/GNS3/dynamips
  21. # nvram_export is (c) 2013 Flávio J. Saraiva
  22. """
  23. iou_export exports startup/private configuration from IOU NVRAM file.
  24. usage: iou_export [-h] NVRAM startup-config [private-config]
  25. positional arguments:
  26. NVRAM NVRAM file
  27. startup-config startup configuration
  28. private-config private configuration
  29. optional arguments:
  30. -h, --help show this help message and exit
  31. """
  32. import struct
  33. # Uncompress data in LZC format, .Z file format
  34. # LZC uses the LZW compression algorithm with a variable dictionary size
  35. # For LZW see https://en.wikipedia.org/wiki/Lempel–Ziv–Welch
  36. # Performance: about 1 MByte/sec, 15-50 times slower than C implementation
  37. def uncompress_LZC(data):
  38. LZC_NUM_BITS_MIN = 9
  39. LZC_NUM_BITS_MAX = 16
  40. in_data = bytearray(data)
  41. in_len = len(in_data)
  42. out_data = bytearray()
  43. if in_len == 0:
  44. return out_data
  45. if in_len < 3:
  46. raise ValueError('invalid length')
  47. if in_data[0] != 0x1F or in_data[1] != 0x9D:
  48. raise ValueError('invalid header')
  49. max_bits = in_data[2] & 0x1F
  50. if max_bits < LZC_NUM_BITS_MIN or max_bits > LZC_NUM_BITS_MAX:
  51. raise ValueError('not supported')
  52. num_items = 1 << max_bits
  53. blockmode = (in_data[2] & 0x80) != 0
  54. in_pos = 3
  55. start_pos = in_pos
  56. num_bits = LZC_NUM_BITS_MIN
  57. dict_size = 1 << num_bits
  58. head = 256
  59. if blockmode:
  60. head += 1
  61. first_sym = True
  62. # initialize dictionary
  63. comp_dict = [None] * num_items
  64. for i in range(0, 256):
  65. comp_dict[i] = bytes(bytearray([i]))
  66. buf = buf_bits = 0
  67. while in_pos < in_len:
  68. # get next symbol
  69. try:
  70. while buf_bits < num_bits:
  71. buf |= in_data[in_pos] << buf_bits
  72. buf_bits += 8
  73. in_pos += 1
  74. buf, symbol = divmod(buf, dict_size)
  75. buf_bits -= num_bits
  76. except IndexError:
  77. raise ValueError('invalid data')
  78. # re-initialize dictionary
  79. if blockmode and symbol == 256:
  80. # skip to next buffer boundary
  81. buf = buf_bits = 0
  82. in_pos += (start_pos - in_pos) % num_bits
  83. # reset to LZC_NUM_BITS_MIN
  84. head = 257
  85. num_bits = LZC_NUM_BITS_MIN
  86. dict_size = 1 << num_bits
  87. start_pos = in_pos
  88. first_sym = True
  89. continue
  90. # first symbol
  91. if first_sym:
  92. first_sym = False
  93. if symbol >= 256:
  94. raise ValueError('invalid data')
  95. prev = symbol
  96. out_data.extend(comp_dict[symbol])
  97. continue
  98. # dictionary full
  99. if head >= num_items:
  100. out_data.extend(comp_dict[symbol])
  101. continue
  102. # update compression dictionary
  103. if symbol < head:
  104. comp_dict[head] = comp_dict[prev] + comp_dict[symbol][0:1]
  105. elif symbol == head:
  106. comp_dict[head] = comp_dict[prev] + comp_dict[prev][0:1]
  107. else:
  108. raise ValueError('invalid data')
  109. prev = symbol
  110. # output symbol
  111. out_data.extend(comp_dict[symbol])
  112. # update head, check for num_bits change
  113. head += 1
  114. if head >= dict_size and num_bits < max_bits:
  115. num_bits += 1
  116. dict_size = 1 << num_bits
  117. start_pos = in_pos
  118. return out_data
  119. # export IOU NVRAM
  120. # NVRAM format: https://github.com/ehlers/IOUtools/blob/master/NVRAM.md
  121. def nvram_export(nvram):
  122. nvram = bytearray(nvram)
  123. offset = 0
  124. # extract startup config
  125. try:
  126. (magic, data_format, _, _, _, _, length, _, _, _, _, _) = \
  127. struct.unpack_from('>HHHHIIIIIHHI', nvram, offset=offset)
  128. offset += 36
  129. if magic != 0xABCD:
  130. raise ValueError('no startup config')
  131. if len(nvram) < offset+length:
  132. raise ValueError('invalid length')
  133. startup = nvram[offset:offset+length]
  134. except struct.error:
  135. raise ValueError('invalid length')
  136. # uncompress startup config
  137. if data_format == 2:
  138. try:
  139. startup = uncompress_LZC(startup)
  140. except ValueError as err:
  141. raise ValueError('uncompress startup: ' + str(err))
  142. private = None
  143. try:
  144. # calculate offset of private header
  145. length += (4 - length % 4) % 4 # alignment to multiple of 4
  146. offset += length
  147. # check for additonal offset of 4
  148. (magic, data_format) = struct.unpack_from('>HH', nvram, offset=offset+4)
  149. if magic == 0xFEDC and data_format == 1:
  150. offset += 4
  151. # extract private config
  152. (magic, data_format, _, _, length) = \
  153. struct.unpack_from('>HHIII', nvram, offset=offset)
  154. offset += 16
  155. if magic == 0xFEDC and data_format == 1:
  156. if len(nvram) < offset+length:
  157. raise ValueError('invalid length')
  158. private = nvram[offset:offset+length]
  159. # missing private header is not an error
  160. except struct.error:
  161. pass
  162. return (startup, private)
  163. if __name__ == '__main__':
  164. # Main program
  165. import argparse
  166. import sys
  167. parser = argparse.ArgumentParser(description='%(prog)s exports startup/private configuration from IOU NVRAM file.')
  168. parser.add_argument('nvram', metavar='NVRAM',
  169. help='NVRAM file')
  170. parser.add_argument('startup', metavar='startup-config',
  171. help='startup configuration')
  172. parser.add_argument('private', metavar='private-config', nargs='?',
  173. help='private configuration')
  174. args = parser.parse_args()
  175. try:
  176. fd = open(args.nvram, 'rb')
  177. nvram = fd.read()
  178. fd.close()
  179. except (IOError, OSError) as err:
  180. sys.stderr.write("Error reading file: {}\n".format(err))
  181. sys.exit(1)
  182. try:
  183. startup, private = nvram_export(nvram)
  184. except ValueError as err:
  185. sys.stderr.write("nvram_export: {}\n".format(err))
  186. sys.exit(3)
  187. try:
  188. fd = open(args.startup, 'wb')
  189. fd.write(startup)
  190. fd.close()
  191. if args.private is not None:
  192. if private is None:
  193. sys.stderr.write("Warning: No private config\n")
  194. else:
  195. fd = open(args.private, 'wb')
  196. fd.write(private)
  197. fd.close()
  198. except (IOError, OSError) as err:
  199. sys.stderr.write("Error writing file: {}\n".format(err))
  200. sys.exit(1)