Revision 07b0130f

b/vncauthproxy/d3des.py
1
#!/usr/bin/env python
2
##
3
##  d3des.py - DES implementation
4
##
5
##  Copyright (c) 2009 by Yusuke Shinyama
6
##
7

  
8
# This is a Python rewrite of d3des.c by Richard Outerbridge.
9
#
10
# I referred to the original VNC viewer code for the changes that
11
# is necessary to maintain the exact behavior of the VNC protocol.
12
# Two constants and two functions were added to the original d3des
13
# code.  These added parts were written in Python and marked
14
# below.  I believe that the added parts do not make this program
15
# a "derivative work" of the VNC viewer (which is GPL'ed and
16
# written in C), but if there's any problem, let me know.
17
#
18
# Yusuke Shinyama (yusuke at cs dot nyu dot edu)
19

  
20

  
21
#  D3DES (V5.09) -
22
#
23
#  A portable, public domain, version of the Data Encryption Standard.
24
#
25
#  Written with Symantec's THINK (Lightspeed) C by Richard Outerbridge.
26
#  Thanks to: Dan Hoey for his excellent Initial and Inverse permutation
27
#  code;  Jim Gillogly & Phil Karn for the DES key schedule code; Dennis
28
#  Ferguson, Eric Young and Dana How for comparing notes; and Ray Lau,
29
#  for humouring me on.
30
#
31
#  Copyright (c) 1988,1989,1990,1991,1992 by Richard Outerbridge.
32
#  (GEnie : OUTER; CIS : [71755,204]) Graven Imagery, 1992.
33
#
34

  
35
from struct import pack, unpack
36

  
37

  
38
###################################################
39
###
40
###  start: changes made for VNC.
41
###
42

  
43
# This constant was taken from vncviewer/rfb/vncauth.c:
44
vnckey = [ 23,82,107,6,35,78,88,7 ]
45

  
46
# This is a departure from the original code.
47
#bytebit = [ 0200, 0100, 040, 020, 010, 04, 02, 01 ] # original
48
bytebit = [ 01, 02, 04, 010, 020, 040, 0100, 0200 ] # VNC version
49

  
50
# two password functions for VNC protocol.
51
def decrypt_passwd(data):
52
    dk = deskey(pack('8B', *vnckey), True)
53
    return desfunc(data, dk)
54

  
55
def generate_response(passwd, challange):
56
    ek = deskey((passwd+'\x00'*8)[:8], False)
57
    return desfunc(challange[:8], ek) + desfunc(challange[8:], ek)
58

  
59
###
60
###  end: changes made for VNC.
61
###
62
###################################################
63

  
64

  
65
bigbyte = [
66
  0x800000L,    0x400000L,      0x200000L,      0x100000L,
67
  0x80000L,     0x40000L,       0x20000L,       0x10000L,
68
  0x8000L,      0x4000L,        0x2000L,        0x1000L,
69
  0x800L,       0x400L,         0x200L,         0x100L,
70
  0x80L,        0x40L,          0x20L,          0x10L,
71
  0x8L,         0x4L,           0x2L,           0x1L
72
  ]
73

  
74
# Use the key schedule specified in the Standard (ANSI X3.92-1981).
75

  
76
pc1 = [
77
  56, 48, 40, 32, 24, 16,  8,    0, 57, 49, 41, 33, 25, 17,
78
   9,  1, 58, 50, 42, 34, 26,   18, 10,  2, 59, 51, 43, 35,
79
  62, 54, 46, 38, 30, 22, 14,    6, 61, 53, 45, 37, 29, 21,
80
  13,  5, 60, 52, 44, 36, 28,   20, 12,  4, 27, 19, 11,  3
81
  ]
82

  
83
totrot = [ 1,2,4,6,8,10,12,14,15,17,19,21,23,25,27,28 ]
84

  
85
pc2 = [
86
  13, 16, 10, 23,  0,  4,  2, 27, 14,  5, 20,  9,
87
  22, 18, 11,  3, 25,  7, 15,  6, 26, 19, 12,  1,
88
  40, 51, 30, 36, 46, 54, 29, 39, 50, 44, 32, 47,
89
  43, 48, 38, 55, 33, 52, 45, 41, 49, 35, 28, 31
90
  ]
91

  
92
def deskey(key, decrypt):      # Thanks to James Gillogly & Phil Karn!
93
    key = unpack('8B', key)
94

  
95
    pc1m = [0]*56
96
    pcr = [0]*56
97
    kn = [0L]*32
98

  
99
    for j in range(56):
100
        l = pc1[j]
101
        m = l & 07
102
        if key[l >> 3] & bytebit[m]:
103
            pc1m[j] = 1
104
        else:
105
            pc1m[j] = 0
