Commit dea53c22 authored by runge's avatar runge

x11vnc: -unixpw_cmd, -passwfile cmd:/custom:, -sslnofail, -ultrafilexfer

parent 52ed38f6
2006-09-20 Karl Runge <runge@karlrunge.com>
* x11vnc: -unixpw_cmd, -passwfile cmd:/custom:, -sslnofail,
-ultrafilexfer
2006-09-17 Karl Runge <runge@karlrunge.com> 2006-09-17 Karl Runge <runge@karlrunge.com>
* x11vnc: move some info printout to -v, -verbose mode. Add * x11vnc: move some info printout to -v, -verbose mode. Add
-connect_or_exit option. Have -rfbport 0 lead to no TCP -connect_or_exit option. Have -rfbport 0 lead to no TCP
......
This diff is collapsed.
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
#include "rates.h" #include "rates.h"
#include "screen.h" #include "screen.h"
#include "unixpw.h" #include "unixpw.h"
#include "user.h"
#include "scan.h" #include "scan.h"
#include "sslcmds.h" #include "sslcmds.h"
#include "sslhelper.h" #include "sslhelper.h"
...@@ -49,10 +50,10 @@ void send_client_info(char *str); ...@@ -49,10 +50,10 @@ void send_client_info(char *str);
void adjust_grabs(int grab, int quiet); void adjust_grabs(int grab, int quiet);
void check_new_clients(void); void check_new_clients(void);
int accept_client(rfbClientPtr client); int accept_client(rfbClientPtr client);
int run_user_command(char *cmd, rfbClientPtr client, char *mode, char *input,
int len, FILE *output);
static rfbClientPtr *client_match(char *str); static rfbClientPtr *client_match(char *str);
static int run_user_command(char *cmd, rfbClientPtr client, char *mode);
static void free_client_data(rfbClientPtr client); static void free_client_data(rfbClientPtr client);
static int check_access(char *addr); static int check_access(char *addr);
static void ugly_geom(char *p, int *x, int *y); static void ugly_geom(char *p, int *x, int *y);
...@@ -365,12 +366,16 @@ int cmd_ok(char *cmd) { ...@@ -365,12 +366,16 @@ int cmd_ok(char *cmd) {
* utility to run a user supplied command setting some RFB_ env vars. * utility to run a user supplied command setting some RFB_ env vars.
* used by, e.g., accept_client() and client_gone() * used by, e.g., accept_client() and client_gone()
*/ */
static int run_user_command(char *cmd, rfbClientPtr client, char *mode) { int run_user_command(char *cmd, rfbClientPtr client, char *mode, char *input,
int len, FILE *output) {
char *old_display = NULL; char *old_display = NULL;
char *addr = client->host; char *addr = client->host;
char str[100]; char str[100];
int rc, ok; int rc, ok;
ClientData *cd = (ClientData *) client->clientData; ClientData *cd = NULL;
if (client != NULL) {
cd = (ClientData *) client->clientData;
}
if (addr == NULL || addr[0] == '\0') { if (addr == NULL || addr[0] == '\0') {
addr = "unknown-host"; addr = "unknown-host";
...@@ -492,6 +497,15 @@ static int run_user_command(char *cmd, rfbClientPtr client, char *mode) { ...@@ -492,6 +497,15 @@ static int run_user_command(char *cmd, rfbClientPtr client, char *mode) {
if (!strcmp(mode, "gone") && cmd_ok("gone")) { if (!strcmp(mode, "gone") && cmd_ok("gone")) {
ok = 1; ok = 1;
} }
if (!strcmp(mode, "cmd_verify") && cmd_ok("unixpw")) {
ok = 1;
}
if (!strcmp(mode, "read_passwds") && cmd_ok("passwdfile")) {
ok = 1;
}
if (!strcmp(mode, "custom_passwd") && cmd_ok("custom_passwd")) {
ok = 1;
}
if (no_external_cmds || !ok) { if (no_external_cmds || !ok) {
rfbLogEnable(1); rfbLogEnable(1);
rfbLog("cannot run external commands in -nocmds mode:\n"); rfbLog("cannot run external commands in -nocmds mode:\n");
...@@ -502,6 +516,32 @@ static int run_user_command(char *cmd, rfbClientPtr client, char *mode) { ...@@ -502,6 +516,32 @@ static int run_user_command(char *cmd, rfbClientPtr client, char *mode) {
rfbLog("running command:\n"); rfbLog("running command:\n");
rfbLog(" %s\n", cmd); rfbLog(" %s\n", cmd);
if (output != NULL) {
FILE *ph = popen(cmd, "r");
char line[1024];
if (ph == NULL) {
rfbLog("popen(%s) failed", cmd);
rfbLogPerror("popen");
clean_up_exit(1);
}
while (fgets(line, 1024, ph) != NULL) {
if (0) fprintf(stderr, "line: %s", line);
fprintf(output, "%s", line);
}
rc = pclose(ph);
goto got_rc;
} else if (input != NULL) {
FILE *ph = popen(cmd, "w");
if (ph == NULL) {
rfbLog("popen(%s) failed", cmd);
rfbLogPerror("popen");
clean_up_exit(1);
}
write(fileno(ph), input, len);
rc = pclose(ph);
goto got_rc;
}
#if LIBVNCSERVER_HAVE_FORK #if LIBVNCSERVER_HAVE_FORK
{ {
pid_t pid, pidw; pid_t pid, pidw;
...@@ -549,6 +589,7 @@ static int run_user_command(char *cmd, rfbClientPtr client, char *mode) { ...@@ -549,6 +589,7 @@ static int run_user_command(char *cmd, rfbClientPtr client, char *mode) {
/* this will still have port 5900 open */ /* this will still have port 5900 open */
rc = system(cmd); rc = system(cmd);
#endif #endif
got_rc:
if (rc >= 256) { if (rc >= 256) {
rc = rc/256; rc = rc/256;
...@@ -671,7 +712,7 @@ void client_gone(rfbClientPtr client) { ...@@ -671,7 +712,7 @@ void client_gone(rfbClientPtr client) {
free(userhost); free(userhost);
} else { } else {
rfbLog("client_gone: using cmd: %s\n", client->host); rfbLog("client_gone: using cmd: %s\n", client->host);
run_user_command(gone_cmd, client, "gone"); run_user_command(gone_cmd, client, "gone", NULL,0,NULL);
} }
} }
...@@ -1412,7 +1453,7 @@ int accept_client(rfbClientPtr client) { ...@@ -1412,7 +1453,7 @@ int accept_client(rfbClientPtr client) {
int rc; int rc;
rfbLog("accept_client: using cmd for: %s\n", addr); rfbLog("accept_client: using cmd for: %s\n", addr);
rc = run_user_command(cmd, client, "accept"); rc = run_user_command(cmd, client, "accept", NULL, 0, NULL);
if (action) { if (action) {
int result; int result;
...@@ -1952,6 +1993,8 @@ enum rfbNewClientAction new_client(rfbClientPtr client) { ...@@ -1952,6 +1993,8 @@ enum rfbNewClientAction new_client(rfbClientPtr client) {
last_event = last_input = time(NULL); last_event = last_input = time(NULL);
latest_client = client;
if (inetd) { if (inetd) {
/* /*
* Set this so we exit as soon as connection closes, * Set this so we exit as soon as connection closes,
...@@ -2025,6 +2068,22 @@ if (0) fprintf(stderr, "SET ssl_helper_pid: %d\n", openssl_last_helper_pid); ...@@ -2025,6 +2068,22 @@ if (0) fprintf(stderr, "SET ssl_helper_pid: %d\n", openssl_last_helper_pid);
return(RFB_CLIENT_REFUSE); return(RFB_CLIENT_REFUSE);
} }
if (passwdfile) {
if (strstr(passwdfile, "read:") == passwdfile ||
strstr(passwdfile, "cmd:") == passwdfile) {
if (read_passwds(passwdfile)) {
install_passwds();
} else {
rfbLog("problem reading: %s\n", passwdfile);
clean_up_exit(1);
}
} else if (strstr(passwdfile, "custom:") == passwdfile) {
if (screen) {
screen->passwordCheck = custom_passwd_check;
}
}
}
cd->uid = clients_served; cd->uid = clients_served;
...@@ -2340,7 +2399,7 @@ void check_new_clients(void) { ...@@ -2340,7 +2399,7 @@ void check_new_clients(void) {
} }
if (run_after_accept) { if (run_after_accept) {
run_user_command(afteraccept_cmd, cl, run_user_command(afteraccept_cmd, cl,
"afteraccept"); "afteraccept", NULL, 0, NULL);
} }
} }
} }
......
...@@ -32,5 +32,7 @@ extern void send_client_info(char *str); ...@@ -32,5 +32,7 @@ extern void send_client_info(char *str);
extern void adjust_grabs(int grab, int quiet); extern void adjust_grabs(int grab, int quiet);
extern void check_new_clients(void); extern void check_new_clients(void);
extern int accept_client(rfbClientPtr client); extern int accept_client(rfbClientPtr client);
extern int run_user_command(char *cmd, rfbClientPtr client, char *mode, char *input,
int len, FILE *output);
#endif /* _X11VNC_CONNECTIONS_H */ #endif /* _X11VNC_CONNECTIONS_H */
...@@ -315,10 +315,11 @@ void print_help(int mode) { ...@@ -315,10 +315,11 @@ void print_help(int mode) {
" change the global or per-client viewonly state the\n" " change the global or per-client viewonly state the\n"
" filetransfer permissions will NOT change.\n" " filetransfer permissions will NOT change.\n"
"\n" "\n"
" Note, to *enable* UltraVNC filetransfer (currently\n" "-ultrafilexfer Note, to *enable* UltraVNC filetransfer (currently\n"
" disabled by default, this may change...) and to get it\n" " disabled by default, this may change...) and to get it\n"
" to work you probably need to supply these libvncserver\n" " to work you probably need to supply these libvncserver\n"
" options: \"-rfbversion 3.6 -permitfiletransfer\"\n" " options: \"-rfbversion 3.6 -permitfiletransfer\"\n"
" \"-ultrafilexfer\" is an alias for this combination.\n"
"\n" "\n"
"-http Instead of using -httpdir (see below) to specify\n" "-http Instead of using -httpdir (see below) to specify\n"
" where the Java vncviewer applet is, have x11vnc try\n" " where the Java vncviewer applet is, have x11vnc try\n"
...@@ -434,20 +435,59 @@ void print_help(int mode) { ...@@ -434,20 +435,59 @@ void print_help(int mode) {
"-passwdfile filename Specify the libvncserver password via the first line\n" "-passwdfile filename Specify the libvncserver password via the first line\n"
" of the file \"filename\" (instead of via -passwd on\n" " of the file \"filename\" (instead of via -passwd on\n"
" the command line where others might see it via ps(1)).\n" " the command line where others might see it via ps(1)).\n"
" See below for how to supply multiple passwords.\n" "\n"
" See the descriptions below for how to supply multiple\n"
" passwords, view-only passwords, to specify external\n"
" programs for the authentication, and other features.\n"
"\n" "\n"
" If the filename is prefixed with \"rm:\" it will be\n" " If the filename is prefixed with \"rm:\" it will be\n"
" removed after being read. Perhaps this is useful in\n" " removed after being read. Perhaps this is useful in\n"
" limiting the readability of the file. In general,\n" " limiting the readability of the file. In general, the\n"
" the password file should not be readable by untrusted\n" " password file should not be readable by untrusted users\n"
" users (BTW: neither should the VNC -rfbauth file:\n" " (BTW: neither should the VNC -rfbauth file: it is NOT\n"
" it is NOT encrypted, only obscured).\n" " encrypted, only obscured with a fixed key).\n"
"\n" "\n"
" If the filename is prefixed with \"read:\" it will\n" " If the filename is prefixed with \"read:\" it will\n"
" periodically be checked for changes and reread.\n" " periodically be checked for changes and reread. It it\n"
"\n" " guaranteed to be reread just when a new client connects\n"
" Note that only the first 8 characters of a password\n" " so that the latest passwords will be used.\n"
" are used.\n" "\n"
" If \"filename\" is prefixed with \"cmd:\" then the\n"
" string after the \":\" is run as an external command:\n"
" the output of the command will be interpreted as if it\n"
" were read from a password file (see below). If the\n"
" command does not exit with 0, then x11vnc terminates\n"
" immediately. To specify more than 1000 passwords this\n"
" way set X11VNC_MAX_PASSWDS before starting x11vnc.\n"
" The environment variables are set as in -accept.\n"
"\n"
" Note that due to the VNC protocol only the first 8\n"
" characters of a password are used (DES key).\n"
"\n"
" If \"filename\" is prefixed with \"custom:\" then a\n"
" custom password checker is supplied as an external\n"
" command following the \":\". The command will be run\n"
" when a client authenticates. If the command exits with\n"
" 0 the client is accepted, otherwise it is rejected.\n"
" The environment variables are set as in -accept.\n"
"\n"
" The standard input to the custom command will be a\n"
" decimal digit \"len\" followed by a newline. \"len\"\n"
" specifies the challenge size and is usually 16 (the\n"
" VNC spec). Then follows len bytes which is the random\n"
" challenge string that was sent to the client. This is\n"
" then followed by len more bytes holding the client's\n"
" response (i.e. the challenge string encrypted via DES\n"
" with the user password in the standard situation).\n"
"\n"
" The \"custom:\" scheme can be useful to implement\n"
" dynamic passwords or to implement methods where longer\n"
" passwords and/or different encryption algorithms\n"
" are used. The latter will require customizing the VNC\n"
" client as well. One could create an MD5SUM based scheme\n"
" for example.\n"
"\n"
" File format for -passwdfile:\n"
"\n" "\n"
" If multiple non-blank lines exist in the file they are\n" " If multiple non-blank lines exist in the file they are\n"
" all taken as valid passwords. Blank lines are ignored.\n" " all taken as valid passwords. Blank lines are ignored.\n"
...@@ -611,6 +651,17 @@ void print_help(int mode) { ...@@ -611,6 +651,17 @@ void print_help(int mode) {
" to use -users unixpw= to switch the process user after\n" " to use -users unixpw= to switch the process user after\n"
" the user logs in.\n" " the user logs in.\n"
"\n" "\n"
"-unixpw_cmd str As -unixpw above, however do not use su(1) but rather\n"
" run the externally supplied command \"str\". The first\n"
" line of its stdin will the username and the second line\n"
" the received password. If the command exits with status\n"
" 0 (success) the VNC client will be accepted. It will be\n"
" rejected for any other return status. Dynamic passwords\n"
" and non-unix passwords can be implemented this way by\n"
" providing your own custom helper program. Note that\n"
" under unixpw mode the remote viewer is given 3 tries\n"
" to enter the correct password.\n"
"\n"
#endif #endif
"-display WAIT:... A special usage mode for the normal -display option.\n" "-display WAIT:... A special usage mode for the normal -display option.\n"
" Useful with -unixpw, but can be used independently\n" " Useful with -unixpw, but can be used independently\n"
...@@ -792,6 +843,11 @@ void print_help(int mode) { ...@@ -792,6 +843,11 @@ void print_help(int mode) {
" Set to zero to poll forever. Set to a negative value\n" " Set to zero to poll forever. Set to a negative value\n"
" to use the builtin setting.\n" " to use the builtin setting.\n"
"\n" "\n"
"-sslnofail Exit at the first SSL connection failure. Useful when\n"
" scripting SSL connections (e.g. x11vnc is started via\n"
" ssh) and you do not want x11vnc waiting around for more\n"
" connections, tying up ports, etc.\n"
"\n"
"-ssldir [dir] Use [dir] as an alternate ssl certificate and key\n" "-ssldir [dir] Use [dir] as an alternate ssl certificate and key\n"
" management toplevel directory. The default is\n" " management toplevel directory. The default is\n"
" ~/.vnc/certs\n" " ~/.vnc/certs\n"
...@@ -3340,7 +3396,7 @@ void print_help(int mode) { ...@@ -3340,7 +3396,7 @@ void print_help(int mode) {
"\n" "\n"
" stunnel, ssl, unixpw, WAIT, id, accept, afteraccept,\n" " stunnel, ssl, unixpw, WAIT, id, accept, afteraccept,\n"
" gone, pipeinput, v4l-info, rawfb-setup, dt, gui,\n" " gone, pipeinput, v4l-info, rawfb-setup, dt, gui,\n"
" storepasswd, crash.\n" " storepasswd, passwdfile, custom_passwd, crash.\n"
"\n" "\n"
" See each option's help to learn the associated external\n" " See each option's help to learn the associated external\n"
" command. Note that the -nocmds option takes precedence\n" " command. Note that the -nocmds option takes precedence\n"
......
...@@ -19,11 +19,13 @@ char *passwdfile = NULL; /* -passwdfile */ ...@@ -19,11 +19,13 @@ char *passwdfile = NULL; /* -passwdfile */
int unixpw = 0; /* -unixpw */ int unixpw = 0; /* -unixpw */
int unixpw_nis = 0; /* -unixpw_nis */ int unixpw_nis = 0; /* -unixpw_nis */
char *unixpw_list = NULL; char *unixpw_list = NULL;
char *unixpw_cmd = NULL;
int use_stunnel = 0; /* -stunnel */ int use_stunnel = 0; /* -stunnel */
int stunnel_port = 0; int stunnel_port = 0;
char *stunnel_pem = NULL; char *stunnel_pem = NULL;
int use_openssl = 0; int use_openssl = 0;
int http_ssl = 0; int http_ssl = 0;
int ssl_no_fail = 0;
char *openssl_pem = NULL; char *openssl_pem = NULL;
char *ssl_certs_dir = NULL; char *ssl_certs_dir = NULL;
int https_port_num = -1; int https_port_num = -1;
......
...@@ -19,11 +19,13 @@ extern char *passwdfile; ...@@ -19,11 +19,13 @@ extern char *passwdfile;
extern int unixpw; extern int unixpw;
extern int unixpw_nis; extern int unixpw_nis;
extern char *unixpw_list; extern char *unixpw_list;
extern char *unixpw_cmd;
extern int use_stunnel; extern int use_stunnel;
extern int stunnel_port; extern int stunnel_port;
extern char *stunnel_pem; extern char *stunnel_pem;
extern int use_openssl; extern int use_openssl;
extern int http_ssl; extern int http_ssl;
extern int ssl_no_fail;
extern char *openssl_pem; extern char *openssl_pem;
extern char *ssl_certs_dir; extern char *ssl_certs_dir;
extern int https_port_num; extern int https_port_num;
......
...@@ -1326,6 +1326,9 @@ void accept_openssl(int mode) { ...@@ -1326,6 +1326,9 @@ void accept_openssl(int mode) {
if (sock < 0) { if (sock < 0) {
rfbLog("SSL: accept_openssl: accept connection failed\n"); rfbLog("SSL: accept_openssl: accept connection failed\n");
rfbLogPerror("accept"); rfbLogPerror("accept");
if (ssl_no_fail) {
clean_up_exit(1);
}
return; return;
} }
listen = openssl_sock; listen = openssl_sock;
...@@ -1335,6 +1338,9 @@ void accept_openssl(int mode) { ...@@ -1335,6 +1338,9 @@ void accept_openssl(int mode) {
if (sock < 0) { if (sock < 0) {
rfbLog("SSL: accept_openssl: accept connection failed\n"); rfbLog("SSL: accept_openssl: accept connection failed\n");
rfbLogPerror("accept"); rfbLogPerror("accept");
if (ssl_no_fail) {
clean_up_exit(1);
}
return; return;
} }
listen = https_sock; listen = https_sock;
...@@ -1357,7 +1363,7 @@ void accept_openssl(int mode) { ...@@ -1357,7 +1363,7 @@ void accept_openssl(int mode) {
if (! cport) { if (! cport) {
rfbLog("SSL: accept_openssl: could not find open port.\n"); rfbLog("SSL: accept_openssl: could not find open port.\n");
close(sock); close(sock);
if (mode == OPENSSL_INETD) { if (mode == OPENSSL_INETD || ssl_no_fail) {
clean_up_exit(1); clean_up_exit(1);
} }
return; return;
...@@ -1370,7 +1376,7 @@ void accept_openssl(int mode) { ...@@ -1370,7 +1376,7 @@ void accept_openssl(int mode) {
rfbLog("SSL: accept_openssl: could not listen on port %d.\n", rfbLog("SSL: accept_openssl: could not listen on port %d.\n",
cport); cport);
close(sock); close(sock);
if (mode == OPENSSL_INETD) { if (mode == OPENSSL_INETD || ssl_no_fail) {
clean_up_exit(1); clean_up_exit(1);
} }
return; return;
...@@ -1416,7 +1422,7 @@ void accept_openssl(int mode) { ...@@ -1416,7 +1422,7 @@ void accept_openssl(int mode) {
rfbLogPerror("fork"); rfbLogPerror("fork");
close(sock); close(sock);
close(csock); close(csock);
if (mode == OPENSSL_INETD) { if (mode == OPENSSL_INETD || ssl_no_fail) {
clean_up_exit(1); clean_up_exit(1);
} }
return; return;
...@@ -1721,7 +1727,7 @@ if (db) fprintf(stderr, "iface: %s\n", iface); ...@@ -1721,7 +1727,7 @@ if (db) fprintf(stderr, "iface: %s\n", iface);
kill(pid, SIGTERM); kill(pid, SIGTERM);
waitpid(pid, &status, WNOHANG); waitpid(pid, &status, WNOHANG);
if (mode == OPENSSL_INETD) { if (mode == OPENSSL_INETD || ssl_no_fail) {
clean_up_exit(1); clean_up_exit(1);
} }
return; return;
...@@ -1787,7 +1793,7 @@ if (db) fprintf(stderr, "iface: %s\n", iface); ...@@ -1787,7 +1793,7 @@ if (db) fprintf(stderr, "iface: %s\n", iface);
} }
kill(pid, SIGTERM); kill(pid, SIGTERM);
waitpid(pid, &status, WNOHANG); waitpid(pid, &status, WNOHANG);
if (mode == OPENSSL_INETD) { if (mode == OPENSSL_INETD || ssl_no_fail) {
clean_up_exit(1); clean_up_exit(1);
} }
return; return;
...@@ -1820,7 +1826,7 @@ if (db) fprintf(stderr, "iface: %s\n", iface); ...@@ -1820,7 +1826,7 @@ if (db) fprintf(stderr, "iface: %s\n", iface);
kill(pid, SIGTERM); kill(pid, SIGTERM);
waitpid(pid, &status, WNOHANG); waitpid(pid, &status, WNOHANG);
if (mode == OPENSSL_INETD) { if (mode == OPENSSL_INETD || ssl_no_fail) {
clean_up_exit(1); clean_up_exit(1);
} }
return; return;
......
...@@ -63,6 +63,7 @@ void unixpw_deny(void); ...@@ -63,6 +63,7 @@ void unixpw_deny(void);
void unixpw_msg(char *msg, int delay); void unixpw_msg(char *msg, int delay);
int su_verify(char *user, char *pass, char *cmd, char *rbuf, int *rbuf_size); int su_verify(char *user, char *pass, char *cmd, char *rbuf, int *rbuf_size);
int crypt_verify(char *user, char *pass); int crypt_verify(char *user, char *pass);
int cmd_verify(char *user, char *pass);
static int white(void); static int white(void);
static int text_x(void); static int text_x(void);
...@@ -408,6 +409,51 @@ int crypt_verify(char *user, char *pass) { ...@@ -408,6 +409,51 @@ int crypt_verify(char *user, char *pass) {
#endif /* UNIXPW_CRYPT */ #endif /* UNIXPW_CRYPT */
} }
int cmd_verify(char *user, char *pass) {
int i, len, rc;
char *str;
if (! user || ! pass) {
return 0;
}
if (! unixpw_cmd || *unixpw_cmd == '\0') {
return 0;
}
if (unixpw_client) {
ClientData *cd = (ClientData *) unixpw_client->clientData;
if (cd) {
cd->username = strdup(user);
}
}
len = strlen(user) + 1 + strlen(pass) + 1 + 1;
str = (char *) malloc(len);
if (! str) {
return 0;
}
str[0] = '\0';
strcat(str, user);
strcat(str, "\n");
strcat(str, pass);
if (!strchr(pass, '\n')) {
strcat(str, "\n");
}
rc = run_user_command(unixpw_cmd, unixpw_client, "cmd_verify",
str, strlen(str), NULL);
for (i=0; i < len; i++) {
str[i] = '\0';
}
free(str);
if (rc == 0) {
return 1;
} else {
return 0;
}
}
int su_verify(char *user, char *pass, char *cmd, char *rbuf, int *rbuf_size) { int su_verify(char *user, char *pass, char *cmd, char *rbuf, int *rbuf_size) {
#ifndef UNIXPW_SU #ifndef UNIXPW_SU
return 0; return 0;
...@@ -849,6 +895,7 @@ static void unixpw_verify(char *user, char *pass) { ...@@ -849,6 +895,7 @@ static void unixpw_verify(char *user, char *pass) {
char log[] = "login: "; char log[] = "login: ";
char *colon = NULL; char *colon = NULL;
ClientData *cd = NULL; ClientData *cd = NULL;
int ok;
if (db) fprintf(stderr, "unixpw_verify: '%s' '%s'\n", user, db > 1 ? pass : "********"); if (db) fprintf(stderr, "unixpw_verify: '%s' '%s'\n", user, db > 1 ? pass : "********");
rfbLog("unixpw_verify: %s\n", user); rfbLog("unixpw_verify: %s\n", user);
...@@ -871,30 +918,43 @@ if (db) fprintf(stderr, "unixpw_verify: '%s' '%s'\n", user, db > 1 ? pass : "*** ...@@ -871,30 +918,43 @@ if (db) fprintf(stderr, "unixpw_verify: '%s' '%s'\n", user, db > 1 ? pass : "***
} }
} }
if (unixpw_nis) { ok = 0;
if (unixpw_cmd) {
if (cmd_verify(user, pass)) {
rfbLog("unixpw_verify: cmd_verify login for '%s'"
" succeeded.\n", user);
ok = 1;
} else {
rfbLog("unixpw_verify: crypt_verify login for '%s'"
" failed.\n", user);
usleep(3000*1000);
ok = 0;
}
} else if (unixpw_nis) {
if (crypt_verify(user, pass)) { if (crypt_verify(user, pass)) {
rfbLog("unixpw_verify: crypt_verify login for '%s'" rfbLog("unixpw_verify: crypt_verify login for '%s'"
" succeeded.\n", user); " succeeded.\n", user);
unixpw_accept(user); ok = 1;
if (keep_unixpw) {
keep_unixpw_user = strdup(user);
keep_unixpw_pass = strdup(pass);
if (colon) {
keep_unixpw_opts = strdup(colon+1);
} else { } else {
keep_unixpw_opts = strdup(""); rfbLog("unixpw_verify: crypt_verify login for '%s'"
} " failed.\n", user);
}
if (colon) *colon = ':';
return;
}
rfbLog("unixpw_verify: crypt_verify login for '%s' failed.\n",
user);
usleep(3000*1000); usleep(3000*1000);
ok = 0;
}
} else { } else {
if (su_verify(user, pass, NULL, NULL, NULL)) { if (su_verify(user, pass, NULL, NULL, NULL)) {
rfbLog("unixpw_verify: su_verify login for '%s'" rfbLog("unixpw_verify: su_verify login for '%s'"
" succeeded.\n", user); " succeeded.\n", user);
ok = 1;
} else {
rfbLog("unixpw_verify: su_verify login for '%s'"
" failed.\n", user);
/* use su(1)'s sleep */
ok = 0;
}
}
if (ok) {
unixpw_accept(user); unixpw_accept(user);
if (keep_unixpw) { if (keep_unixpw) {
keep_unixpw_user = strdup(user); keep_unixpw_user = strdup(user);
...@@ -908,9 +968,6 @@ if (db) fprintf(stderr, "unixpw_verify: '%s' '%s'\n", user, db > 1 ? pass : "*** ...@@ -908,9 +968,6 @@ if (db) fprintf(stderr, "unixpw_verify: '%s' '%s'\n", user, db > 1 ? pass : "***
if (colon) *colon = ':'; if (colon) *colon = ':';
return; return;
} }
rfbLog("unixpw_verify: su_verify login for '%s' failed.\n",
user);
}
if (colon) *colon = ':'; if (colon) *colon = ':';
if (tries < 2) { if (tries < 2) {
......
...@@ -10,6 +10,7 @@ extern void unixpw_deny(void); ...@@ -10,6 +10,7 @@ extern void unixpw_deny(void);
extern void unixpw_msg(char *msg, int delay); extern void unixpw_msg(char *msg, int delay);
extern int su_verify(char *user, char *pass, char *cmd, char *rbuf, int *rbuf_size); extern int su_verify(char *user, char *pass, char *cmd, char *rbuf, int *rbuf_size);
extern int crypt_verify(char *user, char *pass); extern int crypt_verify(char *user, char *pass);
extern int cmd_verify(char *user, char *pass);
extern int unixpw_in_progress; extern int unixpw_in_progress;
extern int unixpw_denied; extern int unixpw_denied;
......
...@@ -19,9 +19,9 @@ void lurk_loop(char *str); ...@@ -19,9 +19,9 @@ void lurk_loop(char *str);
int switch_user(char *user, int fb_mode); int switch_user(char *user, int fb_mode);
int read_passwds(char *passfile); int read_passwds(char *passfile);
void install_passwds(void); void install_passwds(void);
void check_new_passwds(void); void check_new_passwds(int force);
int wait_for_client(int *argc, char** argv, int http); int wait_for_client(int *argc, char** argv, int http);
rfbBool custom_passwd_check(rfbClientPtr cl, const char *response, int len);
static void switch_user_task_dummy(void); static void switch_user_task_dummy(void);
static void switch_user_task_solid_bg(void); static void switch_user_task_solid_bg(void);
...@@ -722,16 +722,21 @@ int read_passwds(char *passfile) { ...@@ -722,16 +722,21 @@ int read_passwds(char *passfile) {
char line[1024]; char line[1024];
char *filename; char *filename;
char **old_passwd_list = passwd_list; char **old_passwd_list = passwd_list;
int remove = 0; int linecount = 0, i, remove = 0, read_mode = 0, begin_vo = -1;
int read_mode = 0;
int begin_vo = -1;
struct stat sbuf; struct stat sbuf;
int linecount = 0, i, max; static int max = -1;
FILE *in; FILE *in = NULL;
static time_t last_read = 0; static time_t last_read = 0;
static int read_cnt = 0; static int read_cnt = 0;
int db_passwd = 0; int db_passwd = 0;
if (max < 0) {
max = 1000;
if (getenv("X11VNC_MAX_PASSWDS")) {
max = atoi(getenv("X11VNC_MAX_PASSWDS"));
}
}
filename = passfile; filename = passfile;
if (strstr(filename, "rm:") == filename) { if (strstr(filename, "rm:") == filename) {
filename += strlen("rm:"); filename += strlen("rm:");
...@@ -741,18 +746,37 @@ int read_passwds(char *passfile) { ...@@ -741,18 +746,37 @@ int read_passwds(char *passfile) {
read_mode = 1; read_mode = 1;
if (stat(filename, &sbuf) == 0) { if (stat(filename, &sbuf) == 0) {
if (sbuf.st_mtime <= last_read) { if (sbuf.st_mtime <= last_read) {
return 0; return 1;
} }
last_read = sbuf.st_mtime; last_read = sbuf.st_mtime;
} }
} else if (strstr(filename, "cmd:") == filename) {
int rc;
filename += strlen("cmd:");
read_mode = 1;
in = tmpfile();
if (in == NULL) {
rfbLog("run_user_command tmpfile() failed: %s\n",
filename);
clean_up_exit(1);
}
rc = run_user_command(filename, latest_client, "read_passwds",
NULL, 0, in);
if (rc != 0) {
rfbLog("run_user_command command failed: %s\n",
filename);
clean_up_exit(1);
}
rewind(in);
} else if (strstr(filename, "custom:") == filename) {
return 1;
} }
if (stat(filename, &sbuf) == 0) { if (in == NULL && stat(filename, &sbuf) == 0) {
/* (poor...) upper bound to number of lines */ /* (poor...) upper bound to number of lines */
max = (int) sbuf.st_size; max = (int) sbuf.st_size;
last_read = sbuf.st_mtime; last_read = sbuf.st_mtime;
} else {
max = 64;
} }
/* create 1 more than max to have it be the ending NULL */ /* create 1 more than max to have it be the ending NULL */
...@@ -761,7 +785,9 @@ int read_passwds(char *passfile) { ...@@ -761,7 +785,9 @@ int read_passwds(char *passfile) {
passwd_list[i] = NULL; passwd_list[i] = NULL;
} }
if (in == NULL) {
in = fopen(filename, "r"); in = fopen(filename, "r");
}
if (in == NULL) { if (in == NULL) {
rfbLog("cannot open passwdfile: %s\n", passfile); rfbLog("cannot open passwdfile: %s\n", passfile);
rfbLogPerror("fopen"); rfbLogPerror("fopen");
...@@ -827,6 +853,7 @@ int read_passwds(char *passfile) { ...@@ -827,6 +853,7 @@ int read_passwds(char *passfile) {
} }
if (linecount >= max) { if (linecount >= max) {
rfbLog("read_passwds: hit max passwd: %d\n", max);
break; break;
} }
} }
...@@ -927,7 +954,7 @@ void install_passwds(void) { ...@@ -927,7 +954,7 @@ void install_passwds(void) {
} }
} }
void check_new_passwds(void) { void check_new_passwds(int force) {
static time_t last_check = 0; static time_t last_check = 0;
time_t now; time_t now;
...@@ -939,6 +966,10 @@ void check_new_passwds(void) { ...@@ -939,6 +966,10 @@ void check_new_passwds(void) {
} }
if (unixpw_in_progress) return; if (unixpw_in_progress) return;
if (force) {
last_check = 0;
}
now = time(NULL); now = time(NULL);
if (now > last_check + 1) { if (now > last_check + 1) {
if (read_passwds(passwdfile)) { if (read_passwds(passwdfile)) {
...@@ -948,6 +979,44 @@ void check_new_passwds(void) { ...@@ -948,6 +979,44 @@ void check_new_passwds(void) {
} }
} }
rfbBool custom_passwd_check(rfbClientPtr cl, const char *response, int len) {
char *input, *q, *cmd;
char num[16];
int j, i, n, rc;
rfbLog("custom_passwd_check: len=%d\n", len);
if (!passwdfile || strstr(passwdfile, "custom:") != passwdfile) {
return FALSE;
}
cmd = passwdfile + strlen("custom:");
sprintf(num, "%d\n", len);
input = (char *) malloc(2 * len + 16 + 1);
input[0] = '\0';
strcat(input, num);
n = strlen(num);
j = n;
for (i=0; i < len; i++) {
input[j] = cl->authChallenge[i];
j++;
}
for (i=0; i < len; i++) {
input[j] = response[i];
j++;
}
rc = run_user_command(cmd, cl, "custom_passwd", input, n+2*len, NULL);
free(input);
if (rc == 0) {
return TRUE;
} else {
return FALSE;
}
}
static void handle_one_http_request(void) { static void handle_one_http_request(void) {
rfbLog("handle_one_http_request: begin.\n"); rfbLog("handle_one_http_request: begin.\n");
......
...@@ -8,7 +8,8 @@ extern void lurk_loop(char *str); ...@@ -8,7 +8,8 @@ extern void lurk_loop(char *str);
extern int switch_user(char *, int); extern int switch_user(char *, int);
extern int read_passwds(char *passfile); extern int read_passwds(char *passfile);
extern void install_passwds(void); extern void install_passwds(void);
extern void check_new_passwds(void); extern void check_new_passwds(int force);
extern int wait_for_client(int *argc, char** argv, int http); extern int wait_for_client(int *argc, char** argv, int http);
extern rfbBool custom_passwd_check(rfbClientPtr cl, const char *response, int len);
#endif /* _X11VNC_USER_H */ #endif /* _X11VNC_USER_H */
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
.TH X11VNC "1" "September 2006" "x11vnc " "User Commands" .TH X11VNC "1" "September 2006" "x11vnc " "User Commands"
.SH NAME .SH NAME
x11vnc - allow VNC connections to real X11 displays x11vnc - allow VNC connections to real X11 displays
version: 0.8.3, lastmod: 2006-09-17 version: 0.8.3, lastmod: 2006-09-20
.SH SYNOPSIS .SH SYNOPSIS
.B x11vnc .B x11vnc
[OPTION]... [OPTION]...
...@@ -373,11 +373,14 @@ Also clients that log in viewonly cannot transfer files. ...@@ -373,11 +373,14 @@ Also clients that log in viewonly cannot transfer files.
However, if the remote control mechanism is used to However, if the remote control mechanism is used to
change the global or per-client viewonly state the change the global or per-client viewonly state the
filetransfer permissions will NOT change. filetransfer permissions will NOT change.
.PP
\fB-ultrafilexfer\fR
.IP .IP
Note, to *enable* UltraVNC filetransfer (currently Note, to *enable* UltraVNC filetransfer (currently
disabled by default, this may change...) and to get it disabled by default, this may change...) and to get it
to work you probably need to supply these libvncserver to work you probably need to supply these libvncserver
options: "\fB-rfbversion\fR \fI3.6 \fB-permitfiletransfer\fR"\fR options: "\fB-rfbversion\fR \fI3.6 \fB-permitfiletransfer\fR"\fR
"\fB-ultrafilexfer\fR" is an alias for this combination.
.PP .PP
\fB-http\fR \fB-http\fR
.IP .IP
...@@ -533,20 +536,59 @@ of the file \fIfilename\fR (instead of via \fB-passwd\fR on ...@@ -533,20 +536,59 @@ of the file \fIfilename\fR (instead of via \fB-passwd\fR on
the command line where others might see it via the command line where others might see it via
.IR ps (1) .IR ps (1)
). ).
See below for how to supply multiple passwords. .IP
See the descriptions below for how to supply multiple
passwords, view-only passwords, to specify external
programs for the authentication, and other features.
.IP .IP
If the filename is prefixed with "rm:" it will be If the filename is prefixed with "rm:" it will be
removed after being read. Perhaps this is useful in removed after being read. Perhaps this is useful in
limiting the readability of the file. In general, limiting the readability of the file. In general, the
the password file should not be readable by untrusted password file should not be readable by untrusted users
users (BTW: neither should the VNC \fB-rfbauth\fR file: (BTW: neither should the VNC \fB-rfbauth\fR file: it is NOT
it is NOT encrypted, only obscured). encrypted, only obscured with a fixed key).
.IP .IP
If the filename is prefixed with "read:" it will If the filename is prefixed with "read:" it will
periodically be checked for changes and reread. periodically be checked for changes and reread. It it
.IP guaranteed to be reread just when a new client connects
Note that only the first 8 characters of a password so that the latest passwords will be used.
are used. .IP
If \fIfilename\fR is prefixed with "cmd:" then the
string after the ":" is run as an external command:
the output of the command will be interpreted as if it
were read from a password file (see below). If the
command does not exit with 0, then x11vnc terminates
immediately. To specify more than 1000 passwords this
way set X11VNC_MAX_PASSWDS before starting x11vnc.
The environment variables are set as in \fB-accept.\fR
.IP
Note that due to the VNC protocol only the first 8
characters of a password are used (DES key).
.IP
If \fIfilename\fR is prefixed with "custom:" then a
custom password checker is supplied as an external
command following the ":". The command will be run
when a client authenticates. If the command exits with
0 the client is accepted, otherwise it is rejected.
The environment variables are set as in \fB-accept.\fR
.IP
The standard input to the custom command will be a
decimal digit "len" followed by a newline. "len"
specifies the challenge size and is usually 16 (the
VNC spec). Then follows len bytes which is the random
challenge string that was sent to the client. This is
then followed by len more bytes holding the client's
response (i.e. the challenge string encrypted via DES
with the user password in the standard situation).
.IP
The "custom:" scheme can be useful to implement
dynamic passwords or to implement methods where longer
passwords and/or different encryption algorithms
are used. The latter will require customizing the VNC
client as well. One could create an MD5SUM based scheme
for example.
.IP
File format for \fB-passwdfile:\fR
.IP .IP
If multiple non-blank lines exist in the file they are If multiple non-blank lines exist in the file they are
all taken as valid passwords. Blank lines are ignored. all taken as valid passwords. Blank lines are ignored.
...@@ -733,6 +775,21 @@ in /etc/shadow can then be authenticated. You may want ...@@ -733,6 +775,21 @@ in /etc/shadow can then be authenticated. You may want
to use \fB-users\fR unixpw= to switch the process user after to use \fB-users\fR unixpw= to switch the process user after
the user logs in. the user logs in.
.PP .PP
\fB-unixpw_cmd\fR \fIstr\fR
.IP
As \fB-unixpw\fR above, however do not use
.IR su (1)
but rather
run the externally supplied command \fIstr\fR. The first
line of its stdin will the username and the second line
the received password. If the command exits with status
0 (success) the VNC client will be accepted. It will be
rejected for any other return status. Dynamic passwords
and non-unix passwords can be implemented this way by
providing your own custom helper program. Note that
under unixpw mode the remote viewer is given 3 tries
to enter the correct password.
.PP
\fB-display\fR \fIWAIT:...\fR \fB-display\fR \fIWAIT:...\fR
.IP .IP
A special usage mode for the normal \fB-display\fR option. A special usage mode for the normal \fB-display\fR option.
...@@ -926,6 +983,13 @@ timeout (25s for about the first minute, 43200s later). ...@@ -926,6 +983,13 @@ timeout (25s for about the first minute, 43200s later).
Set to zero to poll forever. Set to a negative value Set to zero to poll forever. Set to a negative value
to use the builtin setting. to use the builtin setting.
.PP .PP
\fB-sslnofail\fR
.IP
Exit at the first SSL connection failure. Useful when
scripting SSL connections (e.g. x11vnc is started via
ssh) and you do not want x11vnc waiting around for more
connections, tying up ports, etc.
.PP
\fB-ssldir\fR \fI[dir]\fR \fB-ssldir\fR \fI[dir]\fR
.IP .IP
Use [dir] as an alternate ssl certificate and key Use [dir] as an alternate ssl certificate and key
...@@ -4073,7 +4137,7 @@ associated options is: ...@@ -4073,7 +4137,7 @@ associated options is:
.IP .IP
stunnel, ssl, unixpw, WAIT, id, accept, afteraccept, stunnel, ssl, unixpw, WAIT, id, accept, afteraccept,
gone, pipeinput, v4l-info, rawfb-setup, dt, gui, gone, pipeinput, v4l-info, rawfb-setup, dt, gui,
storepasswd, crash. storepasswd, passwdfile, custom_passwd, crash.
.IP .IP
See each option's help to learn the associated external See each option's help to learn the associated external
command. Note that the \fB-nocmds\fR option takes precedence command. Note that the \fB-nocmds\fR option takes precedence
......
...@@ -569,7 +569,7 @@ static void watch_loop(void) { ...@@ -569,7 +569,7 @@ static void watch_loop(void) {
check_xdamage_state(); check_xdamage_state();
check_xrecord_reset(0); check_xrecord_reset(0);
check_add_keysyms(); check_add_keysyms();
check_new_passwds(); check_new_passwds(0);
if (started_as_root) { if (started_as_root) {
check_switched_user(); check_switched_user();
} }
...@@ -1049,7 +1049,15 @@ static void quick_pw(char *str) { ...@@ -1049,7 +1049,15 @@ static void quick_pw(char *str) {
} }
*q = '\0'; *q = '\0';
if (db) fprintf(stderr, "'%s' '%s'\n", p, q+1); if (db) fprintf(stderr, "'%s' '%s'\n", p, q+1);
if (unixpw_nis) { if (unixpw_cmd) {
if (cmd_verify(p, q+1)) {
fprintf(stdout, "Y %s\n", p);
exit(0);
} else {
fprintf(stdout, "N %s\n", p);
exit(1);
}
} else if (unixpw_nis) {
if (crypt_verify(p, q+1)) { if (crypt_verify(p, q+1)) {
fprintf(stdout, "Y %s\n", p); fprintf(stdout, "Y %s\n", p);
exit(0); exit(0);
...@@ -1707,6 +1715,16 @@ int main(int argc, char* argv[]) { ...@@ -1707,6 +1715,16 @@ int main(int argc, char* argv[]) {
passwdfile = strdup(argv[++i]); passwdfile = strdup(argv[++i]);
got_passwdfile = 1; got_passwdfile = 1;
#ifndef NO_SSL_OR_UNIXPW #ifndef NO_SSL_OR_UNIXPW
} else if (!strcmp(arg, "-unixpw_cmd")
|| !strcmp(arg, "-unixpw_cmd_unsafe")) {
CHECK_ARGC
unixpw_cmd = strdup(argv[++i]);
unixpw = 1;
if (strstr(arg, "_unsafe")) {
/* hidden option for testing. */
set_env("UNIXPW_DISABLE_SSL", "1");
set_env("UNIXPW_DISABLE_LOCALHOST", "1");
}
} else if (strstr(arg, "-unixpw") == arg) { } else if (strstr(arg, "-unixpw") == arg) {
unixpw = 1; unixpw = 1;
if (strstr(arg, "-unixpw_nis")) { if (strstr(arg, "-unixpw_nis")) {
...@@ -1741,6 +1759,8 @@ int main(int argc, char* argv[]) { ...@@ -1741,6 +1759,8 @@ int main(int argc, char* argv[]) {
} else if (!strcmp(arg, "-ssltimeout")) { } else if (!strcmp(arg, "-ssltimeout")) {
CHECK_ARGC CHECK_ARGC
ssl_timeout_secs = atoi(argv[++i]); ssl_timeout_secs = atoi(argv[++i]);
} else if (!strcmp(arg, "-sslnofail")) {
ssl_no_fail = 1;
} else if (!strcmp(arg, "-ssldir")) { } else if (!strcmp(arg, "-ssldir")) {
CHECK_ARGC CHECK_ARGC
ssl_certs_dir = strdup(argv[++i]); ssl_certs_dir = strdup(argv[++i]);
...@@ -2367,7 +2387,14 @@ int main(int argc, char* argv[]) { ...@@ -2367,7 +2387,14 @@ int main(int argc, char* argv[]) {
listen_str = strdup(argv[i+1]); listen_str = strdup(argv[i+1]);
} }
/* otherwise copy it for libvncserver use below. */ /* otherwise copy it for libvncserver use below. */
if (argc_vnc < argc_vnc_max) { if (!strcmp(arg, "-ultrafilexfer") ||
!strcmp(arg, "-ultravncfilexfer")) {
if (argc_vnc + 2 < argc_vnc_max) {
argv_vnc[argc_vnc++] = strdup("-rfbversion");
argv_vnc[argc_vnc++] = strdup("3.6");
argv_vnc[argc_vnc++] = strdup("-permitfiletransfer");
}
} else if (argc_vnc < argc_vnc_max) {
argv_vnc[argc_vnc++] = strdup(arg); argv_vnc[argc_vnc++] = strdup(arg);
} else { } else {
rfbLog("too many arguments.\n"); rfbLog("too many arguments.\n");
...@@ -2573,13 +2600,25 @@ int main(int argc, char* argv[]) { ...@@ -2573,13 +2600,25 @@ int main(int argc, char* argv[]) {
} }
} else if (passwdfile) { } else if (passwdfile) {
/* read passwd(s) from file */ /* read passwd(s) from file */
if (read_passwds(passwdfile)) { if (strstr(passwdfile, "cmd:") == passwdfile ||
strstr(passwdfile, "custom:") == passwdfile) {
char tstr[100], *q;
sprintf(tstr, "%f", dnow());
if ((q = strrchr(tstr, '.')) == NULL) {
q = tstr;
} else {
q++;
}
/* never used under cmd:, used to force auth */
argv_vnc[argc_vnc++] = strdup("-passwd");
argv_vnc[argc_vnc++] = strdup(q);
} else if (read_passwds(passwdfile)) {
argv_vnc[argc_vnc++] = strdup("-passwd"); argv_vnc[argc_vnc++] = strdup("-passwd");
argv_vnc[argc_vnc++] = strdup(passwd_list[0]); argv_vnc[argc_vnc++] = strdup(passwd_list[0]);
}
got_passwd = 1; got_passwd = 1;
pw_loc = 100; /* just for pw_loc check below */ pw_loc = 100; /* just for pw_loc check below */
} }
}
if (vpw_loc > 0) { if (vpw_loc > 0) {
int i; int i;
for (i=vpw_loc; i <= vpw_loc+1; i++) { for (i=vpw_loc; i <= vpw_loc+1; i++) {
......
...@@ -454,6 +454,7 @@ extern int hack_val; ...@@ -454,6 +454,7 @@ extern int hack_val;
/* last client to move pointer */ /* last client to move pointer */
extern rfbClientPtr last_pointer_client; extern rfbClientPtr last_pointer_client;
extern rfbClientPtr latest_client;
extern int client_count; extern int client_count;
extern int clients_served; extern int clients_served;
......
...@@ -15,7 +15,7 @@ int xtrap_base_event_type = 0; ...@@ -15,7 +15,7 @@ int xtrap_base_event_type = 0;
int xdamage_base_event_type = 0; int xdamage_base_event_type = 0;
/* date +'lastmod: %Y-%m-%d' */ /* date +'lastmod: %Y-%m-%d' */
char lastmod[] = "0.8.3 lastmod: 2006-09-17"; char lastmod[] = "0.8.3 lastmod: 2006-09-20";
/* X display info */ /* X display info */
...@@ -127,6 +127,7 @@ int hack_val = 0; ...@@ -127,6 +127,7 @@ int hack_val = 0;
/* last client to move pointer */ /* last client to move pointer */
rfbClientPtr last_pointer_client = NULL; rfbClientPtr last_pointer_client = NULL;
rfbClientPtr latest_client = NULL;
int client_count = 0; int client_count = 0;
int clients_served = 0; int clients_served = 0;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment