Sebastian Kuschel reports that pfd_closing can be called for a socket
[u/mdw/putty] / contrib / kh2reg.py
1 #! /usr/bin/env python
2
3 # $Id$
4 # Convert OpenSSH known_hosts and known_hosts2 files to "new format" PuTTY
5 # host keys.
6 # usage:
7 # kh2reg.py [ --win ] known_hosts1 2 3 4 ... > hosts.reg
8 # Creates a Windows .REG file (double-click to install).
9 # kh2reg.py --unix known_hosts1 2 3 4 ... > sshhostkeys
10 # Creates data suitable for storing in ~/.putty/sshhostkeys (Unix).
11 # Line endings are someone else's problem as is traditional.
12 # Developed for Python 1.5.2.
13
14 import fileinput
15 import base64
16 import struct
17 import string
18 import re
19 import sys
20 import getopt
21
22 def winmungestr(s):
23 "Duplicate of PuTTY's mungestr() in winstore.c:1.10 for Registry keys"
24 candot = 0
25 r = ""
26 for c in s:
27 if c in ' \*?%~' or ord(c)<ord(' ') or (c == '.' and not candot):
28 r = r + ("%%%02X" % ord(c))
29 else:
30 r = r + c
31 candot = 1
32 return r
33
34 def strtolong(s):
35 "Convert arbitrary-length big-endian binary data to a Python long"
36 bytes = struct.unpack(">%luB" % len(s), s)
37 return reduce ((lambda a, b: (long(a) << 8) + long(b)), bytes)
38
39 def longtohex(n):
40 """Convert long int to lower-case hex.
41
42 Ick, Python (at least in 1.5.2) doesn't appear to have a way to
43 turn a long int into an unadorned hex string -- % gets upset if the
44 number is too big, and raw hex() uses uppercase (sometimes), and
45 adds unwanted "0x...L" around it."""
46
47 plain=string.lower(re.match(r"0x([0-9A-Fa-f]*)l?$", hex(n), re.I).group(1))
48 return "0x" + plain
49
50 output_type = 'windows'
51
52 try:
53 optlist, args = getopt.getopt(sys.argv[1:], '', [ 'win', 'unix' ])
54 if filter(lambda x: x[0] == '--unix', optlist):
55 output_type = 'unix'
56 except getopt.error, e:
57 sys.stderr.write(str(e) + "\n")
58 sys.exit(1)
59
60 if output_type == 'windows':
61 # Output REG file header.
62 sys.stdout.write("""REGEDIT4
63
64 [HKEY_CURRENT_USER\Software\SimonTatham\PuTTY\SshHostKeys]
65 """)
66
67 # Now process all known_hosts input.
68 for line in fileinput.input(args):
69
70 try:
71 # Remove leading/trailing whitespace (should zap CR and LF)
72 line = string.strip (line)
73
74 # Skip blanks and comments
75 if line == '' or line[0] == '#':
76 raise "Skipping input line"
77
78 # Split line on spaces.
79 fields = string.split (line, ' ')
80
81 # Common fields
82 hostpat = fields[0]
83 magicnumbers = [] # placeholder
84 keytype = "" # placeholder
85
86 # Grotty heuristic to distinguish known_hosts from known_hosts2:
87 # is second field entirely decimal digits?
88 if re.match (r"\d*$", fields[1]):
89
90 # Treat as SSH-1-type host key.
91 # Format: hostpat bits10 exp10 mod10 comment...
92 # (PuTTY doesn't store the number of bits.)
93 magicnumbers = map (long, fields[2:4])
94 keytype = "rsa"
95
96 else:
97
98 # Treat as SSH-2-type host key.
99 # Format: hostpat keytype keyblob64 comment...
100 sshkeytype, blob = fields[1], base64.decodestring (fields[2])
101
102 # 'blob' consists of a number of
103 # uint32 N (big-endian)
104 # uint8[N] field_data
105 subfields = []
106 while blob:
107 sizefmt = ">L"
108 (size,) = struct.unpack (sizefmt, blob[0:4])
109 size = int(size) # req'd for slicage
110 (data,) = struct.unpack (">%lus" % size, blob[4:size+4])
111 subfields.append(data)
112 blob = blob [struct.calcsize(sizefmt) + size : ]
113
114 # The first field is keytype again, and the rest we can treat as
115 # an opaque list of bignums (same numbers and order as stored
116 # by PuTTY). (currently embedded keytype is ignored entirely)
117 magicnumbers = map (strtolong, subfields[1:])
118
119 # Translate key type into something PuTTY can use.
120 if sshkeytype == "ssh-rsa": keytype = "rsa2"
121 elif sshkeytype == "ssh-dss": keytype = "dss"
122 else:
123 raise "Unknown SSH key type", sshkeytype
124
125 # Now print out one line per host pattern, discarding wildcards.
126 for host in string.split (hostpat, ','):
127 if re.search (r"[*?!]", host):
128 sys.stderr.write("Skipping wildcard host pattern '%s'\n"
129 % host)
130 continue
131 elif re.match (r"\|", host):
132 sys.stderr.write("Skipping hashed hostname '%s'\n" % host)
133 continue
134 else:
135 m = re.match (r"\[([^]]*)\]:(\d*)$", host)
136 if m:
137 (host, port) = m.group(1,2)
138 port = int(port)
139 else:
140 port = 22
141 # Slightly bizarre output key format: 'type@port:hostname'
142 # XXX: does PuTTY do anything useful with literal IP[v4]s?
143 key = keytype + ("@%d:%s" % (port, host))
144 value = string.join (map (longtohex, magicnumbers), ',')
145 if output_type == 'unix':
146 # Unix format.
147 sys.stdout.write('%s %s\n' % (key, value))
148 else:
149 # Windows format.
150 # XXX: worry about double quotes?
151 sys.stdout.write("\"%s\"=\"%s\"\n"
152 % (winmungestr(key), value))
153
154 except "Unknown SSH key type", k:
155 sys.stderr.write("Unknown SSH key type '%s', skipping\n" % k)
156 except "Skipping input line":
157 pass