106

  
107
    for i in range(16):
108
        if decrypt:
109
            m = (15 - i) << 1
110
        else:
111
            m = i << 1
112
        n = m + 1
113
        kn[m] = kn[n] = 0L
114
        for j in range(28):
115
            l = j + totrot[i]
116
            if l < 28:
117
                pcr[j] = pc1m[l]
118
            else:
119
                pcr[j] = pc1m[l - 28]
120
        for j in range(28, 56):
121
            l = j + totrot[i]
122
            if l < 56:
123
                pcr[j] = pc1m[l]
124
            else:
125
                pcr[j] = pc1m[l - 28]
126
        for j in range(24):
127
            if pcr[pc2[j]]:
128
                kn[m] |= bigbyte[j]
129
            if pcr[pc2[j+24]]:
130
                kn[n] |= bigbyte[j]
131

  
132
    return cookey(kn)
133

  
134
def cookey(raw):
135
    key = []
136
    for i in range(0, 32, 2):
137
        (raw0, raw1) = (raw[i], raw[i+1])
138
        k  = (raw0 & 0x00fc0000L) << 6
139
        k |= (raw0 & 0x00000fc0L) << 10
140
        k |= (raw1 & 0x00fc0000L) >> 10
141
        k |= (raw1 & 0x00000fc0L) >> 6
142
        key.append(k)
143
        k  = (raw0 & 0x0003f000L) << 12
144
        k |= (raw0 & 0x0000003fL) << 16
145
        k |= (raw1 & 0x0003f000L) >> 4
146
        k |= (raw1 & 0x0000003fL)
147
        key.append(k)
148
    return key
149

  
150
SP1 = [
151
  0x01010400L, 0x00000000L, 0x00010000L, 0x01010404L,
152
  0x01010004L, 0x00010404L, 0x00000004L, 0x00010000L,
153
  0x00000400L, 0x01010400L, 0x01010404L, 0x00000400L,
154
  0x01000404L, 0x01010004L, 0x01000000L, 0x00000004L,
155
  0x00000404L, 0x01000400L, 0x01000400L, 0x00010400L,
156
  0x00010400L, 0x01010000L, 0x01010000L, 0x01000404L,
157
  0x00010004L, 0x01000004L, 0x01000004L, 0x00010004L,
158
  0x00000000L, 0x00000404L, 0x00010404L, 0x01000000L,
159
  0x00010000L, 0x01010404L, 0x00000004L, 0x01010000L,
160
  0x01010400L, 0x01000000L, 0x01000000L, 0x00000400L,
161
  0x01010004L, 0x00010000L, 0x00010400L, 0x01000004L,
162
  0x00000400L, 0x00000004L, 0x01000404L, 0x00010404L,
163
  0x01010404L, 0x00010004L, 0x01010000L, 0x01000404L,
164
  0x01000004L, 0x00000404L, 0x00010404L, 0x01010400L,
165
  0x00000404L, 0x01000400L, 0x01000400L, 0x00000000L,
166
  0x00010004L, 0x00010400L, 0x00000000L, 0x01010004L
167
  ]
168

  
169
SP2 = [
170
  0x80108020L, 0x80008000L, 0x00008000L, 0x00108020L,
171
  0x00100000L, 0x00000020L, 0x80100020L, 0x80008020L,
172
  0x80000020L, 0x80108020L, 0x80108000L, 0x80000000L,
173
  0x80008000L, 0x00100000L, 0x00000020L, 0x80100020L,
174
  0x00108000L, 0x00100020L, 0x80008020L, 0x00000000L,
175
  0x80000000L, 0x00008000L, 0x00108020L, 0x80100000L,
176
  0x00100020L, 0x80000020L, 0x00000000L, 0x00108000L,
177
  0x00008020L, 0x80108000L, 0x80100000L, 0x00008020L,
178
  0x00000000L, 0x00108020L, 0x80100020L, 0x00100000L,
179
  0x80008020L, 0x80100000L, 0x80108000L, 0x00008000L,
180
  0x80100000L, 0x80008000L, 0x00000020L, 0x80108020L,
181
  0x00108020L, 0x00000020L, 0x00008000L, 0x80000000L,
182
  0x00008020L, 0x80108000L, 0x00100000L, 0x80000020L,
183
  0x00100020L, 0x80008020L, 0x80000020L, 0x00100020L,
184
  0x00108000L, 0x00000000L, 0x80008000L, 0x00008020L,
185
  0x80000000L, 0x80100020L, 0x80108020L, 0x00108000L
186
  ]
