VirtualBox

source: vbox/trunk/src/VBox/RDP/client/rdesktop.c@ 21395

Last change on this file since 21395 was 16631, checked in by vboxsync, 16 years ago

typo

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

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette