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.

228 lines
7.6 KiB

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# To use python v2.7 change the first line to:
#!/usr/bin/env python
# Copyright (C) 2015 Bernhard Ehlers
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
iou_import imports startup/private configuration into IOU NVRAM file.
usage: iou_import [-h] [-c size] NVRAM startup-config [private-config]
positional arguments:
NVRAM NVRAM file
startup-config startup configuration
private-config private configuration
optional arguments:
-h, --help show this help message and exit
-c size, --create size
create NVRAM file, size in kByte
"""
import struct
# calculate padding
def padding(length, start_address):
pad = -length % 4 # padding to alignment of 4
# extra padding if pad != 0 and big start_address
if pad != 0 and (start_address & 0x80000000) != 0:
pad += 4
return pad
# update checksum
def checksum(data, start, end):
chk = 0
# calculate checksum of first two words
for word in struct.unpack_from('>2H', data, start):
chk += word
# add remaining words, ignoring old checksum at offset 4
struct_format = '>{:d}H'.format((end - start - 6) // 2)
for word in struct.unpack_from(struct_format, data, start+6):
chk += word
# handle 16 bit overflow
while chk >> 16:
chk = (chk & 0xffff) + (chk >> 16)
chk = chk ^ 0xffff
# save checksum
struct.pack_into('>H', data, start+4, chk)
# import IOU NVRAM
# NVRAM format: https://github.com/ehlers/IOUtools/blob/master/NVRAM.md
def nvram_import(nvram, startup, private, size):
DEFAULT_IOS = 0x0F04 # IOS 15.4
base_address = 0x10000000
# check size parameter
if size is not None and (size < 8 or size > 1024):
raise ValueError('invalid size')
# create new nvram if nvram is empty or has wrong size
if nvram is None or (size is not None and len(nvram) != size*1024):
nvram = bytearray([0] * (size*1024))
else:
nvram = bytearray(nvram)
# check nvram size
nvram_len = len(nvram)
if nvram_len < 8*1024 or nvram_len > 1024*1024 or nvram_len % 1024 != 0:
raise ValueError('invalid NVRAM length')
nvram_len = nvram_len // 2
# get size of current config
config_len = 0
try:
(magic, _, _, ios, start_addr, _, length, _, _, _, _, _) = \
struct.unpack_from('>HHHHIIIIIHHI', nvram, offset=0)
if magic == 0xABCD:
base_address = start_addr - 36
config_len = 36 + length + padding(length, base_address)
(magic, _, _, _, length) = \
struct.unpack_from('>HHIII', nvram, offset=config_len)
if magic == 0xFEDC:
config_len += 16 + length
else:
ios = None
except struct.error:
raise ValueError('unknown nvram format')
if config_len > nvram_len:
raise ValueError('unknown nvram format')
# calculate max. config size
max_config = nvram_len - 2*1024 # reserve 2k for files
idx = max_config
empty_sector = bytearray([0] * 1024)
while True:
idx -= 1024
if idx < config_len:
break
# if valid file header:
(magic, _, flags, length, _) = \
struct.unpack_from('>HHHH24s', nvram, offset=idx)
if magic == 0xDCBA and flags < 8 and length <= 992:
max_config = idx
elif nvram[idx:idx+1024] != empty_sector:
break
# import startup config
new_nvram = bytearray()
if ios is None:
# Target IOS version is unknown. As some IOU don't work nicely with
# the padding of a different version, the startup config is padded
# with '\n' to the alignment of 4.
ios = DEFAULT_IOS
startup += b'\n' * (-len(startup) % 4)
new_nvram.extend(struct.pack('>HHHHIIIIIHHI',
0xABCD, # magic
1, # raw data
0, # checksum, not yet calculated
ios, # IOS version
base_address + 36, # start address
base_address + 36 + len(startup), # end address
len(startup), # length
0, 0, 0, 0, 0))
new_nvram.extend(startup)
new_nvram.extend([0] * padding(len(new_nvram), base_address))
# import private config
if private is None:
private = b''
offset = len(new_nvram)
new_nvram.extend(struct.pack('>HHIII',
0xFEDC, # magic
1, # raw data
base_address + offset + 16, # start address
base_address + offset + 16 + len(private), # end address
len(private) )) # length
new_nvram.extend(private)
# add rest
if len(new_nvram) > max_config:
raise ValueError('NVRAM size too small')
new_nvram.extend([0] * (max_config - len(new_nvram)))
new_nvram.extend(nvram[max_config:])
checksum(new_nvram, 0, nvram_len)
return new_nvram
if __name__ == '__main__':
# Main program
import argparse
import sys
def check_size(string):
try:
value = int(string)
except ValueError:
raise argparse.ArgumentTypeError('invalid int value: ' + string)
if value < 8 or value > 1024:
raise argparse.ArgumentTypeError('size must be 8..1024')
return value
parser = argparse.ArgumentParser(description='%(prog)s imports startup/private configuration into IOU NVRAM file.')
parser.add_argument('-c', '--create', metavar='size', type=check_size,
help='create NVRAM file, size in kByte')
parser.add_argument('nvram', metavar='NVRAM',
help='NVRAM file')
parser.add_argument('startup', metavar='startup-config',
help='startup configuration')
parser.add_argument('private', metavar='private-config', nargs='?',
help='private configuration')
args = parser.parse_args()
try:
if args.create is None:
fd = open(args.nvram, 'rb')
nvram = fd.read()
fd.close()
else:
nvram = None
fd = open(args.startup, 'rb')
startup = fd.read()
fd.close()
if args.private is None:
private = None
else:
fd = open(args.private, 'rb')
private = fd.read()
fd.close()
except (IOError, OSError) as err:
sys.stderr.write("Error reading file: {}\n".format(err))
sys.exit(1)
try:
nvram = nvram_import(nvram, startup, private, args.create)
except ValueError as err:
sys.stderr.write("nvram_import: {}\n".format(err))
sys.exit(3)
try:
fd = open(args.nvram, 'wb')
fd.write(nvram)
fd.close()
except (IOError, OSError) as err:
sys.stderr.write("Error writing file: {}\n".format(err))
sys.exit(1)