187

  
188
SP3 = [
189
  0x00000208L, 0x08020200L, 0x00000000L, 0x08020008L,
190
  0x08000200L, 0x00000000L, 0x00020208L, 0x08000200L,
191
  0x00020008L, 0x08000008L, 0x08000008L, 0x00020000L,
192
  0x08020208L, 0x00020008L, 0x08020000L, 0x00000208L,
193
  0x08000000L, 0x00000008L, 0x08020200L, 0x00000200L,
194
  0x00020200L, 0x08020000L, 0x08020008L, 0x00020208L,
195
  0x08000208L, 0x00020200L, 0x00020000L, 0x08000208L,
196
  0x00000008L, 0x08020208L, 0x00000200L, 0x08000000L,
197
  0x08020200L, 0x08000000L, 0x00020008L, 0x00000208L,
198
  0x00020000L, 0x08020200L, 0x08000200L, 0x00000000L,
199
  0x00000200L, 0x00020008L, 0x08020208L, 0x08000200L,
200
  0x08000008L, 0x00000200L, 0x00000000L, 0x08020008L,
201
  0x08000208L, 0x00020000L, 0x08000000L, 0x08020208L,
202
  0x00000008L, 0x00020208L, 0x00020200L, 0x08000008L,
203
  0x08020000L, 0x08000208L, 0x00000208L, 0x08020000L,
204
  0x00020208L, 0x00000008L, 0x08020008L, 0x00020200L
205
  ]
206

  
207
SP4 = [
208
  0x00802001L, 0x00002081L, 0x00002081L, 0x00000080L,
209
  0x00802080L, 0x00800081L, 0x00800001L, 0x00002001L,
210
  0x00000000L, 0x00802000L, 0x00802000L, 0x00802081L,
211
  0x00000081L, 0x00000000L, 0x00800080L, 0x00800001L,
212
  0x00000001L, 0x00002000L, 0x00800000L, 0x00802001L,
213
  0x00000080L, 0x00800000L, 0x00002001L, 0x00002080L,
214
  0x00800081L, 0x00000001L, 0x00002080L, 0x00800080L,
215
  0x00002000L, 0x00802080L, 0x00802081L, 0x00000081L,
216
  0x00800080L, 0x00800001L, 0x00802000L, 0x00802081L,
217
  0x00000081L, 0x00000000L, 0x00000000L, 0x00802000L,
218
  0x00002080L, 0x00800080L, 0x00800081L, 0x00000001L,
219
  0x00802001L, 0x00002081L, 0x00002081L, 0x00000080L,
220
  0x00802081L, 0x00000081L, 0x00000001L, 0x00002000L,
221
  0x00800001L, 0x00002001L, 0x00802080L, 0x00800081L,
222
  0x00002001L, 0x00002080L, 0x00800000L, 0x00802001L,
223
  0x00000080L, 0x00800000L, 0x00002000L, 0x00802080L
224
  ]
225

  
226
SP5 = [
227
  0x00000100L, 0x02080100L, 0x02080000L, 0x42000100L,
228
  0x00080000L, 0x00000100L, 0x40000000L, 0x02080000L,
229
  0x40080100L, 0x00080000L, 0x02000100L, 0x40080100L,
230
  0x42000100L, 0x42080000L, 0x00080100L, 0x40000000L,
231
  0x02000000L, 0x40080000L, 0x40080000L, 0x00000000L,
232
  0x40000100L, 0x42080100L, 0x42080100L, 0x02000100L,
233
  0x42080000L, 0x40000100L, 0x00000000L, 0x42000000L,
234
  0x02080100L, 0x02000000L, 0x42000000L, 0x00080100L,
235
  0x00080000L, 0x42000100L, 0x00000100L, 0x02000000L,
236
  0x40000000L, 0x02080000L, 0x42000100L, 0x40080100L,
237
  0x02000100L, 0x40000000L, 0x42080000L, 0x02080100L,
238
  0x40080100L, 0x00000100L, 0x02000000L, 0x42080000L,
239
  0x42080100L, 0x00080100L, 0x42000000L, 0x42080100L,
240
  0x02080000L, 0x00000000L, 0x40080000L, 0x42000000L,
241
  0x00080100L, 0x02000100L, 0x40000100L, 0x00080000L,
242
  0x00000000L, 0x40080000L, 0x02080100L, 0x40000100L
243
  ]
