/[rdesktop]/jpeg/rdesktop/trunk/rdesktop.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

Contents of /jpeg/rdesktop/trunk/rdesktop.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1479 - (show annotations)
Fri Sep 26 11:40:54 2008 UTC (15 years, 8 months ago) by astrand
Original Path: sourceforge.net/trunk/rdesktop/rdesktop.c
File MIME type: text/plain
File size: 35111 byte(s)
Removed the hardcoded limit of the username length.

1 /* -*- c-basic-offset: 8 -*-
2 rdesktop: A Remote Desktop Protocol client.
3 Entrypoint and utility functions
4 Copyright (C) Matthew Chapman 1999-2008
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,
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 <stdarg.h> /* va_list va_start va_end */
22 #include <unistd.h> /* read close getuid getgid getpid getppid gethostname */
23 #include <fcntl.h> /* open */
24 #include <pwd.h> /* getpwuid */
25 #include <termios.h> /* tcgetattr tcsetattr */
26 #include <sys/stat.h> /* stat */
27 #include <sys/time.h> /* gettimeofday */
28 #include <sys/times.h> /* times */
29 #include <ctype.h> /* toupper */
30 #include <errno.h>
31 #include "rdesktop.h"
32
33 #ifdef HAVE_LOCALE_H
34 #include <locale.h>
35 #endif
36 #ifdef HAVE_ICONV
37 #ifdef HAVE_LANGINFO_H
38 #include <langinfo.h>
39 #endif
40 #endif
41
42 #ifdef EGD_SOCKET
43 #include <sys/types.h>
44 #include <sys/socket.h> /* socket connect */
45 #include <sys/un.h> /* sockaddr_un */
46 #endif
47
48 #include "ssl.h"
49
50 char g_title[64] = "";
51 char *g_username;
52 char g_hostname[16];
53 char g_keymapname[PATH_MAX] = "";
54 unsigned int g_keylayout = 0x409; /* Defaults to US keyboard layout */
55 int g_keyboard_type = 0x4; /* Defaults to US keyboard layout */
56 int g_keyboard_subtype = 0x0; /* Defaults to US keyboard layout */
57 int g_keyboard_functionkeys = 0xc; /* Defaults to US keyboard layout */
58
59 int g_width = 800; /* width is special: If 0, the
60 geometry will be fetched from
61 _NET_WORKAREA. If negative,
62 absolute value specifies the
63 percent of the whole screen. */
64 int g_height = 600;
65 int g_xpos = 0;
66 int g_ypos = 0;
67 int g_pos = 0; /* 0 position unspecified,
68 1 specified,
69 2 xpos neg,
70 4 ypos neg */
71 extern int g_tcp_port_rdp;
72 int g_server_depth = -1;
73 int g_win_button_size = 0; /* If zero, disable single app mode */
74 RD_BOOL g_bitmap_compression = True;
75 RD_BOOL g_sendmotion = True;
76 RD_BOOL g_bitmap_cache = True;
77 RD_BOOL g_bitmap_cache_persist_enable = False;
78 RD_BOOL g_bitmap_cache_precache = True;
79 RD_BOOL g_encryption = True;
80 RD_BOOL g_packet_encryption = True;
81 RD_BOOL g_desktop_save = True; /* desktop save order */
82 RD_BOOL g_polygon_ellipse_orders = True; /* polygon / ellipse orders */
83 RD_BOOL g_fullscreen = False;
84 RD_BOOL g_grab_keyboard = True;
85 RD_BOOL g_hide_decorations = False;
86 RD_BOOL g_use_rdp5 = True;
87 RD_BOOL g_rdpclip = True;
88 RD_BOOL g_console_session = False;
89 RD_BOOL g_numlock_sync = False;
90 RD_BOOL g_lspci_enabled = False;
91 RD_BOOL g_owncolmap = False;
92 RD_BOOL g_ownbackstore = True; /* We can't rely on external BackingStore */
93 RD_BOOL g_seamless_rdp = False;
94 uint32 g_embed_wnd;
95 uint32 g_rdp5_performanceflags =
96 RDP5_NO_WALLPAPER | RDP5_NO_FULLWINDOWDRAG | RDP5_NO_MENUANIMATIONS;
97 /* Session Directory redirection */
98 RD_BOOL g_redirect = False;
99 char g_redirect_server[64];
100 char g_redirect_domain[16];
101 char g_redirect_password[64];
102 char *g_redirect_username;
103 char g_redirect_cookie[128];
104 uint32 g_redirect_flags = 0;
105
106 #ifdef WITH_RDPSND
107 RD_BOOL g_rdpsnd = False;
108 #endif
109
110 #ifdef HAVE_ICONV
111 char g_codepage[16] = "";
112 #endif
113
114 extern RDPDR_DEVICE g_rdpdr_device[];
115 extern uint32 g_num_devices;
116 extern char *g_rdpdr_clientname;
117
118 #ifdef RDP2VNC
119 extern int rfb_port;
120 extern int defer_time;
121 void
122 rdp2vnc_connect(char *server, uint32 flags, char *domain, char *password,
123 char *shell, char *directory);
124 #endif
125 /* Display usage information */
126 static void
127 usage(char *program)
128 {
129 fprintf(stderr, "rdesktop: A Remote Desktop Protocol client.\n");
130 fprintf(stderr, "Version " VERSION ". Copyright (C) 1999-2008 Matthew Chapman.\n");
131 fprintf(stderr, "See http://www.rdesktop.org/ for more information.\n\n");
132
133 fprintf(stderr, "Usage: %s [options] server[:port]\n", program);
134 #ifdef RDP2VNC
135 fprintf(stderr, " -V: vnc port\n");
136 fprintf(stderr, " -Q: defer time (ms)\n");
137 #endif
138 fprintf(stderr, " -u: user name\n");
139 fprintf(stderr, " -d: domain\n");
140 fprintf(stderr, " -s: shell\n");
141 fprintf(stderr, " -c: working directory\n");
142 fprintf(stderr, " -p: password (- to prompt)\n");
143 fprintf(stderr, " -n: client hostname\n");
144 fprintf(stderr, " -k: keyboard layout on server (en-us, de, sv, etc.)\n");
145 fprintf(stderr, " -g: desktop geometry (WxH)\n");
146 fprintf(stderr, " -f: full-screen mode\n");
147 fprintf(stderr, " -b: force bitmap updates\n");
148 #ifdef HAVE_ICONV
149 fprintf(stderr, " -L: local codepage\n");
150 #endif
151 fprintf(stderr, " -A: enable SeamlessRDP mode\n");
152 fprintf(stderr, " -B: use BackingStore of X-server (if available)\n");
153 fprintf(stderr, " -e: disable encryption (French TS)\n");
154 fprintf(stderr, " -E: disable encryption from client to server\n");
155 fprintf(stderr, " -m: do not send motion events\n");
156 fprintf(stderr, " -C: use private colour map\n");
157 fprintf(stderr, " -D: hide window manager decorations\n");
158 fprintf(stderr, " -K: keep window manager key bindings\n");
159 fprintf(stderr, " -S: caption button size (single application mode)\n");
160 fprintf(stderr, " -T: window title\n");
161 fprintf(stderr, " -N: enable numlock syncronization\n");
162 fprintf(stderr, " -X: embed into another window with a given id.\n");
163 fprintf(stderr, " -a: connection colour depth\n");
164 fprintf(stderr, " -z: enable rdp compression\n");
165 fprintf(stderr, " -x: RDP5 experience (m[odem 28.8], b[roadband], l[an] or hex nr.)\n");
166 fprintf(stderr, " -P: use persistent bitmap caching\n");
167 fprintf(stderr, " -r: enable specified device redirection (this flag can be repeated)\n");
168 fprintf(stderr,
169 " '-r comport:COM1=/dev/ttyS0': enable serial redirection of /dev/ttyS0 to COM1\n");
170 fprintf(stderr, " or COM1=/dev/ttyS0,COM2=/dev/ttyS1\n");
171 fprintf(stderr,
172 " '-r disk:floppy=/mnt/floppy': enable redirection of /mnt/floppy to 'floppy' share\n");
173 fprintf(stderr, " or 'floppy=/mnt/floppy,cdrom=/mnt/cdrom'\n");
174 fprintf(stderr, " '-r clientname=<client name>': Set the client name displayed\n");
175 fprintf(stderr, " for redirected disks\n");
176 fprintf(stderr,
177 " '-r lptport:LPT1=/dev/lp0': enable parallel redirection of /dev/lp0 to LPT1\n");
178 fprintf(stderr, " or LPT1=/dev/lp0,LPT2=/dev/lp1\n");
179 fprintf(stderr, " '-r printer:mydeskjet': enable printer redirection\n");
180 fprintf(stderr,
181 " or mydeskjet=\"HP LaserJet IIIP\" to enter server driver as well\n");
182 #ifdef WITH_RDPSND
183 fprintf(stderr,
184 " '-r sound:[local[:driver[:device]]|off|remote]': enable sound redirection\n");
185 fprintf(stderr, " remote would leave sound on server\n");
186 fprintf(stderr, " available drivers for 'local':\n");
187 rdpsnd_show_help();
188 #endif
189 fprintf(stderr,
190 " '-r clipboard:[off|PRIMARYCLIPBOARD|CLIPBOARD]': enable clipboard\n");
191 fprintf(stderr, " redirection.\n");
192 fprintf(stderr,
193 " 'PRIMARYCLIPBOARD' looks at both PRIMARY and CLIPBOARD\n");
194 fprintf(stderr, " when sending data to server.\n");
195 fprintf(stderr, " 'CLIPBOARD' looks at only CLIPBOARD.\n");
196 #ifdef WITH_SCARD
197 fprintf(stderr, " '-r scard[:\"Scard Name\"=\"Alias Name[;Vendor Name]\"[,...]]\n");
198 fprintf(stderr, " example: -r scard:\"eToken PRO 00 00\"=\"AKS ifdh 0\"\n");
199 fprintf(stderr,
200 " \"eToken PRO 00 00\" -> Device in Linux/Unix enviroment\n");
201 fprintf(stderr,
202 " \"AKS ifdh 0\" -> Device shown in Windows enviroment \n");
203 fprintf(stderr, " example: -r scard:\"eToken PRO 00 00\"=\"AKS ifdh 0;AKS\"\n");
204 fprintf(stderr,
205 " \"eToken PRO 00 00\" -> Device in Linux/Unix enviroment\n");
206 fprintf(stderr,
207 " \"AKS ifdh 0\" -> Device shown in Windows enviroment \n");
208 fprintf(stderr,
209 " \"AKS\" -> Device vendor name \n");
210 #endif
211 fprintf(stderr, " -0: attach to console\n");
212 fprintf(stderr, " -4: use RDP version 4\n");
213 fprintf(stderr, " -5: use RDP version 5 (default)\n");
214 }
215
216 static void
217 print_disconnect_reason(uint16 reason)
218 {
219 char *text;
220
221 switch (reason)
222 {
223 case exDiscReasonNoInfo:
224 text = "No information available";
225 break;
226
227 case exDiscReasonAPIInitiatedDisconnect:
228 text = "Server initiated disconnect";
229 break;
230
231 case exDiscReasonAPIInitiatedLogoff:
232 text = "Server initiated logoff";
233 break;
234
235 case exDiscReasonServerIdleTimeout:
236 text = "Server idle timeout reached";
237 break;
238
239 case exDiscReasonServerLogonTimeout:
240 text = "Server logon timeout reached";
241 break;
242
243 case exDiscReasonReplacedByOtherConnection:
244 text = "The session was replaced";
245 break;
246
247 case exDiscReasonOutOfMemory:
248 text = "The server is out of memory";
249 break;
250
251 case exDiscReasonServerDeniedConnection:
252 text = "The server denied the connection";
253 break;
254
255 case exDiscReasonServerDeniedConnectionFips:
256 text = "The server denied the connection for security reason";
257 break;
258
259 case exDiscReasonLicenseInternal:
260 text = "Internal licensing error";
261 break;
262
263 case exDiscReasonLicenseNoLicenseServer:
264 text = "No license server available";
265 break;
266
267 case exDiscReasonLicenseNoLicense:
268 text = "No valid license available";
269 break;
270
271 case exDiscReasonLicenseErrClientMsg:
272 text = "Invalid licensing message";
273 break;
274
275 case exDiscReasonLicenseHwidDoesntMatchLicense:
276 text = "Hardware id doesn't match software license";
277 break;
278
279 case exDiscReasonLicenseErrClientLicense:
280 text = "Client license error";
281 break;
282
283 case exDiscReasonLicenseCantFinishProtocol:
284 text = "Network error during licensing protocol";
285 break;
286
287 case exDiscReasonLicenseClientEndedProtocol:
288 text = "Licensing protocol was not completed";
289 break;
290
291 case exDiscReasonLicenseErrClientEncryption:
292 text = "Incorrect client license enryption";
293 break;
294
295 case exDiscReasonLicenseCantUpgradeLicense:
296 text = "Can't upgrade license";
297 break;
298
299 case exDiscReasonLicenseNoRemoteConnections:
300 text = "The server is not licensed to accept remote connections";
301 break;
302
303 default:
304 if (reason > 0x1000 && reason < 0x7fff)
305 {
306 text = "Internal protocol error";
307 }
308 else
309 {
310 text = "Unknown reason";
311 }
312 }
313 fprintf(stderr, "disconnect: %s.\n", text);
314 }
315
316 static void
317 rdesktop_reset_state(void)
318 {
319 rdp_reset_state();
320 }
321
322 static RD_BOOL
323 read_password(char *password, int size)
324 {
325 struct termios tios;
326 RD_BOOL ret = False;
327 int istty = 0;
328 char *p;
329
330 if (tcgetattr(STDIN_FILENO, &tios) == 0)
331 {
332 fprintf(stderr, "Password: ");
333 tios.c_lflag &= ~ECHO;
334 tcsetattr(STDIN_FILENO, TCSANOW, &tios);
335 istty = 1;
336 }
337
338 if (fgets(password, size, stdin) != NULL)
339 {
340 ret = True;
341
342 /* strip final newline */
343 p = strchr(password, '\n');
344 if (p != NULL)
345 *p = 0;
346 }
347
348 if (istty)
349 {
350 tios.c_lflag |= ECHO;
351 tcsetattr(STDIN_FILENO, TCSANOW, &tios);
352 fprintf(stderr, "\n");
353 }
354
355 return ret;
356 }
357
358 static void
359 parse_server_and_port(char *server)
360 {
361 char *p;
362 #ifdef IPv6
363 int addr_colons;
364 #endif
365
366 #ifdef IPv6
367 p = server;
368 addr_colons = 0;
369 while (*p)
370 if (*p++ == ':')
371 addr_colons++;
372 if (addr_colons >= 2)
373 {
374 /* numeric IPv6 style address format - [1:2:3::4]:port */
375 p = strchr(server, ']');
376 if (*server == '[' && p != NULL)
377 {
378 if (*(p + 1) == ':' && *(p + 2) != '\0')
379 g_tcp_port_rdp = strtol(p + 2, NULL, 10);
380 /* remove the port number and brackets from the address */
381 *p = '\0';
382 strncpy(server, server + 1, strlen(server));
383 }
384 }
385 else
386 {
387 /* dns name or IPv4 style address format - server.example.com:port or 1.2.3.4:port */
388 p = strchr(server, ':');
389 if (p != NULL)
390 {
391 g_tcp_port_rdp = strtol(p + 1, NULL, 10);
392 *p = 0;
393 }
394 }
395 #else /* no IPv6 support */
396 p = strchr(server, ':');
397 if (p != NULL)
398 {
399 g_tcp_port_rdp = strtol(p + 1, NULL, 10);
400 *p = 0;
401 }
402 #endif /* IPv6 */
403
404 }
405
406 /* Client program */
407 int
408 main(int argc, char *argv[])
409 {
410 char server[64];
411 char fullhostname[64];
412 char domain[16];
413 char password[64];
414 char shell[256];
415 char directory[256];
416 RD_BOOL prompt_password, deactivated;
417 struct passwd *pw;
418 uint32 flags, ext_disc_reason = 0;
419 char *p;
420 int c;
421 char *locale = NULL;
422 int username_option = 0;
423 RD_BOOL geometry_option = False;
424 int run_count = 0; /* Session Directory support */
425 RD_BOOL continue_connect = True; /* Session Directory support */
426 #ifdef WITH_RDPSND
427 char *rdpsnd_optarg = NULL;
428 #endif
429
430 #ifdef HAVE_LOCALE_H
431 /* Set locale according to environment */
432 locale = setlocale(LC_ALL, "");
433 if (locale)
434 {
435 locale = xstrdup(locale);
436 }
437
438 #endif
439 flags = RDP_LOGON_NORMAL;
440 prompt_password = False;
441 domain[0] = password[0] = shell[0] = directory[0] = 0;
442 g_embed_wnd = 0;
443
444 g_num_devices = 0;
445
446 #ifdef RDP2VNC
447 #define VNCOPT "V:Q:"
448 #else
449 #define VNCOPT
450 #endif
451
452 while ((c = getopt(argc, argv,
453 VNCOPT "Au:L:d:s:c:p:n:k:g:fbBeEmzCDKS:T:NX:a:x:Pr:045h?")) != -1)
454 {
455 switch (c)
456 {
457 #ifdef RDP2VNC
458 case 'V':
459 rfb_port = strtol(optarg, NULL, 10);
460 if (rfb_port < 100)
461 rfb_port += 5900;
462 break;
463
464 case 'Q':
465 defer_time = strtol(optarg, NULL, 10);
466 if (defer_time < 0)
467 defer_time = 0;
468 break;
469 #endif
470
471 case 'A':
472 g_seamless_rdp = True;
473 break;
474
475 case 'u':
476 g_username = (char *) xmalloc(strlen(optarg) + 1);
477 STRNCPY(g_username, optarg, strlen(optarg) + 1);
478 username_option = 1;
479 break;
480
481 case 'L':
482 #ifdef HAVE_ICONV
483 STRNCPY(g_codepage, optarg, sizeof(g_codepage));
484 #else
485 error("iconv support not available\n");
486 #endif
487 break;
488
489 case 'd':
490 STRNCPY(domain, optarg, sizeof(domain));
491 break;
492
493 case 's':
494 STRNCPY(shell, optarg, sizeof(shell));
495 break;
496
497 case 'c':
498 STRNCPY(directory, optarg, sizeof(directory));
499 break;
500
501 case 'p':
502 if ((optarg[0] == '-') && (optarg[1] == 0))
503 {
504 prompt_password = True;
505 break;
506 }
507
508 STRNCPY(password, optarg, sizeof(password));
509 flags |= RDP_LOGON_AUTO;
510
511 /* try to overwrite argument so it won't appear in ps */
512 p = optarg;
513 while (*p)
514 *(p++) = 'X';
515 break;
516
517 case 'n':
518 STRNCPY(g_hostname, optarg, sizeof(g_hostname));
519 break;
520
521 case 'k':
522 STRNCPY(g_keymapname, optarg, sizeof(g_keymapname));
523 break;
524
525 case 'g':
526 geometry_option = True;
527 g_fullscreen = False;
528 if (!strcmp(optarg, "workarea"))
529 {
530 g_width = g_height = 0;
531 break;
532 }
533
534 g_width = strtol(optarg, &p, 10);
535 if (g_width <= 0)
536 {
537 error("invalid geometry\n");
538 return 1;
539 }
540
541 if (*p == 'x')
542 g_height = strtol(p + 1, &p, 10);
543
544 if (g_height <= 0)
545 {
546 error("invalid geometry\n");
547 return 1;
548 }
549
550 if (*p == '%')
551 {
552 g_width = -g_width;
553 p++;
554 }
555
556 if (*p == '+' || *p == '-')
557 {
558 g_pos |= (*p == '-') ? 2 : 1;
559 g_xpos = strtol(p, &p, 10);
560
561 }
562 if (*p == '+' || *p == '-')
563 {
564 g_pos |= (*p == '-') ? 4 : 1;
565 g_ypos = strtol(p, NULL, 10);
566 }
567
568 break;
569
570 case 'f':
571 g_fullscreen = True;
572 break;
573
574 case 'b':
575 g_bitmap_cache = False;
576 break;
577
578 case 'B':
579 g_ownbackstore = False;
580 break;
581
582 case 'e':
583 g_encryption = False;
584 break;
585 case 'E':
586 g_packet_encryption = False;
587 break;
588 case 'm':
589 g_sendmotion = False;
590 break;
591
592 case 'C':
593 g_owncolmap = True;
594 break;
595
596 case 'D':
597 g_hide_decorations = True;
598 break;
599
600 case 'K':
601 g_grab_keyboard = False;
602 break;
603
604 case 'S':
605 if (!strcmp(optarg, "standard"))
606 {
607 g_win_button_size = 18;
608 break;
609 }
610
611 g_win_button_size = strtol(optarg, &p, 10);
612
613 if (*p)
614 {
615 error("invalid button size\n");
616 return 1;
617 }
618
619 break;
620
621 case 'T':
622 STRNCPY(g_title, optarg, sizeof(g_title));
623 break;
624
625 case 'N':
626 g_numlock_sync = True;
627 break;
628
629 case 'X':
630 g_embed_wnd = strtol(optarg, NULL, 0);
631 break;
632
633 case 'a':
634 g_server_depth = strtol(optarg, NULL, 10);
635 if (g_server_depth != 8 &&
636 g_server_depth != 16 &&
637 g_server_depth != 15 && g_server_depth != 24
638 && g_server_depth != 32)
639 {
640 error("Invalid server colour depth.\n");
641 return 1;
642 }
643 break;
644
645 case 'z':
646 DEBUG(("rdp compression enabled\n"));
647 flags |= (RDP_LOGON_COMPRESSION | RDP_LOGON_COMPRESSION2);
648 break;
649
650 case 'x':
651 if (str_startswith(optarg, "m")) /* modem */
652 {
653 g_rdp5_performanceflags =
654 RDP5_NO_WALLPAPER | RDP5_NO_FULLWINDOWDRAG |
655 RDP5_NO_MENUANIMATIONS | RDP5_NO_THEMING;
656 }
657 else if (str_startswith(optarg, "b")) /* broadband */
658 {
659 g_rdp5_performanceflags = RDP5_NO_WALLPAPER;
660 }
661 else if (str_startswith(optarg, "l")) /* lan */
662 {
663 g_rdp5_performanceflags = RDP5_DISABLE_NOTHING;
664 }
665 else
666 {
667 g_rdp5_performanceflags = strtol(optarg, NULL, 16);
668 }
669 break;
670
671 case 'P':
672 g_bitmap_cache_persist_enable = True;
673 break;
674
675 case 'r':
676
677 if (str_startswith(optarg, "sound"))
678 {
679 optarg += 5;
680
681 if (*optarg == ':')
682 {
683 optarg++;
684 while ((p = next_arg(optarg, ',')))
685 {
686 if (str_startswith(optarg, "remote"))
687 flags |= RDP_LOGON_LEAVE_AUDIO;
688
689 if (str_startswith(optarg, "local"))
690 #ifdef WITH_RDPSND
691 {
692 rdpsnd_optarg =
693 next_arg(optarg, ':');
694 g_rdpsnd = True;
695 }
696
697 #else
698 warning("Not compiled with sound support\n");
699 #endif
700
701 if (str_startswith(optarg, "off"))
702 #ifdef WITH_RDPSND
703 g_rdpsnd = False;
704 #else
705 warning("Not compiled with sound support\n");
706 #endif
707
708 optarg = p;
709 }
710 }
711 else
712 {
713 #ifdef WITH_RDPSND
714 g_rdpsnd = True;
715 #else
716 warning("Not compiled with sound support\n");
717 #endif
718 }
719 }
720 else if (str_startswith(optarg, "disk"))
721 {
722 /* -r disk:h:=/mnt/floppy */
723 disk_enum_devices(&g_num_devices, optarg + 4);
724 }
725 else if (str_startswith(optarg, "comport"))
726 {
727 serial_enum_devices(&g_num_devices, optarg + 7);
728 }
729 else if (str_startswith(optarg, "lspci"))
730 {
731 g_lspci_enabled = True;
732 }
733 else if (str_startswith(optarg, "lptport"))
734 {
735 parallel_enum_devices(&g_num_devices, optarg + 7);
736 }
737 else if (str_startswith(optarg, "printer"))
738 {
739 printer_enum_devices(&g_num_devices, optarg + 7);
740 }
741 else if (str_startswith(optarg, "clientname"))
742 {
743 g_rdpdr_clientname = xmalloc(strlen(optarg + 11) + 1);
744 strcpy(g_rdpdr_clientname, optarg + 11);
745 }
746 else if (str_startswith(optarg, "clipboard"))
747 {
748 optarg += 9;
749
750 if (*optarg == ':')
751 {
752 optarg++;
753
754 if (str_startswith(optarg, "off"))
755 g_rdpclip = False;
756 else
757 cliprdr_set_mode(optarg);
758 }
759 else
760 g_rdpclip = True;
761 }
762 else if (strncmp("scard", optarg, 5) == 0)
763 {
764 #ifdef WITH_SCARD
765 scard_enum_devices(&g_num_devices, optarg + 5);
766 #else
767 warning("Not compiled with smartcard support\n");
768 #endif
769 }
770 else
771 {
772 warning("Unknown -r argument\n\n\tPossible arguments are: comport, disk, lptport, printer, sound, clipboard, scard\n");
773 }
774 break;
775
776 case '0':
777 g_console_session = True;
778 break;
779
780 case '4':
781 g_use_rdp5 = False;
782 break;
783
784 case '5':
785 g_use_rdp5 = True;
786 break;
787
788 case 'h':
789 case '?':
790 default:
791 usage(argv[0]);
792 return 1;
793 }
794 }
795
796 if (argc - optind != 1)
797 {
798 usage(argv[0]);
799 return 1;
800 }
801
802 STRNCPY(server, argv[optind], sizeof(server));
803 parse_server_and_port(server);
804
805 if (g_seamless_rdp)
806 {
807 if (g_win_button_size)
808 {
809 error("You cannot use -S and -A at the same time\n");
810 return 1;
811 }
812 g_rdp5_performanceflags &= ~RDP5_NO_FULLWINDOWDRAG;
813 if (geometry_option)
814 {
815 error("You cannot use -g and -A at the same time\n");
816 return 1;
817 }
818 if (g_fullscreen)
819 {
820 error("You cannot use -f and -A at the same time\n");
821 return 1;
822 }
823 if (g_hide_decorations)
824 {
825 error("You cannot use -D and -A at the same time\n");
826 return 1;
827 }
828 if (g_embed_wnd)
829 {
830 error("You cannot use -X and -A at the same time\n");
831 return 1;
832 }
833 if (!g_use_rdp5)
834 {
835 error("You cannot use -4 and -A at the same time\n");
836 return 1;
837 }
838 g_width = -100;
839 g_grab_keyboard = False;
840 }
841
842 if (!username_option)
843 {
844 pw = getpwuid(getuid());
845 if ((pw == NULL) || (pw->pw_name == NULL))
846 {
847 error("could not determine username, use -u\n");
848 return 1;
849 }
850 /* +1 for trailing \0 */
851 int pwlen = strlen(pw->pw_name) + 1;
852 g_username = (char *) xmalloc(pwlen);
853 STRNCPY(g_username, pw->pw_name, pwlen);
854 }
855
856 #ifdef HAVE_ICONV
857 if (g_codepage[0] == 0)
858 {
859 if (setlocale(LC_CTYPE, ""))
860 {
861 STRNCPY(g_codepage, nl_langinfo(CODESET), sizeof(g_codepage));
862 }
863 else
864 {
865 STRNCPY(g_codepage, DEFAULT_CODEPAGE, sizeof(g_codepage));
866 }
867 }
868 #endif
869
870 if (g_hostname[0] == 0)
871 {
872 if (gethostname(fullhostname, sizeof(fullhostname)) == -1)
873 {
874 error("could not determine local hostname, use -n\n");
875 return 1;
876 }
877
878 p = strchr(fullhostname, '.');
879 if (p != NULL)
880 *p = 0;
881
882 STRNCPY(g_hostname, fullhostname, sizeof(g_hostname));
883 }
884
885 if (g_keymapname[0] == 0)
886 {
887 if (locale && xkeymap_from_locale(locale))
888 {
889 fprintf(stderr, "Autoselected keyboard map %s\n", g_keymapname);
890 }
891 else
892 {
893 STRNCPY(g_keymapname, "en-us", sizeof(g_keymapname));
894 }
895 }
896 if (locale)
897 xfree(locale);
898
899
900 if (prompt_password && read_password(password, sizeof(password)))
901 flags |= RDP_LOGON_AUTO;
902
903 if (g_title[0] == 0)
904 {
905 strcpy(g_title, "rdesktop - ");
906 strncat(g_title, server, sizeof(g_title) - sizeof("rdesktop - "));
907 }
908
909 #ifdef RDP2VNC
910 rdp2vnc_connect(server, flags, domain, password, shell, directory);
911 return 0;
912 #else
913
914 if (!ui_init())
915 return 1;
916
917 #ifdef WITH_RDPSND
918 if (g_rdpsnd)
919 {
920 if (!rdpsnd_init(rdpsnd_optarg))
921 {
922 warning("Initializing sound-support failed!\n");
923 }
924 }
925 #endif
926
927 if (g_lspci_enabled)
928 lspci_init();
929
930 rdpdr_init();
931
932 while (run_count < 2 && continue_connect) /* add support for Session Directory; only reconnect once */
933 {
934 if (run_count == 0)
935 {
936 if (!rdp_connect(server, flags, domain, password, shell, directory))
937 return 1;
938 }
939 else if (!rdp_reconnect
940 (server, flags, domain, password, shell, directory, g_redirect_cookie))
941 return 1;
942
943 /* By setting encryption to False here, we have an encrypted login
944 packet but unencrypted transfer of other packets */
945 if (!g_packet_encryption)
946 g_encryption = False;
947
948
949 DEBUG(("Connection successful.\n"));
950 memset(password, 0, sizeof(password));
951
952 if (run_count == 0)
953 if (!ui_create_window())
954 continue_connect = False;
955
956 if (continue_connect)
957 rdp_main_loop(&deactivated, &ext_disc_reason);
958
959 DEBUG(("Disconnecting...\n"));
960 rdp_disconnect();
961
962 if ((g_redirect == True) && (run_count == 0)) /* Support for Session Directory */
963 {
964 /* reset state of major globals */
965 rdesktop_reset_state();
966
967 STRNCPY(domain, g_redirect_domain, sizeof(domain));
968 xfree(g_username);
969 g_username = (char *) xmalloc(strlen(g_redirect_username) + 1);
970 STRNCPY(g_username, g_redirect_username, sizeof(g_username));
971 STRNCPY(password, g_redirect_password, sizeof(password));
972 STRNCPY(server, g_redirect_server, sizeof(server));
973 flags |= RDP_LOGON_AUTO;
974
975 g_redirect = False;
976 }
977 else
978 {
979 continue_connect = False;
980 ui_destroy_window();
981 break;
982 }
983
984 run_count++;
985 }
986
987 cache_save_state();
988 ui_deinit();
989
990 if (ext_disc_reason >= 2)
991 print_disconnect_reason(ext_disc_reason);
992
993 if (deactivated)
994 {
995 /* clean disconnect */
996 return 0;
997 }
998 else
999 {
1000 if (ext_disc_reason == exDiscReasonAPIInitiatedDisconnect
1001 || ext_disc_reason == exDiscReasonAPIInitiatedLogoff)
1002 {
1003 /* not so clean disconnect, but nothing to worry about */
1004 return 0;
1005 }
1006 else
1007 {
1008 /* return error */
1009 return 2;
1010 }
1011 }
1012
1013 #endif
1014 if (g_redirect_username)
1015 xfree(g_redirect_username);
1016
1017 xfree(g_username);
1018 }
1019
1020 #ifdef EGD_SOCKET
1021 /* Read 32 random bytes from PRNGD or EGD socket (based on OpenSSL RAND_egd) */
1022 static RD_BOOL
1023 generate_random_egd(uint8 * buf)
1024 {
1025 struct sockaddr_un addr;
1026 RD_BOOL ret = False;
1027 int fd;
1028
1029 fd = socket(AF_UNIX, SOCK_STREAM, 0);
1030 if (fd == -1)
1031 return False;
1032
1033 addr.sun_family = AF_UNIX;
1034 memcpy(addr.sun_path, EGD_SOCKET, sizeof(EGD_SOCKET));
1035 if (connect(fd, (struct sockaddr *) &addr, sizeof(addr)) == -1)
1036 goto err;
1037
1038 /* PRNGD and EGD use a simple communications protocol */
1039 buf[0] = 1; /* Non-blocking (similar to /dev/urandom) */
1040 buf[1] = 32; /* Number of requested random bytes */
1041 if (write(fd, buf, 2) != 2)
1042 goto err;
1043
1044 if ((read(fd, buf, 1) != 1) || (buf[0] == 0)) /* Available? */
1045 goto err;
1046
1047 if (read(fd, buf, 32) != 32)
1048 goto err;
1049
1050 ret = True;
1051
1052 err:
1053 close(fd);
1054 return ret;
1055 }
1056 #endif
1057
1058 /* Generate a 32-byte random for the secure transport code. */
1059 void
1060 generate_random(uint8 * random)
1061 {
1062 struct stat st;
1063 struct tms tmsbuf;
1064 SSL_MD5 md5;
1065 uint32 *r;
1066 int fd, n;
1067
1068 /* If we have a kernel random device, try that first */
1069 if (((fd = open("/dev/urandom", O_RDONLY)) != -1)
1070 || ((fd = open("/dev/random", O_RDONLY)) != -1))
1071 {
1072 n = read(fd, random, 32);
1073 close(fd);
1074 if (n == 32)
1075 return;
1076 }
1077
1078 #ifdef EGD_SOCKET
1079 /* As a second preference use an EGD */
1080 if (generate_random_egd(random))
1081 return;
1082 #endif
1083
1084 /* Otherwise use whatever entropy we can gather - ideas welcome. */
1085 r = (uint32 *) random;
1086 r[0] = (getpid()) | (getppid() << 16);
1087 r[1] = (getuid()) | (getgid() << 16);
1088 r[2] = times(&tmsbuf); /* system uptime (clocks) */
1089 gettimeofday((struct timeval *) &r[3], NULL); /* sec and usec */
1090 stat("/tmp", &st);
1091 r[5] = st.st_atime;
1092 r[6] = st.st_mtime;
1093 r[7] = st.st_ctime;
1094
1095 /* Hash both halves with MD5 to obscure possible patterns */
1096 ssl_md5_init(&md5);
1097 ssl_md5_update(&md5, random, 16);
1098 ssl_md5_final(&md5, random);
1099 ssl_md5_update(&md5, random + 16, 16);
1100 ssl_md5_final(&md5, random + 16);
1101 }
1102
1103 /* malloc; exit if out of memory */
1104 void *
1105 xmalloc(int size)
1106 {
1107 void *mem = malloc(size);
1108 if (mem == NULL)
1109 {
1110 error("xmalloc %d\n", size);
1111 exit(1);
1112 }
1113 return mem;
1114 }
1115
1116 /* Exit on NULL pointer. Use to verify result from XGetImage etc */
1117 void
1118 exit_if_null(void *ptr)
1119 {
1120 if (ptr == NULL)
1121 {
1122 error("unexpected null pointer. Out of memory?\n");
1123 exit(1);
1124 }
1125 }
1126
1127 /* strdup */
1128 char *
1129 xstrdup(const char *s)
1130 {
1131 char *mem = strdup(s);
1132 if (mem == NULL)
1133 {
1134 perror("strdup");
1135 exit(1);
1136 }
1137 return mem;
1138 }
1139
1140 /* realloc; exit if out of memory */
1141 void *
1142 xrealloc(void *oldmem, size_t size)
1143 {
1144 void *mem;
1145
1146 if (size == 0)
1147 size = 1;
1148 mem = realloc(oldmem, size);
1149 if (mem == NULL)
1150 {
1151 error("xrealloc %ld\n", size);
1152 exit(1);
1153 }
1154 return mem;
1155 }
1156
1157 /* free */
1158 void
1159 xfree(void *mem)
1160 {
1161 free(mem);
1162 }
1163
1164 /* report an error */
1165 void
1166 error(char *format, ...)
1167 {
1168 va_list ap;
1169
1170 fprintf(stderr, "ERROR: ");
1171
1172 va_start(ap, format);
1173 vfprintf(stderr, format, ap);
1174 va_end(ap);
1175 }
1176
1177 /* report a warning */
1178 void
1179 warning(char *format, ...)
1180 {
1181 va_list ap;
1182
1183 fprintf(stderr, "WARNING: ");
1184
1185 va_start(ap, format);
1186 vfprintf(stderr, format, ap);
1187 va_end(ap);
1188 }
1189
1190 /* report an unimplemented protocol feature */
1191 void
1192 unimpl(char *format, ...)
1193 {
1194 va_list ap;
1195
1196 fprintf(stderr, "NOT IMPLEMENTED: ");
1197
1198 va_start(ap, format);
1199 vfprintf(stderr, format, ap);
1200 va_end(ap);
1201 }
1202
1203 /* produce a hex dump */
1204 void
1205 hexdump(unsigned char *p, unsigned int len)
1206 {
1207 unsigned char *line = p;
1208 int i, thisline, offset = 0;
1209
1210 while (offset < len)
1211 {
1212 printf("%04x ", offset);
1213 thisline = len - offset;
1214 if (thisline > 16)
1215 thisline = 16;
1216
1217 for (i = 0; i < thisline; i++)
1218 printf("%02x ", line[i]);
1219
1220 for (; i < 16; i++)
1221 printf(" ");
1222
1223 for (i = 0; i < thisline; i++)
1224 printf("%c", (line[i] >= 0x20 && line[i] < 0x7f) ? line[i] : '.');
1225
1226 printf("\n");
1227 offset += thisline;
1228 line += thisline;
1229 }
1230 }
1231
1232 /*
1233 input: src is the string we look in for needle.
1234 Needle may be escaped by a backslash, in
1235 that case we ignore that particular needle.
1236 return value: returns next src pointer, for
1237 succesive executions, like in a while loop
1238 if retval is 0, then there are no more args.
1239 pitfalls:
1240 src is modified. 0x00 chars are inserted to
1241 terminate strings.
1242 return val, points on the next val chr after ins
1243 0x00
1244
1245 example usage:
1246 while( (pos = next_arg( optarg, ',')) ){
1247 printf("%s\n",optarg);
1248 optarg=pos;
1249 }
1250
1251 */
1252 char *
1253 next_arg(char *src, char needle)
1254 {
1255 char *nextval;
1256 char *p;
1257 char *mvp = 0;
1258
1259 /* EOS */
1260 if (*src == (char) 0x00)
1261 return 0;
1262
1263 p = src;
1264 /* skip escaped needles */
1265 while ((nextval = strchr(p, needle)))
1266 {
1267 mvp = nextval - 1;
1268 /* found backslashed needle */
1269 if (*mvp == '\\' && (mvp > src))
1270 {
1271 /* move string one to the left */
1272 while (*(mvp + 1) != (char) 0x00)
1273 {
1274 *mvp = *(mvp + 1);
1275 mvp++;
1276 }
1277 *mvp = (char) 0x00;
1278 p = nextval;
1279 }
1280 else
1281 {
1282 p = nextval + 1;
1283 break;
1284 }
1285
1286 }
1287
1288 /* more args available */
1289 if (nextval)
1290 {
1291 *nextval = (char) 0x00;
1292 return ++nextval;
1293 }
1294
1295 /* no more args after this, jump to EOS */
1296 nextval = src + strlen(src);
1297 return nextval;
1298 }
1299
1300
1301 void
1302 toupper_str(char *p)
1303 {
1304 while (*p)
1305 {
1306 if ((*p >= 'a') && (*p <= 'z'))
1307 *p = toupper((int) *p);
1308 p++;
1309 }
1310 }
1311
1312
1313 RD_BOOL
1314 str_startswith(const char *s, const char *prefix)
1315 {
1316 return (strncmp(s, prefix, strlen(prefix)) == 0);
1317 }
1318
1319
1320 /* Split input into lines, and call linehandler for each
1321 line. Incomplete lines are saved in the rest variable, which should
1322 initially point to NULL. When linehandler returns False, stop and
1323 return False. Otherwise, return True. */
1324 RD_BOOL
1325 str_handle_lines(const char *input, char **rest, str_handle_lines_t linehandler, void *data)
1326 {
1327 char *buf, *p;
1328 char *oldrest;
1329 size_t inputlen;
1330 size_t buflen;
1331 size_t restlen = 0;
1332 RD_BOOL ret = True;
1333
1334 /* Copy data to buffer */
1335 inputlen = strlen(input);
1336 if (*rest)
1337 restlen = strlen(*rest);
1338 buflen = restlen + inputlen + 1;
1339 buf = (char *) xmalloc(buflen);
1340 buf[0] = '\0';
1341 if (*rest)
1342 STRNCPY(buf, *rest, buflen);
1343 strncat(buf, input, inputlen);
1344 p = buf;
1345
1346 while (1)
1347 {
1348 char *newline = strchr(p, '\n');
1349 if (newline)
1350 {
1351 *newline = '\0';
1352 if (!linehandler(p, data))
1353 {
1354 p = newline + 1;
1355 ret = False;
1356 break;
1357 }
1358 p = newline + 1;
1359 }
1360 else
1361 {
1362 break;
1363
1364 }
1365 }
1366
1367 /* Save in rest */
1368 oldrest = *rest;
1369 restlen = buf + buflen - p;
1370 *rest = (char *) xmalloc(restlen);
1371 STRNCPY((*rest), p, restlen);
1372 xfree(oldrest);
1373
1374 xfree(buf);
1375 return ret;
1376 }
1377
1378 /* Execute the program specified by argv. For each line in
1379 stdout/stderr output, call linehandler. Returns false on failure. */
1380 RD_BOOL
1381 subprocess(char *const argv[], str_handle_lines_t linehandler, void *data)
1382 {
1383 pid_t child;
1384 int fd[2];
1385 int n = 1;
1386 char output[256];
1387 char *rest = NULL;
1388
1389 if (pipe(fd) < 0)
1390 {
1391 perror("pipe");
1392 return False;
1393 }
1394
1395 if ((child = fork()) < 0)
1396 {
1397 perror("fork");
1398 return False;
1399 }
1400
1401 /* Child */
1402 if (child == 0)
1403 {
1404 /* Close read end */
1405 close(fd[0]);
1406
1407 /* Redirect stdout and stderr to pipe */
1408 dup2(fd[1], 1);
1409 dup2(fd[1], 2);
1410
1411 /* Execute */
1412 execvp(argv[0], argv);
1413 perror("Error executing child");
1414 _exit(128);
1415 }
1416
1417 /* Parent. Close write end. */
1418 close(fd[1]);
1419 while (n > 0)
1420 {
1421 n = read(fd[0], output, 255);
1422 output[n] = '\0';
1423 str_handle_lines(output, &rest, linehandler, data);
1424 }
1425 xfree(rest);
1426
1427 return True;
1428 }
1429
1430
1431 /* not all clibs got ltoa */
1432 #define LTOA_BUFSIZE (sizeof(long) * 8 + 1)
1433
1434 char *
1435 l_to_a(long N, int base)
1436 {
1437 static char ret[LTOA_BUFSIZE];
1438
1439 char *head = ret, buf[LTOA_BUFSIZE], *tail = buf + sizeof(buf);
1440
1441 register int divrem;
1442
1443 if (base < 36 || 2 > base)
1444 base = 10;
1445
1446 if (N < 0)
1447 {
1448 *head++ = '-';
1449 N = -N;
1450 }
1451
1452 tail = buf + sizeof(buf);
1453 *--tail = 0;
1454
1455 do
1456 {
1457 divrem = N % base;
1458 *--tail = (divrem <= 9) ? divrem + '0' : divrem + 'a' - 10;
1459 N /= base;
1460 }
1461 while (N);
1462
1463 strcpy(head, tail);
1464 return ret;
1465 }
1466
1467
1468 int
1469 load_licence(unsigned char **data)
1470 {
1471 char *home, *path;
1472 struct stat st;
1473 int fd, length;
1474
1475 home = getenv("HOME");
1476 if (home == NULL)
1477 return -1;
1478
1479 path = (char *) xmalloc(strlen(home) + strlen(g_hostname) + sizeof("/.rdesktop/licence."));
1480 sprintf(path, "%s/.rdesktop/licence.%s", home, g_hostname);
1481
1482 fd = open(path, O_RDONLY);
1483 if (fd == -1)
1484 return -1;
1485
1486 if (fstat(fd, &st))
1487 return -1;
1488
1489 *data = (uint8 *) xmalloc(st.st_size);
1490 length = read(fd, *data, st.st_size);
1491 close(fd);
1492 xfree(path);
1493 return length;
1494 }
1495
1496 void
1497 save_licence(unsigned char *data, int length)
1498 {
1499 char *home, *path, *tmppath;
1500 int fd;
1501
1502 home = getenv("HOME");
1503 if (home == NULL)
1504 return;
1505
1506 path = (char *) xmalloc(strlen(home) + strlen(g_hostname) + sizeof("/.rdesktop/licence."));
1507
1508 sprintf(path, "%s/.rdesktop", home);
1509 if ((mkdir(path, 0700) == -1) && errno != EEXIST)
1510 {
1511 perror(path);
1512 return;
1513 }
1514
1515 /* write licence to licence.hostname.new, then atomically rename to licence.hostname */
1516
1517 sprintf(path, "%s/.rdesktop/licence.%s", home, g_hostname);
1518 tmppath = (char *) xmalloc(strlen(path) + sizeof(".new"));
1519 strcpy(tmppath, path);
1520 strcat(tmppath, ".new");
1521
1522 fd = open(tmppath, O_WRONLY | O_CREAT | O_TRUNC, 0600);
1523 if (fd == -1)
1524 {
1525 perror(tmppath);
1526 return;
1527 }
1528
1529 if (write(fd, data, length) != length)
1530 {
1531 perror(tmppath);
1532 unlink(tmppath);
1533 }
1534 else if (rename(tmppath, path) == -1)
1535 {
1536 perror(path);
1537 unlink(tmppath);
1538 }
1539
1540 close(fd);
1541 xfree(tmppath);
1542 xfree(path);
1543 }
1544
1545 /* Create the bitmap cache directory */
1546 RD_BOOL
1547 rd_pstcache_mkdir(void)
1548 {
1549 char *home;
1550 char bmpcache_dir[256];
1551
1552 home = getenv("HOME");
1553
1554 if (home == NULL)
1555 return False;
1556
1557 sprintf(bmpcache_dir, "%s/%s", home, ".rdesktop");
1558
1559 if ((mkdir(bmpcache_dir, S_IRWXU) == -1) && errno != EEXIST)
1560 {
1561 perror(bmpcache_dir);
1562 return False;
1563 }
1564
1565 sprintf(bmpcache_dir, "%s/%s", home, ".rdesktop/cache");
1566
1567 if ((mkdir(bmpcache_dir, S_IRWXU) == -1) && errno != EEXIST)
1568 {
1569 perror(bmpcache_dir);
1570 return False;
1571 }
1572
1573 return True;
1574 }
1575
1576 /* open a file in the .rdesktop directory */
1577 int
1578 rd_open_file(char *filename)
1579 {
1580 char *home;
1581 char fn[256];
1582 int fd;
1583
1584 home = getenv("HOME");
1585 if (home == NULL)
1586 return -1;
1587 sprintf(fn, "%s/.rdesktop/%s", home, filename);
1588 fd = open(fn, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
1589 if (fd == -1)
1590 perror(fn);
1591 return fd;
1592 }
1593
1594 /* close file */
1595 void
1596 rd_close_file(int fd)
1597 {
1598 close(fd);
1599 }
1600
1601 /* read from file*/
1602 int
1603 rd_read_file(int fd, void *ptr, int len)
1604 {
1605 return read(fd, ptr, len);
1606 }
1607
1608 /* write to file */
1609 int
1610 rd_write_file(int fd, void *ptr, int len)
1611 {
1612 return write(fd, ptr, len);
1613 }
1614
1615 /* move file pointer */
1616 int
1617 rd_lseek_file(int fd, int offset)
1618 {
1619 return lseek(fd, offset, SEEK_SET);
1620 }
1621
1622 /* do a write lock on a file */
1623 RD_BOOL
1624 rd_lock_file(int fd, int start, int len)
1625 {
1626 struct flock lock;
1627
1628 lock.l_type = F_WRLCK;
1629 lock.l_whence = SEEK_SET;
1630 lock.l_start = start;
1631 lock.l_len = len;
1632 if (fcntl(fd, F_SETLK, &lock) == -1)
1633 return False;
1634 return True;
1635 }

  ViewVC Help
Powered by ViewVC 1.1.26