/[rdesktop]/sourceforge.net/trunk/rdesktop/secure.c
This is repository of my old source code which isn't updated any more. Go to git.rot13.org for current projects!
ViewVC logotype

Annotation of /sourceforge.net/trunk/rdesktop/secure.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 309 - (hide annotations)
Tue Feb 4 05:32:13 2003 UTC (21 years, 3 months ago) by jsorg71
File MIME type: text/plain
File size: 14340 byte(s)
16bit

1 matty 10 /*
2     rdesktop: A Remote Desktop Protocol client.
3     Protocol services - RDP encryption and licensing
4 matthewc 207 Copyright (C) Matthew Chapman 1999-2002
5 matty 10
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,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14     GNU 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., 675 Mass Ave, Cambridge, MA 02139, USA.
19     */
20    
21     #include "rdesktop.h"
22 matty 32
23     #ifdef WITH_OPENSSL
24     #include <openssl/rc4.h>
25     #include <openssl/md5.h>
26     #include <openssl/sha.h>
27     #include <openssl/bn.h>
28     #else
29 matty 10 #include "crypto/rc4.h"
30     #include "crypto/md5.h"
31     #include "crypto/sha.h"
32 matty 32 #include "crypto/bn.h"
33     #endif
34 matty 10
35     extern char hostname[16];
36     extern int width;
37     extern int height;
38     extern int keylayout;
39 matty 30 extern BOOL encryption;
40 matty 28 extern BOOL licence_issued;
41 jsorg71 309 extern int server_bpp;
42 matty 10
43     static int rc4_key_len;
44     static RC4_KEY rc4_decrypt_key;
45     static RC4_KEY rc4_encrypt_key;
46    
47 matthewc 57 static uint8 sec_sign_key[16];
48 matty 10 static uint8 sec_decrypt_key[16];
49     static uint8 sec_encrypt_key[16];
50 matthewc 57 static uint8 sec_decrypt_update_key[16];
51     static uint8 sec_encrypt_update_key[16];
52     static uint8 sec_crypted_random[SEC_MODULUS_SIZE];
53 matty 10
54     /*
55     * General purpose 48-byte transformation, using two 32-byte salts (generally,
56     * a client and server salt) and a global salt value used for padding.
57     * Both SHA1 and MD5 algorithms are used.
58     */
59 matty 25 void
60 astrand 64 sec_hash_48(uint8 * out, uint8 * in, uint8 * salt1, uint8 * salt2, uint8 salt)
61 matty 10 {
62     uint8 shasig[20];
63     uint8 pad[4];
64     SHA_CTX sha;
65     MD5_CTX md5;
66     int i;
67    
68     for (i = 0; i < 3; i++)
69     {
70 matty 24 memset(pad, salt + i, i + 1);
71 matty 10
72     SHA1_Init(&sha);
73 matty 24 SHA1_Update(&sha, pad, i + 1);
74 matty 10 SHA1_Update(&sha, in, 48);
75     SHA1_Update(&sha, salt1, 32);
76     SHA1_Update(&sha, salt2, 32);
77     SHA1_Final(shasig, &sha);
78    
79     MD5_Init(&md5);
80     MD5_Update(&md5, in, 48);
81     MD5_Update(&md5, shasig, 20);
82 matty 24 MD5_Final(&out[i * 16], &md5);
83 matty 10 }
84     }
85    
86     /*
87     * Weaker 16-byte transformation, also using two 32-byte salts, but
88     * only using a single round of MD5.
89     */
90 matty 25 void
91 astrand 64 sec_hash_16(uint8 * out, uint8 * in, uint8 * salt1, uint8 * salt2)
92 matty 10 {
93     MD5_CTX md5;
94    
95     MD5_Init(&md5);
96     MD5_Update(&md5, in, 16);
97     MD5_Update(&md5, salt1, 32);
98     MD5_Update(&md5, salt2, 32);
99     MD5_Final(out, &md5);
100     }
101    
102     /* Reduce key entropy from 64 to 40 bits */
103 matty 25 static void
104 astrand 64 sec_make_40bit(uint8 * key)
105 matty 10 {
106     key[0] = 0xd1;
107     key[1] = 0x26;
108     key[2] = 0x9e;
109     }
110    
111     /* Generate a session key and RC4 keys, given client and server randoms */
112 matty 25 static void
113 astrand 64 sec_generate_keys(uint8 * client_key, uint8 * server_key, int rc4_key_size)
114 matty 10 {
115     uint8 session_key[48];
116     uint8 temp_hash[48];
117     uint8 input[48];
118    
119     /* Construct input data to hash */
120 matty 24 memcpy(input, client_key, 24);
121     memcpy(input + 24, server_key, 24);
122 matty 10
123     /* Generate session key - two rounds of sec_hash_48 */
124 matty 24 sec_hash_48(temp_hash, input, client_key, server_key, 65);
125 matty 10 sec_hash_48(session_key, temp_hash, client_key, server_key, 88);
126    
127 matthewc 57 /* Store first 16 bytes of session key, for generating signatures */
128     memcpy(sec_sign_key, session_key, 16);
129 matty 10
130     /* Generate RC4 keys */
131 astrand 82 sec_hash_16(sec_decrypt_key, &session_key[16], client_key, server_key);
132     sec_hash_16(sec_encrypt_key, &session_key[32], client_key, server_key);
133 matty 10
134     if (rc4_key_size == 1)
135     {
136 matty 30 DEBUG(("40-bit encryption enabled\n"));
137 matty 10 sec_make_40bit(sec_sign_key);
138     sec_make_40bit(sec_decrypt_key);
139     sec_make_40bit(sec_encrypt_key);
140     rc4_key_len = 8;
141     }
142     else
143     {
144 matty 30 DEBUG(("128-bit encryption enabled\n"));
145 matty 10 rc4_key_len = 16;
146     }
147    
148 matthewc 57 /* Save initial RC4 keys as update keys */
149     memcpy(sec_decrypt_update_key, sec_decrypt_key, 16);
150     memcpy(sec_encrypt_update_key, sec_encrypt_key, 16);
151 matty 10
152     /* Initialise RC4 state arrays */
153     RC4_set_key(&rc4_decrypt_key, rc4_key_len, sec_decrypt_key);
154     RC4_set_key(&rc4_encrypt_key, rc4_key_len, sec_encrypt_key);
155     }
156    
157 matty 24 static uint8 pad_54[40] = {
158     54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54,
159 matty 25 54, 54, 54,
160 matty 24 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54,
161 matty 25 54, 54, 54
162 matty 10 };
163    
164 matty 24 static uint8 pad_92[48] = {
165     92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92,
166 matty 25 92, 92, 92, 92, 92, 92, 92,
167 matty 24 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92,
168 matty 25 92, 92, 92, 92, 92, 92, 92
169 matty 10 };
170    
171     /* Output a uint32 into a buffer (little-endian) */
172 matty 25 void
173 astrand 64 buf_out_uint32(uint8 * buffer, uint32 value)
174 matty 10 {
175     buffer[0] = (value) & 0xff;
176     buffer[1] = (value >> 8) & 0xff;
177     buffer[2] = (value >> 16) & 0xff;
178     buffer[3] = (value >> 24) & 0xff;
179     }
180    
181     /* Generate a signature hash, using a combination of SHA1 and MD5 */
182 matty 25 void
183 astrand 82 sec_sign(uint8 * signature, int siglen, uint8 * session_key, int keylen, uint8 * data, int datalen)
184 matty 10 {
185     uint8 shasig[20];
186     uint8 md5sig[16];
187     uint8 lenhdr[4];
188     SHA_CTX sha;
189     MD5_CTX md5;
190    
191 matty 24 buf_out_uint32(lenhdr, datalen);
192 matty 10
193     SHA1_Init(&sha);
194 matthewc 61 SHA1_Update(&sha, session_key, keylen);
195 matty 10 SHA1_Update(&sha, pad_54, 40);
196     SHA1_Update(&sha, lenhdr, 4);
197     SHA1_Update(&sha, data, datalen);
198     SHA1_Final(shasig, &sha);
199    
200     MD5_Init(&md5);
201 matthewc 61 MD5_Update(&md5, session_key, keylen);
202 matty 10 MD5_Update(&md5, pad_92, 48);
203     MD5_Update(&md5, shasig, 20);
204     MD5_Final(md5sig, &md5);
205    
206 matthewc 61 memcpy(signature, md5sig, siglen);
207 matty 10 }
208    
209     /* Update an encryption key - similar to the signing process */
210 matty 25 static void
211 astrand 64 sec_update(uint8 * key, uint8 * update_key)
212 matty 10 {
213     uint8 shasig[20];
214     SHA_CTX sha;
215     MD5_CTX md5;
216     RC4_KEY update;
217    
218     SHA1_Init(&sha);
219 matthewc 57 SHA1_Update(&sha, update_key, rc4_key_len);
220 matty 10 SHA1_Update(&sha, pad_54, 40);
221 matthewc 57 SHA1_Update(&sha, key, rc4_key_len);
222 matty 10 SHA1_Final(shasig, &sha);
223    
224     MD5_Init(&md5);
225 matthewc 57 MD5_Update(&md5, update_key, rc4_key_len);
226 matty 10 MD5_Update(&md5, pad_92, 48);
227     MD5_Update(&md5, shasig, 20);
228     MD5_Final(key, &md5);
229    
230     RC4_set_key(&update, rc4_key_len, key);
231     RC4(&update, rc4_key_len, key, key);
232    
233     if (rc4_key_len == 8)
234     sec_make_40bit(key);
235     }
236    
237     /* Encrypt data using RC4 */
238 matty 25 static void
239 astrand 64 sec_encrypt(uint8 * data, int length)
240 matty 10 {
241     static int use_count;
242    
243     if (use_count == 4096)
244     {
245     sec_update(sec_encrypt_key, sec_encrypt_update_key);
246     RC4_set_key(&rc4_encrypt_key, rc4_key_len, sec_encrypt_key);
247     use_count = 0;
248     }
249    
250     RC4(&rc4_encrypt_key, length, data, data);
251     use_count++;
252     }
253    
254     /* Decrypt data using RC4 */
255 matty 25 static void
256 astrand 64 sec_decrypt(uint8 * data, int length)
257 matty 10 {
258     static int use_count;
259    
260     if (use_count == 4096)
261     {
262     sec_update(sec_decrypt_key, sec_decrypt_update_key);
263     RC4_set_key(&rc4_decrypt_key, rc4_key_len, sec_decrypt_key);
264     use_count = 0;
265     }
266    
267     RC4(&rc4_decrypt_key, length, data, data);
268     use_count++;
269     }
270    
271 matty 25 static void
272 astrand 64 reverse(uint8 * p, int len)
273 matty 10 {
274     int i, j;
275 matty 32 uint8 temp;
276 matty 10
277 astrand 64 for (i = 0, j = len - 1; i < j; i++, j--)
278 matty 10 {
279 matty 32 temp = p[i];
280     p[i] = p[j];
281     p[j] = temp;
282 matty 10 }
283     }
284    
285     /* Perform an RSA public key encryption operation */
286 matty 25 static void
287 astrand 82 sec_rsa_encrypt(uint8 * out, uint8 * in, int len, uint8 * modulus, uint8 * exponent)
288 matty 10 {
289 matthewc 295 BN_CTX *ctx;
290 matty 32 BIGNUM mod, exp, x, y;
291     uint8 inr[SEC_MODULUS_SIZE];
292     int outlen;
293 matty 10
294 matty 32 reverse(modulus, SEC_MODULUS_SIZE);
295     reverse(exponent, SEC_EXPONENT_SIZE);
296     memcpy(inr, in, len);
297     reverse(inr, len);
298 matty 10
299 matthewc 295 ctx = BN_CTX_new();
300 matty 32 BN_init(&mod);
301     BN_init(&exp);
302     BN_init(&x);
303     BN_init(&y);
304    
305     BN_bin2bn(modulus, SEC_MODULUS_SIZE, &mod);
306     BN_bin2bn(exponent, SEC_EXPONENT_SIZE, &exp);
307     BN_bin2bn(inr, len, &x);
308 matthewc 295 BN_mod_exp(&y, &x, &exp, &mod, ctx);
309 matty 32 outlen = BN_bn2bin(&y, out);
310     reverse(out, outlen);
311     if (outlen < SEC_MODULUS_SIZE)
312 astrand 64 memset(out + outlen, 0, SEC_MODULUS_SIZE - outlen);
313 matty 32
314     BN_free(&y);
315     BN_clear_free(&x);
316     BN_free(&exp);
317     BN_free(&mod);
318 matthewc 295 BN_CTX_free(ctx);
319 matty 10 }
320    
321     /* Initialise secure transport packet */
322 matty 25 STREAM
323     sec_init(uint32 flags, int maxlen)
324 matty 10 {
325     int hdrlen;
326     STREAM s;
327    
328 matty 28 if (!licence_issued)
329     hdrlen = (flags & SEC_ENCRYPT) ? 12 : 4;
330     else
331     hdrlen = (flags & SEC_ENCRYPT) ? 12 : 0;
332 matty 24 s = mcs_init(maxlen + hdrlen);
333 matty 10 s_push_layer(s, sec_hdr, hdrlen);
334    
335     return s;
336     }
337    
338     /* Transmit secure transport packet */
339 matty 25 void
340     sec_send(STREAM s, uint32 flags)
341 matty 10 {
342     int datalen;
343    
344     s_pop_layer(s, sec_hdr);
345 matty 28 if (!licence_issued || (flags & SEC_ENCRYPT))
346     out_uint32_le(s, flags);
347 matty 10
348     if (flags & SEC_ENCRYPT)
349     {
350     flags &= ~SEC_ENCRYPT;
351     datalen = s->end - s->p - 8;
352    
353 matty 30 #if WITH_DEBUG
354     DEBUG(("Sending encrypted packet:\n"));
355 matty 24 hexdump(s->p + 8, datalen);
356 matty 10 #endif
357    
358 astrand 82 sec_sign(s->p, 8, sec_sign_key, rc4_key_len, s->p + 8, datalen);
359 matty 24 sec_encrypt(s->p + 8, datalen);
360 matty 10 }
361    
362     mcs_send(s);
363     }
364    
365     /* Transfer the client random to the server */
366 matty 25 static void
367 matthewc 192 sec_establish_key(void)
368 matty 10 {
369     uint32 length = SEC_MODULUS_SIZE + SEC_PADDING_SIZE;
370     uint32 flags = SEC_CLIENT_RANDOM;
371     STREAM s;
372    
373     s = sec_init(flags, 76);
374    
375     out_uint32_le(s, length);
376     out_uint8p(s, sec_crypted_random, SEC_MODULUS_SIZE);
377     out_uint8s(s, SEC_PADDING_SIZE);
378    
379     s_mark_end(s);
380     sec_send(s, flags);
381     }
382    
383     /* Output connect initial data blob */
384 matty 25 static void
385     sec_out_mcs_data(STREAM s)
386 matty 10 {
387     int hostlen = 2 * strlen(hostname);
388 astrand 101
389 jsorg71 88 if (hostlen > 30)
390     hostlen = 30;
391 matty 10
392     out_uint16_be(s, 5); /* unknown */
393     out_uint16_be(s, 0x14);
394     out_uint8(s, 0x7c);
395     out_uint16_be(s, 1);
396    
397 matty 24 out_uint16_be(s, (158 | 0x8000)); /* remaining length */
398 matty 10
399     out_uint16_be(s, 8); /* length? */
400     out_uint16_be(s, 16);
401     out_uint8(s, 0);
402     out_uint16_le(s, 0xc001);
403     out_uint8(s, 0);
404    
405 matty 24 out_uint32_le(s, 0x61637544); /* "Duca" ?! */
406     out_uint16_be(s, (144 | 0x8000)); /* remaining length */
407 matty 10
408     /* Client information */
409     out_uint16_le(s, SEC_TAG_CLI_INFO);
410     out_uint16_le(s, 136); /* length */
411     out_uint16_le(s, 1);
412     out_uint16_le(s, 8);
413     out_uint16_le(s, width);
414     out_uint16_le(s, height);
415     out_uint16_le(s, 0xca01);
416     out_uint16_le(s, 0xaa03);
417     out_uint32_le(s, keylayout);
418 matty 24 out_uint32_le(s, 419); /* client build? we are 419 compatible :-) */
419 matty 10
420     /* Unicode name of client, padded to 32 bytes */
421     rdp_out_unistr(s, hostname, hostlen);
422 matty 24 out_uint8s(s, 30 - hostlen);
423 matty 10
424     out_uint32_le(s, 4);
425     out_uint32(s, 0);
426     out_uint32_le(s, 12);
427 matty 24 out_uint8s(s, 64); /* reserved? 4 + 12 doublewords */
428 matty 10
429 jsorg71 309 if (server_bpp == 16)
430     {
431     out_uint16_le(s, 0xca03); /* 16 bit */
432     }
433     else
434     {
435     out_uint16_le(s, 0xca01); /* 8 bit */
436     }
437 matty 10 out_uint16(s, 0);
438    
439     /* Client encryption settings */
440     out_uint16_le(s, SEC_TAG_CLI_CRYPT);
441 n-ki 176 out_uint16_le(s, 8); /* length */
442 matthewc 57 out_uint32_le(s, encryption ? 0x3 : 0); /* encryption supported, 128-bit supported */
443 matty 10 s_mark_end(s);
444     }
445    
446     /* Parse a public key structure */
447 matty 25 static BOOL
448 astrand 64 sec_parse_public_key(STREAM s, uint8 ** modulus, uint8 ** exponent)
449 matty 10 {
450     uint32 magic, modulus_len;
451    
452     in_uint32_le(s, magic);
453     if (magic != SEC_RSA_MAGIC)
454     {
455 matty 30 error("RSA magic 0x%x\n", magic);
456 matty 10 return False;
457     }
458    
459     in_uint32_le(s, modulus_len);
460     if (modulus_len != SEC_MODULUS_SIZE + SEC_PADDING_SIZE)
461     {
462 matty 30 error("modulus len 0x%x\n", modulus_len);
463 matty 10 return False;
464     }
465    
466 matty 24 in_uint8s(s, 8); /* modulus_bits, unknown */
467 matty 10 in_uint8p(s, *exponent, SEC_EXPONENT_SIZE);
468     in_uint8p(s, *modulus, SEC_MODULUS_SIZE);
469     in_uint8s(s, SEC_PADDING_SIZE);
470    
471     return s_check(s);
472     }
473    
474     /* Parse a crypto information structure */
475 matty 25 static BOOL
476 astrand 64 sec_parse_crypt_info(STREAM s, uint32 * rc4_key_size,
477 astrand 82 uint8 ** server_random, uint8 ** modulus, uint8 ** exponent)
478 matty 10 {
479     uint32 crypt_level, random_len, rsa_info_len;
480     uint16 tag, length;
481     uint8 *next_tag, *end;
482    
483 matty 24 in_uint32_le(s, *rc4_key_size); /* 1 = 40-bit, 2 = 128-bit */
484     in_uint32_le(s, crypt_level); /* 1 = low, 2 = medium, 3 = high */
485 jsorg71 89 if (crypt_level == 0) /* no encryptation */
486     return False;
487 matty 10 in_uint32_le(s, random_len);
488     in_uint32_le(s, rsa_info_len);
489    
490     if (random_len != SEC_RANDOM_SIZE)
491     {
492 matty 30 error("random len %d\n", random_len);
493 matty 10 return False;
494     }
495    
496     in_uint8p(s, *server_random, random_len);
497    
498     /* RSA info */
499     end = s->p + rsa_info_len;
500     if (end > s->end)
501     return False;
502    
503 matty 24 in_uint8s(s, 12); /* unknown */
504 matty 10
505     while (s->p < end)
506     {
507     in_uint16_le(s, tag);
508     in_uint16_le(s, length);
509    
510     next_tag = s->p + length;
511    
512     switch (tag)
513     {
514     case SEC_TAG_PUBKEY:
515 astrand 82 if (!sec_parse_public_key(s, modulus, exponent))
516 matty 10 return False;
517    
518     break;
519    
520     case SEC_TAG_KEYSIG:
521     /* Is this a Microsoft key that we just got? */
522     /* Care factor: zero! */
523     break;
524    
525     default:
526 matty 30 unimpl("crypt tag 0x%x\n", tag);
527 matty 10 }
528    
529     s->p = next_tag;
530     }
531    
532     return s_check_end(s);
533     }
534    
535     /* Process crypto information blob */
536 matty 25 static void
537     sec_process_crypt_info(STREAM s)
538 matty 10 {
539     uint8 *server_random, *modulus, *exponent;
540     uint8 client_random[SEC_RANDOM_SIZE];
541     uint32 rc4_key_size;
542    
543 astrand 82 if (!sec_parse_crypt_info(s, &rc4_key_size, &server_random, &modulus, &exponent))
544 matty 10 return;
545    
546     /* Generate a client random, and hence determine encryption keys */
547     generate_random(client_random);
548 astrand 82 sec_rsa_encrypt(sec_crypted_random, client_random, SEC_RANDOM_SIZE, modulus, exponent);
549 matty 10 sec_generate_keys(client_random, server_random, rc4_key_size);
550     }
551    
552     /* Process connect response data blob */
553 matty 25 static void
554     sec_process_mcs_data(STREAM s)
555 matty 10 {
556     uint16 tag, length;
557     uint8 *next_tag;
558 jsorg71 90 uint8 len;
559 matty 10
560 jsorg71 90 in_uint8s(s, 21); /* header */
561     in_uint8(s, len);
562     if (len & 0x80)
563     in_uint8(s, len);
564 matty 10
565     while (s->p < s->end)
566     {
567     in_uint16_le(s, tag);
568     in_uint16_le(s, length);
569    
570     if (length <= 4)
571     return;
572    
573     next_tag = s->p + length - 4;
574    
575     switch (tag)
576     {
577     case SEC_TAG_SRV_INFO:
578     case SEC_TAG_SRV_3:
579     break;
580    
581     case SEC_TAG_SRV_CRYPT:
582     sec_process_crypt_info(s);
583     break;
584    
585     default:
586 matty 30 unimpl("response tag 0x%x\n", tag);
587 matty 10 }
588    
589     s->p = next_tag;
590     }
591     }
592    
593     /* Receive secure transport packet */
594 matty 25 STREAM
595 matthewc 192 sec_recv(void)
596 matty 10 {
597     uint32 sec_flags;
598     STREAM s;
599    
600     while ((s = mcs_recv()) != NULL)
601     {
602 matty 30 if (encryption || !licence_issued)
603 matty 10 {
604 matty 28 in_uint32_le(s, sec_flags);
605 matty 10
606 matty 28 if (sec_flags & SEC_LICENCE_NEG)
607     {
608     licence_process(s);
609     continue;
610     }
611    
612     if (sec_flags & SEC_ENCRYPT)
613     {
614     in_uint8s(s, 8); /* signature */
615     sec_decrypt(s->p, s->end - s->p);
616     }
617 matty 10 }
618    
619     return s;
620     }
621    
622     return NULL;
623     }
624    
625     /* Establish a secure connection */
626 matty 25 BOOL
627     sec_connect(char *server)
628 matty 10 {
629     struct stream mcs_data;
630    
631     /* We exchange some RDP data during the MCS-Connect */
632     mcs_data.size = 512;
633     mcs_data.p = mcs_data.data = xmalloc(mcs_data.size);
634     sec_out_mcs_data(&mcs_data);
635    
636     if (!mcs_connect(server, &mcs_data))
637     return False;
638    
639     sec_process_mcs_data(&mcs_data);
640 matty 30 if (encryption)
641 matty 28 sec_establish_key();
642 matthewc 218 xfree(mcs_data.data);
643 matty 10 return True;
644     }
645    
646     /* Disconnect a connection */
647 matty 25 void
648 matthewc 192 sec_disconnect(void)
649 matty 10 {
650     mcs_disconnect();
651     }

  ViewVC Help
Powered by ViewVC 1.1.26