1
0
mirror of https://git.tartarus.org/simon/putty.git synced 2025-01-10 18:07:59 +00:00
putty-source/contrib/kh2reg.py
Simon Tatham 4d8782e74f Rework versioning system to not depend on Subversion.
I've shifted away from using the SVN revision number as a monotonic
version identifier (replacing it in the Windows version resource with
a count of days since an arbitrary epoch), and I've removed all uses
of SVN keyword expansion (replacing them with version information
written out by Buildscr).

While I'm at it, I've done a major rewrite of the affected code which
centralises all the computation of the assorted version numbers and
strings into Buildscr, so that they're all more or less alongside each
other rather than scattered across multiple source files.

I've also retired the MD5-based manifest file system. A long time ago,
it seemed like a good idea to arrange that binaries of PuTTY would
automatically cease to identify themselves as a particular upstream
version number if any changes were made to the source code, so that if
someone made a local tweak and distributed the result then I wouldn't
get blamed for the results. Since then I've decided the whole idea is
more trouble than it's worth, so now distribution tarballs will have
version information baked in and people can just cope with that.

[originally from svn r10262]
2014-09-24 10:33:13 +00:00

157 lines
5.5 KiB
Python
Executable File

#! /usr/bin/env python
# Convert OpenSSH known_hosts and known_hosts2 files to "new format" PuTTY
# host keys.
# usage:
# kh2reg.py [ --win ] known_hosts1 2 3 4 ... > hosts.reg
# Creates a Windows .REG file (double-click to install).
# kh2reg.py --unix known_hosts1 2 3 4 ... > sshhostkeys
# Creates data suitable for storing in ~/.putty/sshhostkeys (Unix).
# Line endings are someone else's problem as is traditional.
# Developed for Python 1.5.2.
import fileinput
import base64
import struct
import string
import re
import sys
import getopt
def winmungestr(s):
"Duplicate of PuTTY's mungestr() in winstore.c:1.10 for Registry keys"
candot = 0
r = ""
for c in s:
if c in ' \*?%~' or ord(c)<ord(' ') or (c == '.' and not candot):
r = r + ("%%%02X" % ord(c))
else:
r = r + c
candot = 1
return r
def strtolong(s):
"Convert arbitrary-length big-endian binary data to a Python long"
bytes = struct.unpack(">%luB" % len(s), s)
return reduce ((lambda a, b: (long(a) << 8) + long(b)), bytes)
def longtohex(n):
"""Convert long int to lower-case hex.
Ick, Python (at least in 1.5.2) doesn't appear to have a way to
turn a long int into an unadorned hex string -- % gets upset if the
number is too big, and raw hex() uses uppercase (sometimes), and
adds unwanted "0x...L" around it."""
plain=string.lower(re.match(r"0x([0-9A-Fa-f]*)l?$", hex(n), re.I).group(1))
return "0x" + plain
output_type = 'windows'
try:
optlist, args = getopt.getopt(sys.argv[1:], '', [ 'win', 'unix' ])
if filter(lambda x: x[0] == '--unix', optlist):
output_type = 'unix'
except getopt.error, e:
sys.stderr.write(str(e) + "\n")
sys.exit(1)
if output_type == 'windows':
# Output REG file header.
sys.stdout.write("""REGEDIT4
[HKEY_CURRENT_USER\Software\SimonTatham\PuTTY\SshHostKeys]
""")
# Now process all known_hosts input.
for line in fileinput.input(args):
try:
# Remove leading/trailing whitespace (should zap CR and LF)
line = string.strip (line)
# Skip blanks and comments
if line == '' or line[0] == '#':
raise "Skipping input line"
# Split line on spaces.
fields = string.split (line, ' ')
# Common fields
hostpat = fields[0]
magicnumbers = [] # placeholder
keytype = "" # placeholder
# Grotty heuristic to distinguish known_hosts from known_hosts2:
# is second field entirely decimal digits?
if re.match (r"\d*$", fields[1]):
# Treat as SSH-1-type host key.
# Format: hostpat bits10 exp10 mod10 comment...
# (PuTTY doesn't store the number of bits.)
magicnumbers = map (long, fields[2:4])
keytype = "rsa"
else:
# Treat as SSH-2-type host key.
# Format: hostpat keytype keyblob64 comment...
sshkeytype, blob = fields[1], base64.decodestring (fields[2])
# 'blob' consists of a number of
# uint32 N (big-endian)
# uint8[N] field_data
subfields = []
while blob:
sizefmt = ">L"
(size,) = struct.unpack (sizefmt, blob[0:4])
size = int(size) # req'd for slicage
(data,) = struct.unpack (">%lus" % size, blob[4:size+4])
subfields.append(data)
blob = blob [struct.calcsize(sizefmt) + size : ]
# The first field is keytype again, and the rest we can treat as
# an opaque list of bignums (same numbers and order as stored
# by PuTTY). (currently embedded keytype is ignored entirely)
magicnumbers = map (strtolong, subfields[1:])
# Translate key type into something PuTTY can use.
if sshkeytype == "ssh-rsa": keytype = "rsa2"
elif sshkeytype == "ssh-dss": keytype = "dss"
else:
raise "Unknown SSH key type", sshkeytype
# Now print out one line per host pattern, discarding wildcards.
for host in string.split (hostpat, ','):
if re.search (r"[*?!]", host):
sys.stderr.write("Skipping wildcard host pattern '%s'\n"
% host)
continue
elif re.match (r"\|", host):
sys.stderr.write("Skipping hashed hostname '%s'\n" % host)
continue
else:
m = re.match (r"\[([^]]*)\]:(\d*)$", host)
if m:
(host, port) = m.group(1,2)
port = int(port)
else:
port = 22
# Slightly bizarre output key format: 'type@port:hostname'
# XXX: does PuTTY do anything useful with literal IP[v4]s?
key = keytype + ("@%d:%s" % (port, host))
value = string.join (map (longtohex, magicnumbers), ',')
if output_type == 'unix':
# Unix format.
sys.stdout.write('%s %s\n' % (key, value))
else:
# Windows format.
# XXX: worry about double quotes?
sys.stdout.write("\"%s\"=\"%s\"\n"
% (winmungestr(key), value))
except "Unknown SSH key type", k:
sys.stderr.write("Unknown SSH key type '%s', skipping\n" % k)
except "Skipping input line":
pass