244

  
245
SP6 = [
246
  0x20000010L, 0x20400000L, 0x00004000L, 0x20404010L,
247
  0x20400000L, 0x00000010L, 0x20404010L, 0x00400000L,
248
  0x20004000L, 0x00404010L, 0x00400000L, 0x20000010L,
249
  0x00400010L, 0x20004000L, 0x20000000L, 0x00004010L,
250
  0x00000000L, 0x00400010L, 0x20004010L, 0x00004000L,
251
  0x00404000L, 0x20004010L, 0x00000010L, 0x20400010L,
252
  0x20400010L, 0x00000000L, 0x00404010L, 0x20404000L,
253
  0x00004010L, 0x00404000L, 0x20404000L, 0x20000000L,
254
  0x20004000L, 0x00000010L, 0x20400010L, 0x00404000L,
255
  0x20404010L, 0x00400000L, 0x00004010L, 0x20000010L,
256
  0x00400000L, 0x20004000L, 0x20000000L, 0x00004010L,
257
  0x20000010L, 0x20404010L, 0x00404000L, 0x20400000L,
258
  0x00404010L, 0x20404000L, 0x00000000L, 0x20400010L,
259
  0x00000010L, 0x00004000L, 0x20400000L, 0x00404010L,
260
  0x00004000L, 0x00400010L, 0x20004010L, 0x00000000L,
261
  0x20404000L, 0x20000000L, 0x00400010L, 0x20004010L
262
  ]
263

  
264
SP7 = [
265
  0x00200000L, 0x04200002L, 0x04000802L, 0x00000000L,
266
  0x00000800L, 0x04000802L, 0x00200802L, 0x04200800L,
267
  0x04200802L, 0x00200000L, 0x00000000L, 0x04000002L,
268
  0x00000002L, 0x04000000L, 0x04200002L, 0x00000802L,
269
  0x04000800L, 0x00200802L, 0x00200002L, 0x04000800L,
270
  0x04000002L, 0x04200000L, 0x04200800L, 0x00200002L,
271
  0x04200000L, 0x00000800L, 0x00000802L, 0x04200802L,
272
  0x00200800L, 0x00000002L, 0x04000000L, 0x00200800L,
273
  0x04000000L, 0x00200800L, 0x00200000L, 0x04000802L,
274
  0x04000802L, 0x04200002L, 0x04200002L, 0x00000002L,
275
  0x00200002L, 0x04000000L, 0x04000800L, 0x00200000L,
276
  0x04200800L, 0x00000802L, 0x00200802L, 0x04200800L,
277
  0x00000802L, 0x04000002L, 0x04200802L, 0x04200000L,
278
  0x00200800L, 0x00000000L, 0x00000002L, 0x04200802L,
279
  0x00000000L, 0x00200802L, 0x04200000L, 0x00000800L,
280
  0x04000002L, 0x04000800L, 0x00000800L, 0x00200002L
281
  ]
282

  
283
SP8 = [
284
  0x10001040L, 0x00001000L, 0x00040000L, 0x10041040L,
285
  0x10000000L, 0x10001040L, 0x00000040L, 0x10000000L,
286
  0x00040040L, 0x10040000L, 0x10041040L, 0x00041000L,
287
  0x10041000L, 0x00041040L, 0x00001000L, 0x00000040L,
288
  0x10040000L, 0x10000040L, 0x10001000L, 0x00001040L,
289
  0x00041000L, 0x00040040L, 0x10040040L, 0x10041000L,
290
  0x00001040L, 0x00000000L, 0x00000000L, 0x10040040L,
291
  0x10000040L, 0x10001000L, 0x00041040L, 0x00040000L,
292
  0x00041040L, 0x00040000L, 0x10041000L, 0x00001000L,
293
  0x00000040L, 0x10040040L, 0x00001000L, 0x00041040L,
294
  0x10001000L, 0x00000040L, 0x10000040L, 0x10040000L,
295
  0x10040040L, 0x10000000L, 0x00040000L, 0x10001040L,
296
  0x00000000L, 0x10041040L, 0x00040040L, 0x10000040L,
297
  0x10040000L, 0x10001000L, 0x10001040L, 0x00000000L,
298
  0x10041040L, 0x00041000L, 0x00041000L, 0x00001040L,
299
  0x00001040L, 0x00040040L, 0x10000000L, 0x10041000L
300
  ]
301

  
302
def desfunc(block, keys):
303
    (leftt, right) = unpack('>II', block)
304

  
305
    work = ((leftt >> 4) ^ right) & 0x0f0f0f0fL
306
    right ^= work
307
    leftt ^= (work << 4)
308
    work = ((leftt >> 16) ^ right) & 0x0000ffffL
309
    right ^= work
310
    leftt ^= (work << 16)
311
    work = ((right >> 2) ^ leftt) & 0x33333333L
312
    leftt ^= work
313
    right ^= (work << 2)
314
    work = ((right >> 8) ^ leftt) & 0x00ff00ffL
315
    leftt ^= work
316
    right ^= (work << 8)
317
    right = ((right << 1) | ((right >> 31) & 1L)) & 0xffffffffL
318
    work = (leftt ^ right) & 0xaaaaaaaaL
319
    leftt ^= work
320
    right ^= work
321
    leftt = ((leftt << 1) | ((leftt >> 31) & 1L)) & 0xffffffffL
