OSDN Git Service

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