Revision 9a6eacb9

b/api/actions.py
75 75
    dport = console_data['port']
76 76
    passwd = random_password()
77 77

  
78
    request_vnc_forwarding(sport, daddr, dport, passwd)
78
    if not request_vnc_forwarding(sport, daddr, dport, passwd):
79
        raise ServiceUnavailable("Could not allocate VNC console port")
79 80
    vnc = { 'host': getfqdn(), 'port': sport, 'password': passwd }
80 81

  
81 82
    # Format to be reviewed by [verigak], FIXME
b/util/vapclient.py
35 35
    ctrl.connect(CTRL_SOCKET)
36 36
    ctrl.send(request)
37 37
    response = ctrl.recv(1024)
38
    if response == "OK":
38
    if response == "OK\n":
39 39
        return True
40 40
    else:
41 41
        return False
42 42

  
43 43
if __name__ == '__main__':
44
    request_forwarding(*sys.argv[1:])
44
    if not request_forwarding(*sys.argv[1:]):
45
	sys.exit(1)
/dev/null
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.'
/dev/null
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
/dev/null
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
DEFAULT_CTRL_SOCKET = "/tmp/vncproxy.sock"
22
DEFAULT_LOG_FILE = "/var/log/vncauthproxy/vncauthproxy.log"
23
DEFAULT_PID_FILE = "/var/run/vncauthproxy/vncauthproxy.pid"
24
DEFAULT_CONNECT_TIMEOUT = 30
25

  
26
import os
27
import sys
28
import logging
29
import gevent
30
import daemon
31
import daemon.pidlockfile
32

  
33
import rfb
34

  
35
from gevent import socket
36
from signal import SIGINT, SIGTERM
37
from gevent import signal
38
from gevent.select import select
39

  
40

  
41
class VncAuthProxy(gevent.Greenlet):
42
    """
43
    Simple class implementing a VNC Forwarder with MITM authentication as a
44
    Greenlet
45

  
46
    VncAuthProxy forwards VNC traffic from a specified port of the local host
47
    to a specified remote host:port. Furthermore, it implements VNC
48
    Authentication, intercepting the client/server handshake and asking the
49
    client for authentication even if the backend requires none.
50

  
51
    It is primarily intended for use in virtualization environments, as a VNC
52
    ``switch''.
53

  
54
    """
55
    id = 1
56

  
57
    def __init__(self, logger, sport, daddr, dport, password, connect_timeout):
58
        """
59
        @type logger: logging.Logger
60
        @param logger: the logger to use
61
        @type sport: int
62
        @param sport: source port
63
        @type daddr: str
64
        @param daddr: destination address (IPv4, IPv6 or hostname)
65
        @type dport: int
66
        @param dport: destination port
67
        @type password: str
68
        @param password: password to request from the client
69
        @type connect_timeout: int
70
        @param connect_timeout: how long to wait for client connections
71
                                (seconds)
72

  
73
        """
74
        gevent.Greenlet.__init__(self)
75
        self.id = VncAuthProxy.id
76
        VncAuthProxy.id += 1
77
        self.sport = sport
78
        self.daddr = daddr
79
        self.dport = dport
80
        self.password = password
81
        self.log = logger
82
        self.server = None
83
        self.client = None
84
        self.timeout = connect_timeout
85

  
86
    def _cleanup(self):
87
        """Close all active sockets and exit gracefully"""
88
        if self.server:
89
            self.server.close()
90
        if self.client:
91
            self.client.close()
92
        raise gevent.GreenletExit
93

  
94
    def info(self, msg):
95
        self.log.info("[C%d] %s" % (self.id, msg))
96

  
97
    def debug(self, msg):
98
        self.log.debug("[C%d] %s" % (self.id, msg))
99

  
100
    def warn(self, msg):
101
        self.log.warn("[C%d] %s" % (self.id, msg))
102

  
103
    def error(self, msg):
104
        self.log.error("[C%d] %s" % (self.id, msg))
105

  
106
    def critical(self, msg):
107
        self.log.critical("[C%d] %s" % (self.id, msg))
108

  
109
    def __str__(self):
110
        return "VncAuthProxy: %d -> %s:%d" % (self.sport, self.daddr, self.dport)
111

  
112
    def _forward(self, source, dest):
113
        """
114
        Forward traffic from source to dest
115

  
116
        @type source: socket
117
        @param source: source socket
118
        @type dest: socket
119
        @param dest: destination socket
120

  
121
        """
122

  
123
        while True:
124
            d = source.recv(8096)
125
            if d == '':
126
                if source == self.client:
127
                    self.info("Client connection closed")
128
                else:
129
                    self.info("Server connection closed")
130
                break
131
            dest.sendall(d)
132
        source.close()
133
        dest.close()
134

  
135

  
136
    def _handshake(self):
137
        """
138
        Perform handshake/authentication with a connecting client
139

  
140
        Outline:
141
        1. Client connects
142
        2. We fake RFB 3.8 protocol and require VNC authentication
143
        3. Client accepts authentication method
144
        4. We send an authentication challenge
145
        5. Client sends the authentication response
146
        6. We check the authentication
147
        7. We initiate a connection with the backend server and perform basic
148
           RFB 3.8 handshake with it.
149
        8. If the above is successful, "bridge" both connections through two
150
           "fowrarder" greenlets.
151

  
152
        """
153
        self.client.send(rfb.RFB_VERSION_3_8 + "\n")
154
        client_version = self.client.recv(1024)
155
        if not rfb.check_version(client_version):
156
            self.error("Invalid version: %s" % client_version)
157
            self._cleanup()
158
        self.debug("Requesting authentication")
159
        auth_request = rfb.make_auth_request(rfb.RFB_AUTHTYPE_VNC)
160
        self.client.send(auth_request)
161
        res = self.client.recv(1024)
162
        type = rfb.parse_client_authtype(res)
163
        if type == rfb.RFB_AUTHTYPE_ERROR:
164
            self.warn("Client refused authentication: %s" % res[1:])
165
        else:
166
            self.debug("Client requested authtype %x" % type)
167

  
168
        if type != rfb.RFB_AUTHTYPE_VNC:
169
            self.error("Wrong auth type: %d" % type)
170
            self.client.send(rfb.to_u32(rfb.RFB_AUTH_ERROR))
171
            self._cleanup()
172

  
173
        # Generate the challenge
174
        challenge = os.urandom(16)
175
        self.client.send(challenge)
176
        response = self.client.recv(1024)
177
        if len(response) != 16:
178
            self.error("Wrong response length %d, should be 16" % len(response))
179
            self._cleanup()
180

  
181
        if rfb.check_password(challenge, response, password):
182
            self.debug("Authentication successful!")
183
        else:
184
            self.warn("Authentication failed")
185
            self.client.send(rfb.to_u32(rfb.RFB_AUTH_ERROR))
186
            self._cleanup()
187

  
188
        # Accept the authentication
189
        self.client.send(rfb.to_u32(rfb.RFB_AUTH_SUCCESS))
190

  
191
        # Try to connect to the server
192
        tries = 50
193

  
194
        while tries:
195
            tries -= 1
196

  
197
            # Initiate server connection
198
            for res in socket.getaddrinfo(self.daddr, self.dport, socket.AF_UNSPEC,
199
                                          socket.SOCK_STREAM, 0, socket.AI_PASSIVE):
200
                af, socktype, proto, canonname, sa = res
201
                try:
202
                    self.server = socket.socket(af, socktype, proto)
203
                except socket.error, msg:
204
                    self.server = None
205
                    continue
206

  
207
                try:
208
                    self.debug("Connecting to %s:%s" % sa[:2])
209
                    self.server.connect(sa)
210
                    self.debug("Connection to %s:%s successful" % sa[:2])
211
                except socket.error, msg:
212
                    self.server.close()
213
                    self.server = None
214
                    continue
215

  
216
                # We succesfully connected to the server
217
                tries = 0
218
                break
219

  
220
            # Wait and retry
221
            gevent.sleep(0.2)
222

  
223
        if self.server is None:
224
            self.error("Failed to connect to server")
225
            self._cleanup()
226

  
227
        version = self.server.recv(1024)
228
        if not rfb.check_version(version):
229
            self.error("Unsupported RFB version: %s" % version.strip())
230
            self._cleanup()
231

  
232
        self.server.send(rfb.RFB_VERSION_3_8 + "\n")
233

  
234
        res = self.server.recv(1024)
235
        types = rfb.parse_auth_request(res)
236
        if not types:
237
            self.error("Error handshaking with the server")
238
            self._cleanup()
239

  
240
        else:
241
            self.debug("Supported authentication types: %s" %
242
                           " ".join([str(x) for x in types]))
243

  
244
        if rfb.RFB_AUTHTYPE_NONE not in types:
245
            self.error("Error, server demands authentication")
246
            self._cleanup()
247

  
248
        self.server.send(rfb.to_u8(rfb.RFB_AUTHTYPE_NONE))
249

  
250
        # Check authentication response
251
        res = self.server.recv(4)
252
        res = rfb.from_u32(res)
253

  
254
        if res != 0:
255
            self.error("Authentication error")
256
            self._cleanup()
257

  
258
        # Bridge client/server connections
259
        self.workers = [gevent.spawn(self._forward, self.client, self.server),
260
                        gevent.spawn(self._forward, self.server, self.client)]
261
        gevent.joinall(self.workers)
262

  
263
        del self.workers
264
        self._cleanup()
265

  
266
    def _run(self):
267
        sockets = []
268

  
269
        # Use two sockets, one for IPv4, one for IPv6. IPv4-to-IPv6 mapped
270
        # addresses do not work reliably everywhere (under linux it may have
271
        # been disabled in /proc/sys/net/ipv6/bind_ipv6_only).
272
        for res in socket.getaddrinfo(None, self.sport, socket.AF_UNSPEC,
273
                                      socket.SOCK_STREAM, 0, socket.AI_PASSIVE):
274
            af, socktype, proto, canonname, sa = res
275
            try:
276
                s = socket.socket(af, socktype, proto)
277
                if af == socket.AF_INET6:
278
                    # Bind v6 only when AF_INET6, otherwise either v4 or v6 bind
279
                    # will fail.
280
                    s.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 1)
281
            except socket.error, msg:
282
                s = None
283
                continue;
284

  
285
            try:
286
                s.bind(sa)
287
                s.listen(1)
288
                self.debug("Listening on %s:%d" % sa[:2])
289
            except socket.error, msg:
290
                self.error("Error binding to %s:%d: %s" %
291
                               (sa[0], sa[1], msg[1]))
292
                s.close()
293
                s = None
294
                continue
295

  
296
            if s:
297
                sockets.append(s)
298

  
299
        if not sockets:
300
            self.error("Failed to listen for connections")
301
            self._cleanup()
302

  
303
        self.log.debug("Waiting for client to connect")
304
        rlist, _, _ = select(sockets, [], [], timeout=self.timeout)
305

  
306
        if not rlist:
307
            self.info("Timed out, no connection after %d sec" % self.timeout)
308
            self._cleanup()
309

  
310
        for sock in rlist:
311
            self.client, addrinfo = sock.accept()
312
            self.info("Connection from %s:%d" % addrinfo[:2])
313

  
314
            # Close all listening sockets, we only want a one-shot connection
315
            # from a single client.
316
            for listener in sockets:
317
                listener.close()
318
            break
319

  
320
        self._handshake()
321

  
322

  
323
def fatal_signal_handler(signame):
324
    logger.info("Caught %s, will raise SystemExit" % signame)
325
    raise SystemExit
326

  
327

  
328
if __name__ == '__main__':
329
    from optparse import OptionParser
330

  
331
    parser = OptionParser()
332
    parser.add_option("-s", "--socket", dest="ctrl_socket",
333
                      default=DEFAULT_CTRL_SOCKET,
334
                      metavar="PATH",
335
                      help="UNIX socket path for control connections (default: %s" %
336
                          DEFAULT_CTRL_SOCKET)
337
    parser.add_option("-d", "--debug", action="store_true", dest="debug",
338
                      help="Enable debugging information")
339
    parser.add_option("-l", "--log", dest="log_file",
340
                      default=DEFAULT_LOG_FILE,
341
                      metavar="FILE",
342
                      help="Write log to FILE instead of %s" % DEFAULT_LOG_FILE),
343
    parser.add_option('--pid-file', dest="pid_file",
344
                      default=DEFAULT_PID_FILE,
345
                      metavar='PIDFILE',
346
                      help="Save PID to file (default: %s)" %
347
                          DEFAULT_PID_FILE)
348
    parser.add_option("-t", "--connect-timeout", dest="connect_timeout",
349
                      default=DEFAULT_CONNECT_TIMEOUT, type="int", metavar="SECONDS",
350
                      help="How long to listen for clients to forward")
351

  
352
    (opts, args) = parser.parse_args(sys.argv[1:])
353

  
354
    # Create pidfile
355
    pidf = daemon.pidlockfile.TimeoutPIDLockFile(
356
        opts.pid_file, 10)
357
    
358
    # Initialize logger
359
    lvl = logging.DEBUG if opts.debug else logging.INFO
360
    logger = logging.getLogger("vncauthproxy")
361
    logger.setLevel(lvl)
362
    formatter = logging.Formatter("%(asctime)s vncauthproxy[%(process)d] %(levelname)s: %(message)s",
363
        "%Y-%m-%d %H:%M:%S")
364
    handler = logging.FileHandler(opts.log_file)
365
    handler.setFormatter(formatter)
366
    logger.addHandler(handler)
367

  
368
    # Become a daemon
369
    # Redirecting stdout and stderr to handler.stream to catch
370
    # early errors in the daemonization process [e.g., pidfile creation]
371
    # which will otherwise go to /dev/null.
372
    daemon_context = daemon.DaemonContext(
373
        pidfile=pidf,
374
        umask=0o0022,
375
        stdout=handler.stream,
376
        stderr=handler.stream,
377
        files_preserve=[handler.stream])
378
    daemon_context.open()
379
    logger.info("Became a daemon")
380

  
381
    # A fork() has occured while daemonizing,
382
    # we *must* reinit gevent
383
    gevent.reinit()
384

  
385
    if os.path.exists(opts.ctrl_socket):
386
        logger.critical("Socket '%s' already exists" % opts.ctrl_socket)
387
        sys.exit(1)
388

  
389
    # TODO: make this tunable? chgrp as well?
390
    old_umask = os.umask(0077)
391

  
392
    ctrl = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
393
    ctrl.bind(opts.ctrl_socket)
394

  
395
    os.umask(old_umask)
396

  
397
    ctrl.listen(1)
398
    logger.info("Initialized, waiting for control connections at %s" %
399
                 opts.ctrl_socket)
400

  
401
    # Catch signals to ensure graceful shutdown,
402
    # e.g., to make sure the control socket gets unlink()ed.
403
    #
404
    # Uses gevent.signal so the handler fires even during
405
    # gevent.socket.accept()
406
    gevent.signal(SIGINT, fatal_signal_handler, "SIGINT")
407
    gevent.signal(SIGTERM, fatal_signal_handler, "SIGTERM")
408
    while True:
409
        try:
410
            client, addr = ctrl.accept()
411
        except SystemExit:
412
            break
413

  
414
        logger.info("New control connection")
415
        line = client.recv(1024).strip()
416
        try:
417
            # Control message format:
418
            # TODO: make this json-based?
419
            # TODO: support multiple forwardings in the same message?
420
            # <source_port>:<destination_address>:<destination_port>:<password>
421
            # <password> will be used for MITM authentication of clients
422
            # connecting to <source_port>, who will subsequently be forwarded
423
            # to a VNC server at <destination_address>:<destination_port>
424
            sport, daddr, dport, password = line.split(':', 3)
425
            logger.info("New forwarding [%d -> %s:%d]" %
426
                         (int(sport), daddr, int(dport)))
427
        except:
428
            logger.warn("Malformed request: %s" % line)
429
            client.send("FAILED\n")
430
            client.close()
431
            continue
432

  
433
        client.send("OK\n")
434
        VncAuthProxy.spawn(logger, sport, daddr, dport, password, opts.connect_timeout)
435
        client.close()
436

  
437
    logger.info("Unlinking control socket at %s" %
438
                 opts.ctrl_socket)
439
    os.unlink(opts.ctrl_socket)
440
    daemon_context.close()
441
    sys.exit(0)

Also available in: Unified diff