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_import 7.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  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. """
  20. iou_import imports startup/private configuration into IOU NVRAM file.
  21. usage: iou_import [-h] [-c size] NVRAM startup-config [private-config]
  22. positional arguments:
  23. NVRAM NVRAM file
  24. startup-config startup configuration
  25. private-config private configuration
  26. optional arguments:
  27. -h, --help show this help message and exit
  28. -c size, --create size
  29. create NVRAM file, size in kByte
  30. """
  31. import struct
  32. # calculate padding
  33. def padding(off, ios, nvram_len):
  34. pad = (4 - off % 4) % 4 # padding to alignment of 4
  35. # add 4 if IOS <= 15.0 or NVRAM area >= 64KB
  36. if (ios <= 0x0F00 or nvram_len >= 64*1024) and pad != 0:
  37. pad += 4
  38. return pad
  39. # update checksum
  40. def checksum(data, start, end):
  41. chk = 0
  42. # calculate checksum of first two words
  43. for word in struct.unpack_from('>2H', data, start):
  44. chk += word
  45. # add remaining words, ignoring old checksum at offset 4
  46. struct_format = '>{:d}H'.format((end - start - 6) // 2)
  47. for word in struct.unpack_from(struct_format, data, start+6):
  48. chk += word
  49. # handle 16 bit overflow
  50. while chk >> 16:
  51. chk = (chk & 0xffff) + (chk >> 16)
  52. chk = chk ^ 0xffff
  53. # save checksum
  54. struct.pack_into('>H', data, start+4, chk)
  55. # import IOU NVRAM
  56. # NVRAM format: https://github.com/ehlers/IOUtools/blob/master/NVRAM.md
  57. def nvram_import(nvram, startup, private, size):
  58. BASE_ADDRESS = 0x10000000
  59. DEFAULT_IOS = 0x0F04 # IOS 15.4
  60. # check size parameter
  61. if size is not None and (size < 8 or size > 1024):
  62. raise ValueError('invalid size')
  63. # create new nvram if nvram is empty or has wrong size
  64. if nvram is None or (size is not None and len(nvram) != size*1024):
  65. nvram = bytearray([0] * (size*1024))
  66. else:
  67. nvram = bytearray(nvram)
  68. # check nvram size
  69. nvram_len = len(nvram)
  70. if nvram_len < 8*1024 or nvram_len > 1024*1024 or nvram_len % 1024 != 0:
  71. raise ValueError('invalid NVRAM length')
  72. nvram_len = nvram_len // 2
  73. # get size of current config
  74. config_len = 0
  75. try:
  76. (magic, _, _, ios, _, _, length, _, _, _, _, _) = \
  77. struct.unpack_from('>HHHHIIIIIHHI', nvram, offset=0)
  78. if magic == 0xABCD:
  79. config_len = 36 + length + padding(length, ios, nvram_len)
  80. (magic, _, _, _, length) = \
  81. struct.unpack_from('>HHIII', nvram, offset=config_len)
  82. if magic == 0xFEDC:
  83. config_len += 16 + length
  84. else:
  85. ios = None
  86. except struct.error:
  87. raise ValueError('unknown nvram format')
  88. if config_len > nvram_len:
  89. raise ValueError('unknown nvram format')
  90. # calculate max. config size
  91. max_config = nvram_len - 2*1024 # reserve 2k for files
  92. idx = max_config
  93. empty_sector = bytearray([0] * 1024)
  94. while True:
  95. idx -= 1024
  96. if idx < config_len:
  97. break
  98. # if valid file header:
  99. (magic, _, flags, length, _) = \
  100. struct.unpack_from('>HHHH24s', nvram, offset=idx)
  101. if magic == 0xDCBA and flags < 8 and length <= 992:
  102. max_config = idx
  103. elif nvram[idx:idx+1024] != empty_sector:
  104. break
  105. # import startup config
  106. new_nvram = bytearray()
  107. if ios is None:
  108. # Target IOS version is unknown. As some IOU don't work nicely with
  109. # the padding of a different version, the startup config is padded
  110. # with '\n' to the alignment of 4.
  111. ios = DEFAULT_IOS
  112. startup += b'\n' * ((4 - len(startup) % 4) % 4)
  113. new_nvram.extend(struct.pack('>HHHHIIIIIHHI',
  114. 0xABCD, # magic
  115. 1, # raw data
  116. 0, # checksum, not yet calculated
  117. ios, # IOS version
  118. BASE_ADDRESS + 36, # start address
  119. BASE_ADDRESS + 36 + len(startup), # end address
  120. len(startup), # length
  121. 0, 0, 0, 0, 0))
  122. new_nvram.extend(startup)
  123. new_nvram.extend([0] * padding(len(new_nvram), ios, nvram_len))
  124. # import private config
  125. if private is None:
  126. private = b''
  127. offset = len(new_nvram)
  128. new_nvram.extend(struct.pack('>HHIII',
  129. 0xFEDC, # magic
  130. 1, # raw data
  131. BASE_ADDRESS + offset + 16, # start address
  132. BASE_ADDRESS + offset + 16 + len(private), # end address
  133. len(private) )) # length
  134. new_nvram.extend(private)
  135. # add rest
  136. if len(new_nvram) > max_config:
  137. raise ValueError('NVRAM size too small')
  138. new_nvram.extend([0] * (max_config - len(new_nvram)))
  139. new_nvram.extend(nvram[max_config:])
  140. checksum(new_nvram, 0, nvram_len)
  141. return new_nvram
  142. if __name__ == '__main__':
  143. # Main program
  144. import argparse
  145. import sys
  146. def check_size(string):
  147. try:
  148. value = int(string)
  149. except ValueError:
  150. raise argparse.ArgumentTypeError('invalid int value: ' + string)
  151. if value < 8 or value > 1024:
  152. raise argparse.ArgumentTypeError('size must be 8..1024')
  153. return value
  154. parser = argparse.ArgumentParser(description='%(prog)s imports startup/private configuration into IOU NVRAM file.')
  155. parser.add_argument('-c', '--create', metavar='size', type=check_size,
  156. help='create NVRAM file, size in kByte')
  157. parser.add_argument('nvram', metavar='NVRAM',
  158. help='NVRAM file')
  159. parser.add_argument('startup', metavar='startup-config',
  160. help='startup configuration')
  161. parser.add_argument('private', metavar='private-config', nargs='?',
  162. help='private configuration')
  163. args = parser.parse_args()
  164. try:
  165. if args.create is None:
  166. fd = open(args.nvram, 'rb')
  167. nvram = fd.read()
  168. fd.close()
  169. else:
  170. nvram = None
  171. fd = open(args.startup, 'rb')
  172. startup = fd.read()
  173. fd.close()
  174. if args.private is None:
  175. private = None
  176. else:
  177. fd = open(args.private, 'rb')
  178. private = fd.read()
  179. fd.close()
  180. except (IOError, OSError) as err:
  181. sys.stderr.write("Error reading file: {}\n".format(err))
  182. sys.exit(1)
  183. try:
  184. nvram = nvram_import(nvram, startup, private, args.create)
  185. except ValueError as err:
  186. sys.stderr.write("nvram_import: {}\n".format(err))
  187. sys.exit(3)
  188. try:
  189. fd = open(args.nvram, 'wb')
  190. fd.write(nvram)
  191. fd.close()
  192. except (IOError, OSError) as err:
  193. sys.stderr.write("Error writing file: {}\n".format(err))
  194. sys.exit(1)