322

  
323
    for i in range(0, 32, 4):
324
        work  = (right << 28) | (right >> 4)
325
        work ^= keys[i]
326
        fval  = SP7[ work            & 0x3fL]
327
        fval |= SP5[(work >>  8) & 0x3fL]
328
        fval |= SP3[(work >> 16) & 0x3fL]
329
        fval |= SP1[(work >> 24) & 0x3fL]
330
        work  = right ^ keys[i+1]
331
        fval |= SP8[ work            & 0x3fL]
332
        fval |= SP6[(work >>  8) & 0x3fL]
333
        fval |= SP4[(work >> 16) & 0x3fL]
334
        fval |= SP2[(work >> 24) & 0x3fL]
335
        leftt ^= fval
336
        work  = (leftt << 28) | (leftt >> 4)
337
        work ^= keys[i+2]
338
        fval  = SP7[ work            & 0x3fL]
339
        fval |= SP5[(work >>  8) & 0x3fL]
340
        fval |= SP3[(work >> 16) & 0x3fL]
341
        fval |= SP1[(work >> 24) & 0x3fL]
342
        work  = leftt ^ keys[i+3]
343
        fval |= SP8[ work            & 0x3fL]
344
        fval |= SP6[(work >>  8) & 0x3fL]
345
        fval |= SP4[(work >> 16) & 0x3fL]
346
        fval |= SP2[(work >> 24) & 0x3fL]
347
        right ^= fval
348

  
349
    right = (right << 31) | (right >> 1)
350
    work = (leftt ^ right) & 0xaaaaaaaaL
351
    leftt ^= work
352
    right ^= work
353
    leftt = (leftt << 31) | (leftt >> 1)
354
    work = ((leftt >> 8) ^ right) & 0x00ff00ffL
355
    right ^= work
356
    leftt ^= (work << 8)
357
    work = ((leftt >> 2) ^ right) & 0x33333333L
358
    right ^= work
359
    leftt ^= (work << 2)
360
    work = ((right >> 16) ^ leftt) & 0x0000ffffL
361
    leftt ^= work
362
    right ^= (work << 16)
363
    work = ((right >> 4) ^ leftt) & 0x0f0f0f0fL
364
    leftt ^= work
365
    right ^= (work << 4)
366

  
367
    leftt &= 0xffffffffL
368
    right &= 0xffffffffL
369
    return pack('>II', right, leftt)
370

  
371

  
372
# test
373
if __name__ == '__main__':
374
    key = '0123456789abcdef'.decode('hex')
375
    plain = '0123456789abcdef'.decode('hex')
376
    cipher = '6e09a37726dd560c'.decode('hex')
377
    ek = deskey(key, False)
378
    dk = deskey(key, True)
379
    assert desfunc(plain, ek) == cipher
380
    assert desfunc(desfunc(plain, ek), dk) == plain
381
    assert desfunc(desfunc(plain, dk), ek) == plain
382
    print 'test succeeded.'
b/vncauthproxy/rfb.py
1
#
2
#
3

  
4
# Copyright (c) 2010 GRNET SA
5
#
6
# This program is free software; you can redistribute it and/or modify
7
# it under the terms of the GNU General Public License as published by
8
# the Free Software Foundation; either version 2 of the License, or
9
# (at your option) any later version.
10
#
11
# This program is distributed in the hope that it will be useful, but
12
# WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14
# General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19
# 02110-1301, USA.
20

  
21
import d3des
22
from struct import pack, unpack
23

  
24
RFB_AUTH_SUCCESS = 0
25
RFB_AUTH_ERROR = 1
26
RFB_AUTHTYPE_VNC = 2
27
RFB_AUTHTYPE_NONE = 1
28
RFB_AUTHTYPE_ERROR = 0
29
RFB_SUPPORTED_AUTHTYPES = [RFB_AUTHTYPE_NONE, RFB_AUTHTYPE_VNC]
30
RFB_VERSION_3_8 = "RFB 003.008"
31
RFB_VERSION_3_7 = "RFB 003.007"
32
RFB_VERSION_3_3 = "RFB 003.003"
33
RFB_VALID_VERSIONS = [
34
#    RFB_VERSION_3_3,
35
#    RFB_VERSION_3_7,
36
    RFB_VERSION_3_8,
37
]
38

  
39
class RfbError(Exception):
40
    pass
41

  
42
def check_version(version):
43
    return version.strip()[:11] in RFB_VALID_VERSIONS
44

  
45
def make_auth_request(*auth_methods):
46
    auth_methods = set(auth_methods)
47
    for method in auth_methods:
48
        if method not in RFB_SUPPORTED_AUTHTYPES:
49
            raise RfbError("Unsupported authentication type: %d" % method)
