VirtualBox

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

最後變更 在這個檔案從13795是 11982,由 vboxsync 提交於 16 年 前

All: license header changes for 2.0 (OSE headers, add Sun GPL/LGPL disclaimer)

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

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