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