50
    return pack('B' + 'B' * len(auth_methods), len(auth_methods), *auth_methods)
51

  
52
def parse_auth_request(request):
53
    length = unpack('B', request[0])[0]
54
    if length == 0:
55
        return []
56
    return unpack('B' * length, request[1:])
57

  
58
def parse_client_authtype(authtype):
59
    return unpack('B', authtype[0])[0]
60

  
61
def from_u32(val):
62
    return unpack('>L', val)[0]
63

  
64
def to_u32(val):
65
    return pack('>L', val)
66

  
67
def from_u8(val):
68
    return unpack('B', val)[0]
69

  
70
def to_u8(val):
71
    return pack('B', val)
72

  
73
def check_password(challenge, response, password):
74
    return d3des.generate_response((password + '\0' * 8 )[:8],
75
                                   challenge) == response
b/vncauthproxy/vncauthproxy.py
1
#!/usr/bin/env python
2
#
3

  
4
# Copyright (c) 2010 GRNET SA
5
#
6
# This program is free software; you can redistribute it and/or modify
7
# it under the terms of the GNU General Public License as published by
8
# the Free Software Foundation; either version 2 of the License, or
9
# (at your option) any later version.
10
#
11
# This program is distributed in the hope that it will be useful, but
12
# WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14
# General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19
# 02110-1301, USA.
20

  
21

  
22
import os
23
import sys
24
import logging
25
import gevent
26

  
27
import rfb
28

  
29
from gevent import socket
30
from gevent.select import select
31

  
32
class VncAuthProxy(gevent.Greenlet):
33
    """
34
    Simple class implementing a VNC Forwarder with MITM authentication as a
35
    Greenlet
36

  
37
    VncAuthProxy forwards VNC traffic from a specified port of the local host
38
    to a specified remote host:port. Furthermore, it implements VNC
39
    Authentication, intercepting the client/server handshake and asking the
40
    client for authentication even if the backend requires none.
41

  
42
    It is primarily intended for use in virtualization environments, as a VNC
43
    ``switch''.
44

  
45
    """
46
    id = 1
47

  
48
    def __init__(self, sport, daddr, dport, password, connect_timeout=30):
49
        """
50
        @type sport: int
51
        @param sport: source port
52
        @type daddr: str
53
        @param daddr: destination address (IPv4, IPv6 or hostname)
54
        @type dport: int
55
        @param dport: destination port
56
        @type password: str
57
        @param password: password to request from the client
58
        @type connect_timeout: int
59
        @param connect_timeout: how long to wait for client connections
60
                                (seconds)
61

  
62
        """
63
        gevent.Greenlet.__init__(self)
64
        self.id = VncAuthProxy.id
65
        VncAuthProxy.id += 1
66
        self.sport = sport
67
        self.daddr = daddr
68
        self.dport = dport
69
        self.password = password
70
        self.log = logging
71
        self.server = None
72
        self.client = None
73
        self.timeout = connect_timeout
74

  
75
    def _cleanup(self):
76
        """Close all active sockets and exit gracefully"""
77
        if self.server:
78
            self.server.close()
79
        if self.client:
80
            self.client.close()
81
        raise gevent.GreenletExit
82

  
83
    def info(self, msg):
84
        logging.info("[C%d] %s" % (self.id, msg))
85

  
86
    def debug(self, msg):
87
        logging.debug("[C%d] %s" % (self.id, msg))
88

  
89
    def warn(self, msg):
90
        logging.warn("[C%d] %s" % (self.id, msg))
91

  
92
    def error(self, msg):
93
        logging.error("[C%d] %s" % (self.id, msg))
94

  
95
    def critical(self, msg):
96
        logging.critical("[C%d] %s" % (self.id, msg))
97

  
98
    def __str__(self):
99
        return "VncAuthProxy: %d -> %s:%d" % (self.sport, self.daddr, self.dport)
100

  
101
    def _forward(self, source, dest):
102
        """
103
        Forward traffic from source to dest
104

  
105
        @type source: socket
106
        @param source: source socket
107
        @type dest: socket
108
        @param dest: destination socket
109

  
110
        """
111

  
112
        while True:
113
            d = source.recv(8096)
114
            if d == '':
115
                if source == self.client:
116
                    self.info("Client connection closed")
117
                else:
118
                    self.info("Server connection closed")
119
                break
120
            dest.sendall(d)
121
        source.close()
122
        dest.close()
123

  
124

  
125
    def _handshake(self):
126
        """
127
        Perform handshake/authentication with a connecting client
128

  
129
        Outline:
130
        1. Client connects
131
        2. We fake RFB 3.8 protocol and require VNC authentication
132
        3. Client accepts authentication method
133
        4. We send an authentication challenge
134
        5. Client sends the authentication response
135
        6. We check the authentication
136
        7. We initiate a connection with the backend server and perform basic
137
           RFB 3.8 handshake with it.
138
        8. If the above is successful, "bridge" both connections through two
139
           "fowrarder" greenlets.
140

  
141
        """
142
        self.client.send(rfb.RFB_VERSION_3_8 + "\n")
143
        client_version = self.client.recv(1024)
144
        if not rfb.check_version(client_version):
145
            self.error("Invalid version: %s" % client_version)
146
            self._cleanup()
147
        self.debug("Requesting authentication")
148
        auth_request = rfb.make_auth_request(rfb.RFB_AUTHTYPE_VNC)
149
        self.client.send(auth_request)
150
        res = self.client.recv(1024)
151
        type = rfb.parse_client_authtype(res)
152
        if type == rfb.RFB_AUTHTYPE_ERROR:
153
            self.warn("Client refused authentication: %s" % res[1:])
154
        else:
155
            self.debug("Client requested authtype %x" % type)
156

  
157
        if type != rfb.RFB_AUTHTYPE_VNC:
158
            self.error("Wrong auth type: %d" % type)
159
            self.client.send(rfb.to_u32(rfb.RFB_AUTH_ERROR))
160
            self._cleanup()
161

  
162
        # Generate the challenge
163
        challenge = os.urandom(16)
164
        self.client.send(challenge)
165
        response = self.client.recv(1024)
166
        if len(response) != 16:
167
            self.error("Wrong response length %d, should be 16" % len(response))
168
            self._cleanup()
169

  
170
        if rfb.check_password(challenge, response, password):
171
            self.debug("Authentication successful!")
172
        else:
173
            self.warn("Authentication failed")
174
            self.client.send(rfb.to_u32(rfb.RFB_AUTH_ERROR))
175
            self._cleanup()
176

  
177
        # Accept the authentication
178
        self.client.send(rfb.to_u32(rfb.RFB_AUTH_SUCCESS))
179

  
180
        # Try to connect to the server
181
        tries = 50
182

  
183
        while tries:
184
            tries -= 1
185

  
186
            # Initiate server connection
187
            for res in socket.getaddrinfo(self.daddr, self.dport, socket.AF_UNSPEC,
188
                                          socket.SOCK_STREAM, 0, socket.AI_PASSIVE):
189
                af, socktype, proto, canonname, sa = res
190
                try:
191
                    self.server = socket.socket(af, socktype, proto)
192
                except socket.error, msg:
193
                    self.server = None
194
                    continue
195

  
196
                try:
197
                    self.debug("Connecting to %s:%s" % sa[:2])
198
                    self.server.connect(sa)
199
                    self.debug("Connection to %s:%s successful" % sa[:2])
200
                except socket.error, msg:
201
                    self.server.close()
202
                    self.server = None
203
                    continue
204

  
205
                # We succesfully connected to the server
206
                tries = 0
207
                break
208

  
209
            # Wait and retry
210
            gevent.sleep(0.2)
211

  
212
        if self.server is None:
213
            self.error("Failed to connect to server")
214
            self._cleanup()
215

  
216
        version = self.server.recv(1024)
217
        if not rfb.check_version(version):
218
            self.error("Unsupported RFB version: %s" % version.strip())
219
            self._cleanup()
220

  
221
        self.server.send(rfb.RFB_VERSION_3_8 + "\n")
222

  
223
        res = self.server.recv(1024)
224
        types = rfb.parse_auth_request(res)
225
        if not types:
226
            self.error("Error handshaking with the server")
227
            self._cleanup()
228

  
229
        else:
230
            self.debug("Supported authentication types: %s" %
231
                           " ".join([str(x) for x in types]))
232

  
233
        if rfb.RFB_AUTHTYPE_NONE not in types:
234
            self.error("Error, server demands authentication")
235
            self._cleanup()
236

  
237
        self.server.send(rfb.to_u8(rfb.RFB_AUTHTYPE_NONE))
238

  
239
        # Check authentication response
240
        res = self.server.recv(4)
241
        res = rfb.from_u32(res)
242

  
243
        if res != 0:
244
            self.error("Authentication error")
245
            self._cleanup()
246

  
247
        # Bridge client/server connections
248
        self.workers = [gevent.spawn(self._forward, self.client, self.server),
249
                        gevent.spawn(self._forward, self.server, self.client)]
250
        gevent.joinall(self.workers)
251

  
252
        del self.workers
253
        self._cleanup()
254

  
255
    def _run(self):
256
        sockets = []
257

  
258
        # Use two sockets, one for IPv4, one for IPv6. IPv4-to-IPv6 mapped
259
        # addresses do not work reliably everywhere (under linux it may have
