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.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  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(length, start_address):
  34. pad = -length % 4 # padding to alignment of 4
  35. # extra padding if pad != 0 and big start_address
  36. if pad != 0 and (start_address & 0x80000000) != 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. DEFAULT_IOS = 0x0F04 # IOS 15.4
  59. base_address = 0x10000000
  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, start_addr, _, length, _, _, _, _, _) = \
  77. struct.unpack_from('>HHHHIIIIIHHI', nvram, offset=0)
  78. if magic == 0xABCD:
  79. base_address = start_addr - 36
  80. config_len = 36 + length + padding(length, base_address)
  81. (magic, _, _, _, length) = \
  82. struct.unpack_from('>HHIII', nvram, offset=config_len)
  83. if magic == 0xFEDC:
  84. config_len += 16 + length
  85. else:
  86. ios = None
  87. except struct.error:
  88. raise ValueError('unknown nvram format')
  89. if config_len > nvram_len:
  90. raise ValueError('unknown nvram format')
  91. # calculate max. config size
  92. max_config = nvram_len - 2*1024 # reserve 2k for files
  93. idx = max_config
  94. empty_sector = bytearray([0] * 1024)
  95. while True:
  96. idx -= 1024
  97. if idx < config_len:
  98. break
  99. # if valid file header:
  100. (magic, _, flags, length, _) = \
  101. struct.unpack_from('>HHHH24s', nvram, offset=idx)
  102. if magic == 0xDCBA and flags < 8 and length <= 992:
  103. max_config = idx
  104. elif nvram[idx:idx+1024] != empty_sector:
  105. break
  106. # import startup config
  107. new_nvram = bytearray()
  108. if ios is None:
  109. # Target IOS version is unknown. As some IOU don't work nicely with
  110. # the padding of a different version, the startup config is padded
  111. # with '\n' to the alignment of 4.
  112. ios = DEFAULT_IOS
  113. startup += b'\n' * (-len(startup) % 4)
  114. new_nvram.extend(struct.pack('>HHHHIIIIIHHI',
  115. 0xABCD, # magic
  116. 1, # raw data
  117. 0, # checksum, not yet calculated
  118. ios, # IOS version
  119. base_address + 36, # start address
  120. base_address + 36 + len(startup), # end address
  121. len(startup), # length
  122. 0, 0, 0, 0, 0))
  123. new_nvram.extend(startup)
  124. new_nvram.extend([0] * padding(len(new_nvram), base_address))
  125. # import private config
  126. if private is None:
  127. private = b''
  128. offset = len(new_nvram)
  129. new_nvram.extend(struct.pack('>HHIII',
  130. 0xFEDC, # magic
  131. 1, # raw data
  132. base_address + offset + 16, # start address
  133. base_address + offset + 16 + len(private), # end address
  134. len(private) )) # length
  135. new_nvram.extend(private)
  136. # add rest
  137. if len(new_nvram) > max_config:
  138. raise ValueError('NVRAM size too small')
  139. new_nvram.extend([0] * (max_config - len(new_nvram)))
  140. new_nvram.extend(nvram[max_config:])
  141. checksum(new_nvram, 0, nvram_len)
  142. return new_nvram
  143. if __name__ == '__main__':
  144. # Main program
  145. import argparse
  146. import sys
  147. def check_size(string):
  148. try:
  149. value = int(string)
  150. except ValueError:
  151. raise argparse.ArgumentTypeError('invalid int value: ' + string)
  152. if value < 8 or value > 1024:
  153. raise argparse.ArgumentTypeError('size must be 8..1024')
  154. return value
  155. parser = argparse.ArgumentParser(description='%(prog)s imports startup/private configuration into IOU NVRAM file.')
  156. parser.add_argument('-c', '--create', metavar='size', type=check_size,
  157. help='create NVRAM file, size in kByte')
  158. parser.add_argument('nvram', metavar='NVRAM',
  159. help='NVRAM file')
  160. parser.add_argument('startup', metavar='startup-config',
  161. help='startup configuration')
  162. parser.add_argument('private', metavar='private-config', nargs='?',
  163. help='private configuration')
  164. args = parser.parse_args()
  165. try:
  166. if args.create is None:
  167. fd = open(args.nvram, 'rb')
  168. nvram = fd.read()
  169. fd.close()
  170. else:
  171. nvram = None
  172. fd = open(args.startup, 'rb')
  173. startup = fd.read()
  174. fd.close()
  175. if args.private is None:
  176. private = None
  177. else:
  178. fd = open(args.private, 'rb')
  179. private = fd.read()
  180. fd.close()
  181. except (IOError, OSError) as err:
  182. sys.stderr.write("Error reading file: {}\n".format(err))
  183. sys.exit(1)
  184. try:
  185. nvram = nvram_import(nvram, startup, private, args.create)
  186. except ValueError as err:
  187. sys.stderr.write("nvram_import: {}\n".format(err))
  188. sys.exit(3)
  189. try:
  190. fd = open(args.nvram, 'wb')
  191. fd.write(nvram)
  192. fd.close()
  193. except (IOError, OSError) as err:
  194. sys.stderr.write("Error writing file: {}\n".format(err))
  195. sys.exit(1)