1 /* VNC Reflector
2 * Copyright (C) 2001-2003 HorizonLive.com, Inc. All rights reserved.
3 *
4 * This software is released under the terms specified in the file LICENSE,
5 * included. HorizonLive provides e-Learning and collaborative synchronous
6 * presentation solutions in a totally Web-based environment. For more
7 * information about HorizonLive, please see our website at
8 * http://www.horizonlive.com.
9 *
10 * This software was authored by Constantin Kaplinsky <const@ce.cctpu.edu.ru>
11 * and sponsored by HorizonLive.com, Inc.
12 *
13 * $Id: main.c,v 1.47 2003/01/11 09:44:02 const Exp $
14 * Main module
15 */
16
17 #include <stdio.h>
18 #include <stdlib.h>
19 #include <unistd.h>
20 #include <string.h>
21 #include <signal.h>
22 #include <errno.h>
23 #include <sys/types.h>
24 #include <sys/stat.h>
25 #include <fcntl.h>
26 #include <zlib.h>
27
28 #include "rfblib.h"
29 #include "async_io.h"
30 #include "logging.h"
31 #include "reflector.h"
32 #include "host_connect.h"
33 #include "translate.h"
34 #include "host_io.h"
35 #include "client_io.h"
36 #include "encode.h"
37
38 /*
39 * Configuration options
40 */
41
42 static int opt_no_banner;
43 static int opt_cl_listen_port;
44 static char *opt_log_filename;
45 static char *opt_passwd_filename;
46 static char *opt_active_filename;
47 static char *opt_actions_filename;
48 static int opt_foreground;
49 static int opt_stderr_loglevel;
50 static int opt_file_loglevel;
51 static char opt_pid_file[256];
52 static char *opt_fbs_prefix;
53 static int opt_join_sessions;
54 static char *opt_bind_ip;
55 static int opt_request_tight;
56 static int opt_tight_level;
57
58 static unsigned char opt_client_password[9];
59 static unsigned char opt_client_ro_password[9];
60
61 static char *opt_host_info_file;
62
63 /*
64 * Global variables
65 */
66
67 RFB_SCREEN_INFO g_screen_info;
68 CARD32 *g_framebuffer;
69 CARD16 g_fb_width, g_fb_height;
70
71 /*
72 * Functions local to this file
73 */
74
75 static void parse_args(int argc, char **argv);
76 static void report_usage(char *program_name);
77 static int read_password_file(void);
78 static int init_screen_info(void);
79 static int write_pid_file(void);
80 static int remove_pid_file(void);
81
82 /*
83 * Implementation
84 */
85
main(int argc,char ** argv)86 int main(int argc, char **argv)
87 {
88 long cache_hits, cache_misses;
89
90 /* Parse command line, exit on error */
91 parse_args(argc, argv);
92
93 if (!opt_no_banner) {
94 fprintf(stderr,
95 "VNC Reflector %s. Copyright (C) 2001-2003 HorizonLive.com, Inc.\n\n"
96 "HorizonLive provides e-Learning and collaborative synchronous presentation\n"
97 "solutions in a totally Web-based environment. For more information about\n"
98 "HorizonLive, please see our website at http://www.horizonlive.com/\n\n",
99 VERSION);
100 }
101
102 if (!log_open(opt_log_filename, opt_file_loglevel,
103 (opt_foreground) ? opt_stderr_loglevel : -1)) {
104 fprintf(stderr, "%s: error opening log file (ignoring this error)\n",
105 argv[0]);
106 }
107
108 log_write(LL_MSG, "Starting VNC Reflector %s", VERSION);
109
110 /* Fork the process to the background if necessary */
111 if (!opt_foreground) {
112 if (!opt_no_banner) {
113 fprintf(stderr, "Starting in the background, "
114 "see the log file for errors and other messages.\n");
115 }
116
117 if (getpid() != 1) {
118 signal(SIGTTIN, SIG_IGN);
119 signal(SIGTTOU, SIG_IGN);
120 signal(SIGTSTP, SIG_IGN);
121 if (fork ())
122 return 0;
123 setsid();
124 }
125 close(0);
126 close(1);
127 close(2);
128 log_write(LL_INFO, "Switched to the background mode");
129 }
130
131 /* Initialization */
132 if (init_screen_info()) {
133 read_password_file();
134 set_host_encodings(opt_request_tight, opt_tight_level);
135 set_client_passwords(opt_client_password, opt_client_ro_password);
136 fbs_set_prefix(opt_fbs_prefix, opt_join_sessions);
137
138 set_active_file(opt_active_filename);
139 set_actions_file(opt_actions_filename);
140
141 aio_init();
142 if (opt_bind_ip != NULL) {
143 if (aio_set_bind_address(opt_bind_ip)) {
144 log_write(LL_INFO, "Would bind listening sockets to address %s",
145 opt_bind_ip);
146 } else {
147 log_write(LL_WARN, "Illegal address to bind listening sockets to: %s",
148 opt_bind_ip);
149 }
150 }
151
152 /* Main work */
153 if (connect_to_host(opt_host_info_file, opt_cl_listen_port)) {
154 if (write_pid_file()) {
155 set_control_signals();
156 aio_mainloop();
157 remove_pid_file();
158 }
159 }
160
161 /* Cleanup */
162 if (g_framebuffer != NULL) {
163 log_write(LL_DETAIL, "Freeing framebuffer and associated structures");
164 free(g_framebuffer);
165 free_enc_cache();
166 }
167 if (g_screen_info.name != NULL)
168 free(g_screen_info.name);
169
170 get_hextile_caching_stats(&cache_hits, &cache_misses);
171 if (cache_hits + cache_misses != 0) {
172 log_write(LL_INFO, "Hextile BGR233 caching efficiency: %d%%",
173 (int)((cache_hits * 100 + (cache_hits + cache_misses) / 2)
174 / (cache_hits + cache_misses)));
175 }
176 }
177
178 log_write(LL_MSG, "Terminating");
179
180 /* Close logs */
181 if (!log_close() && opt_foreground) {
182 fprintf(stderr, "%s: error closing log file (ignoring this error)\n",
183 argv[0]);
184 }
185
186 /* Done */
187 exit(1);
188 }
189
parse_args(int argc,char ** argv)190 static void parse_args(int argc, char **argv)
191 {
192 int err = 0;
193 int c;
194 char *temp_pid_file = NULL;
195 char temp_buf[32]; /* 32 bytes should be more than enough */
196
197 opt_no_banner = 0;
198 opt_foreground = 0;
199 opt_stderr_loglevel = -1;
200 opt_file_loglevel = -1;
201 opt_passwd_filename = NULL;
202 opt_log_filename = NULL;
203 opt_active_filename = NULL;
204 opt_cl_listen_port = -1;
205 opt_pid_file[0] = '\0';
206 opt_fbs_prefix = NULL;
207 opt_join_sessions = 0;
208 opt_bind_ip = NULL;
209 opt_request_tight = 0;
210 opt_tight_level = -1;
211
212 while (!err &&
213 (c = getopt(argc, argv, "hqjv:f:p:a:c:g:l:i:s:b:tT:")) != -1) {
214 switch (c) {
215 case 'h':
216 err = 1;
217 break;
218 case 'q':
219 opt_no_banner = 1;
220 break;
221 case 'j':
222 opt_join_sessions = 1;
223 break;
224 case 'v':
225 if (opt_file_loglevel != -1)
226 err = 1;
227 else
228 opt_file_loglevel = atoi(optarg);
229 break;
230 case 'f':
231 opt_foreground = 1;
232 if (opt_stderr_loglevel != -1)
233 err = 1;
234 else
235 opt_stderr_loglevel = atoi(optarg);
236 break;
237 case 'p':
238 if (opt_passwd_filename != NULL)
239 err = 1;
240 else
241 opt_passwd_filename = optarg;
242 break;
243 case 'g':
244 if (opt_log_filename != NULL)
245 err = 1;
246 else
247 opt_log_filename = optarg;
248 break;
249 case 'a':
250 if (opt_active_filename != NULL)
251 err = 1;
252 else
253 opt_active_filename = optarg;
254 break;
255 case 'c':
256 if (opt_actions_filename != NULL)
257 err = 1;
258 else
259 opt_actions_filename = optarg;
260 break;
261 case 'l':
262 if (opt_cl_listen_port != -1)
263 err = 1;
264 else {
265 opt_cl_listen_port = atoi(optarg);
266 if (opt_cl_listen_port <= 0)
267 err = 1;
268 }
269 break;
270 case 'i':
271 if (temp_pid_file != NULL)
272 err = 1;
273 else
274 temp_pid_file = optarg;
275 break;
276 case 's':
277 if (opt_fbs_prefix != NULL)
278 err = 1;
279 else
280 opt_fbs_prefix = optarg;
281 break;
282 case 'b':
283 if (opt_bind_ip != NULL)
284 err = 1;
285 else
286 opt_bind_ip = optarg;
287 break;
288 case 't':
289 if (opt_request_tight)
290 err = 1;
291 else
292 opt_request_tight = 1;
293 break;
294 case 'T':
295 if (opt_request_tight) {
296 err = 1;
297 } else {
298 opt_request_tight = 1;
299 opt_tight_level = atoi(optarg);
300 if (opt_tight_level <= 0 || opt_tight_level > 9)
301 err = 1;
302 }
303 break;
304 default:
305 err = 1;
306 }
307 }
308
309 /* Print usage help on error */
310 if (err || optind != argc - 1) {
311 report_usage(argv[0]);
312 exit(1);
313 }
314
315 /* Provide reasonable defaults for some options */
316 if (opt_file_loglevel == -1)
317 opt_file_loglevel = LL_INFO;
318 if (opt_passwd_filename == NULL)
319 opt_passwd_filename = "passwd";
320 if (opt_log_filename == NULL)
321 opt_log_filename = "reflector.log";
322 if (opt_cl_listen_port == -1)
323 opt_cl_listen_port = 5999;
324
325 /* Append listening port number to pid filename */
326 if (temp_pid_file != NULL) {
327 sprintf(temp_buf, "%d", opt_cl_listen_port);
328 sprintf(opt_pid_file, "%.*s.%s", (int)(255 - strlen(temp_buf) - 1),
329 temp_pid_file, temp_buf);
330 }
331
332 /* Save pointer to host info filename */
333 opt_host_info_file = argv[optind];
334 }
335
report_usage(char * program_name)336 static void report_usage(char *program_name)
337 {
338 fprintf(stderr,
339 "VNC Reflector %s. Copyright (C) 2001-2003 HorizonLive.com, Inc."
340 "\n\n",
341 VERSION);
342
343 fprintf(stderr, "Usage: %s [OPTIONS...] HOST_INFO_FILE\n\n",
344 program_name);
345
346 fprintf(stderr,
347 "Options:\n"
348 " -i PID_FILE - write pid file, appending listening port"
349 " to the filename\n"
350 " -p PASSWD_FILE - read a plaintext client password file"
351 " [default: passwd]\n"
352 " -a ACTIVE_FILE - create file during times when a host is"
353 " connected\n"
354 " -c ACTIONS_FILE - on events, execute commands specified in"
355 " a file\n"
356 " -l LISTEN_PORT - port to listen for client connections"
357 " [default: 5999]\n"
358 " -b IP_ADDRESS - bind listening sockets to a specific IP"
359 " [default: any]\n");
360 fprintf(stderr,
361 " -s FBS_PREFIX - save host sessions in rfbproxy-compatible"
362 " files\n"
363 " (optionally appending 3-digit session IDs"
364 " to the\n"
365 " filename prefix, only if used without the"
366 " -j option)\n"
367 " -j - join saved sessions (see -s option) in one"
368 " session file\n"
369 " -t - use Tight encoding for host communications"
370 " if possible\n"
371 " -T COMPR_LEVEL - like -t, but use the specified compression"
372 " level (1..9)\n");
373 fprintf(stderr,
374 " -g LOG_FILE - write logs to the specified file"
375 " [default: reflector.log]\n"
376 " -v LOG_LEVEL - set verbosity level for the log file (0..%d)"
377 " [default: %d]\n"
378 " -f LOG_LEVEL - run in foreground, show logs on stderr"
379 " at the specified\n"
380 " verbosity level (0..%d) [note: use %d for"
381 " normal output]\n"
382 " -q - suppress printing copyright banner at startup\n"
383 " -h - print this help message\n\n",
384 LL_DEBUG, LL_INFO, LL_DEBUG, LL_MSG);
385
386 fprintf(stderr,
387 "Please refer to the README file for a description of the file"
388 " formats for\n"
389 " HOST_INFO_FILE and PASSWD_FILE files mentioned above in this"
390 " help text.\n\n");
391 }
392
read_password_file(void)393 static int read_password_file(void)
394 {
395 FILE *passwd_fp;
396 unsigned char *password_ptr = opt_client_password;
397 int line = 0, len = 0;
398 int c;
399
400 /* Fill passwords with zeros */
401 memset(opt_client_password, 0, 9);
402 memset(opt_client_ro_password, 0, 9);
403
404 log_write(LL_DETAIL, "Looking for passwords in the file \"%s\"",
405 opt_passwd_filename);
406
407 passwd_fp = fopen(opt_passwd_filename, "r");
408 if (passwd_fp == NULL) {
409 log_write(LL_WARN,
410 "No client password file, assuming no authentication");
411 return 1;
412 }
413
414 /* Read password file */
415 while (line < 2) {
416 c = getc(passwd_fp);
417 if (c == '\r')
418 c = getc(passwd_fp); /* Handle MS-DOS-style end of line */
419 if (c != '\n' && c != EOF && len < 8) {
420 password_ptr[len++] = c;
421 } else {
422 password_ptr[len] = '\0';
423 /* Truncate long passwords */
424 if (len == 8 && c != '\n' && c != EOF) {
425 log_write(LL_WARN, "Using only 8 first bytes of a longer password");
426 do {
427 c = getc(passwd_fp);
428 } while (c != '\n' && c != EOF);
429 }
430 /* End of file */
431 if (c == EOF)
432 break;
433 /* Empty password means no authentication */
434 if (len == 0) {
435 log_write(LL_WARN, "Got empty client password, hoping no auth is ok");
436 }
437 /* End of line */
438 if (++line == 1) {
439 password_ptr = opt_client_ro_password;
440 }
441 len = 0;
442 }
443 }
444 if (len == 0) {
445 if (line == 0) {
446 log_write(LL_WARN, "Client password not specified, assuming no auth");
447 } else {
448 line--;
449 }
450 }
451
452 log_write(LL_DEBUG, "Got %d password(s) from file, including empty ones",
453 line + 1);
454
455 /* Provide reasonable defaults if not all two passwords set */
456 if (line == 0) {
457 log_write(LL_DETAIL, "Read-only client password not specified");
458 strcpy((char *)opt_client_ro_password, (char *)opt_client_password);
459 }
460
461 fclose(passwd_fp);
462 return 1;
463 }
464
init_screen_info(void)465 static int init_screen_info(void)
466 {
467 union {
468 CARD32 value32;
469 CARD8 test;
470 } little_endian;
471
472 /* Set the initial desktop name */
473 g_screen_info.name_length = 1;
474 g_screen_info.name = malloc(2);
475 if (g_screen_info.name == NULL) {
476 log_write(LL_ERROR, "Error allocating memory");
477 return 0;
478 }
479 memcpy(g_screen_info.name, "?", 2);
480
481 /* Fill in the PIXEL_FORMAT structure */
482 g_screen_info.pixformat.bits_pixel = 32;
483 g_screen_info.pixformat.color_depth = 24;
484 g_screen_info.pixformat.true_color = 1;
485 g_screen_info.pixformat.r_max = 255;
486 g_screen_info.pixformat.g_max = 255;
487 g_screen_info.pixformat.b_max = 255;
488 g_screen_info.pixformat.r_shift = 16;
489 g_screen_info.pixformat.g_shift = 8;
490 g_screen_info.pixformat.b_shift = 0;
491
492 /* Set correct endian flag in PIXEL_FORMAT */
493 little_endian.value32 = 1;
494 if (little_endian.test) {
495 log_write(LL_DEBUG, "Our machine is little endian");
496 g_screen_info.pixformat.big_endian = 0;
497 } else {
498 log_write(LL_DEBUG, "Our machine is big endian");
499 g_screen_info.pixformat.big_endian = 1;
500 }
501
502 /* Make sure we would not try to free the framebuffer */
503 g_framebuffer = NULL;
504
505 return 1;
506 }
507
write_pid_file(void)508 static int write_pid_file(void)
509 {
510 int pid_fd, len;
511 char buf[32]; /* 32 bytes should be more than enough */
512
513 if (opt_pid_file[0] == '\0')
514 return 1;
515
516 pid_fd = open(opt_pid_file, O_WRONLY | O_CREAT | O_EXCL, 0644);
517 if (pid_fd == -1) {
518 log_write(LL_ERROR, "Pid file exists, another instance may be running");
519 return 0;
520 }
521 sprintf(buf, "%d\n", (int)getpid());
522 len = strlen(buf);
523 if (write(pid_fd, buf, len) != len) {
524 close(pid_fd);
525 log_write(LL_ERROR, "Error writing to pid file");
526 return 0;
527 }
528
529 log_write(LL_DEBUG, "Wrote pid file: %s", opt_pid_file);
530
531 close(pid_fd);
532 return 1;
533 }
534
remove_pid_file(void)535 static int remove_pid_file(void)
536 {
537 if (opt_pid_file[0] != '\0') {
538 if (unlink(opt_pid_file) == 0) {
539 log_write(LL_DEBUG, "Removed pid file", opt_pid_file);
540 } else {
541 log_write(LL_WARN, "Error removing pid file: %s", opt_pid_file);
542 return 0;
543 }
544 }
545 return 1;
546 }
547
548