260
        # been disabled in /proc/sys/net/ipv6/bind_ipv6_only).
261
        for res in socket.getaddrinfo(None, self.sport, socket.AF_UNSPEC,
262
                                      socket.SOCK_STREAM, 0, socket.AI_PASSIVE):
263
            af, socktype, proto, canonname, sa = res
264
            try:
265
                s = socket.socket(af, socktype, proto)
266
                if af == socket.AF_INET6:
267
                    # Bind v6 only when AF_INET6, otherwise either v4 or v6 bind
268
                    # will fail.
269
                    s.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 1)
270
            except socket.error, msg:
271
                s = None
272
                continue;
273

  
274
            try:
275
                s.bind(sa)
276
                s.listen(1)
277
                self.debug("Listening on %s:%d" % sa[:2])
278
            except socket.error, msg:
279
                self.error("Error binding to %s:%d: %s" %
280
                               (sa[0], sa[1], msg[1]))
281
                s.close()
282
                s = None
283
                continue
284

  
285
            if s:
286
                sockets.append(s)
287

  
288
        if not sockets:
289
            self.error("Failed to listen for connections")
290
            self._cleanup()
291

  
292
        self.log.debug("Waiting for client to connect")
293
        rlist, _, _ = select(sockets, [], [], timeout=self.timeout)
294

  
295
        if not rlist:
296
            self.info("Timed out, no connection after %d sec" % self.timeout)
297
            self._cleanup()
298

  
299
        for sock in rlist:
300
            self.client, addrinfo = sock.accept()
301
            self.info("Connection from %s:%d" % addrinfo[:2])
302

  
303
            # Close all listening sockets, we only want a one-shot connection
304
            # from a single client.
305
            for listener in sockets:
306
                listener.close()
307
            break
308

  
309
        self._handshake()
310

  
311

  
312
if __name__ == '__main__':
313
    from optparse import OptionParser
314

  
315
    parser = OptionParser()
316
    parser.add_option("-s", "--socket", dest="ctrl_socket",
317
                      help="UNIX socket path for control connections",
318
                      default="/tmp/vncproxy.sock",
319
                      metavar="PATH")
320
    parser.add_option("-d", "--debug", action="store_true", dest="debug",
321
                      help="Enable debugging information")
322
    parser.add_option("-l", "--log", dest="logfile", default=None,
323
                      help="Write log to FILE instead of stdout",
324
                      metavar="FILE")
325
    parser.add_option("-t", "--connect-timeout", dest="connect_timeout",
326
                      default=30, type="int", metavar="SECONDS",
327
                      help="How long to listen for clients to forward")
328

  
329
    (opts, args) = parser.parse_args(sys.argv[1:])
330

  
331
    lvl = logging.DEBUG if opts.debug else logging.INFO
332

  
333
    logging.basicConfig(level=lvl, filename=opts.logfile,
334
                        format="%(asctime)s %(levelname)s: %(message)s",
335
                        datefmt="%m/%d/%Y %H:%M:%S")
336

  
337
    if os.path.exists(opts.ctrl_socket):
338
        logging.critical("Socket '%s' already exists" % opts.ctrl_socket)
339
        sys.exit(1)
340

  
341
    # TODO: make this tunable? chgrp as well?
342
    old_umask = os.umask(0077)
343

  
344
    ctrl = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
345
    ctrl.bind(opts.ctrl_socket)
346

  
347
    os.umask(old_umask)
348

  
349
    ctrl.listen(1)
350
    logging.info("Initalized, waiting for control connections at %s" %
351
                 opts.ctrl_socket)
352

  
353
    while True:
354
        try:
355
            client, addr = ctrl.accept()
356
        except KeyboardInterrupt:
357
            break
358

  
359
        logging.info("New control connection")
360
        line = client.recv(1024).strip()
361
        try:
362
            # Control message format:
363
            # TODO: make this json-based?
364
            # TODO: support multiple forwardings in the same message?
365
            # <source_port>:<destination_address>:<destination_port>:<password>
366
            # <password> will be used for MITM authentication of clients
367
            # connecting to <source_port>, who will subsequently be forwarded
368
            # to a VNC server at <destination_address>:<destination_port>
369
            sport, daddr, dport, password = line.split(':', 3)
370
            logging.info("New forwarding [%d -> %s:%d]" %
371
                         (int(sport), daddr, int(dport)))
372
        except:
373
            logging.warn("Malformed request: %s" % line)
374
            client.send("FAILED\n")
375
            client.close()
376
            continue
377

  
378
        client.send("OK\n")
379
        VncAuthProxy.spawn(sport, daddr, dport, password, opts.connect_timeout)
380
        client.close()
381

  
382
    os.unlink(opts.ctrl_socket)
383
    sys.exit(0)

Also available in: Unified diff