You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
libtdevnc/x11vnc/connections.c

1883 lines
41 KiB

/* -- connections.c -- */
#include "x11vnc.h"
#include "inet.h"
#include "remote.h"
#include "keyboard.h"
#include "cleanup.h"
#include "gui.h"
#include "solid.h"
#include "rates.h"
#include "screen.h"
/*
* routines for handling incoming, outgoing, etc connections
*/
/* string for the VNC_CONNECT property */
char vnc_connect_str[VNC_CONNECT_MAX+1];
Atom vnc_connect_prop = None;
int all_clients_initialized(void);
char *list_clients(void);
int new_fb_size_clients(rfbScreenInfoPtr s);
void close_all_clients(void);
void close_clients(char *str);
void set_client_input(char *str);
void set_child_info(void);
void reverse_connect(char *str);
void set_vnc_connect_prop(char *str);
void read_vnc_connect_prop(void);
void check_connect_inputs(void);
void check_gui_inputs(void);
enum rfbNewClientAction new_client(rfbClientPtr client);
void start_client_info_sock(char *host_port_cookie);
void send_client_info(char *str);
void check_new_clients(void);
static rfbClientPtr *client_match(char *str);
static int run_user_command(char *cmd, rfbClientPtr client, char *mode);
static void client_gone(rfbClientPtr client);
static int check_access(char *addr);
static int ugly_accept_window(char *addr, char *userhost, int X, int Y,
int timeout, char *mode);
static int action_match(char *action, int rc);
static int accept_client(rfbClientPtr client);
static void check_connect_file(char *file);
static void send_client_connect(void);
/*
* check that all clients are in RFB_NORMAL state
*/
int all_clients_initialized(void) {
rfbClientIteratorPtr iter;
rfbClientPtr cl;
int ok = 1;
if (! screen) {
return ok;
}
iter = rfbGetClientIterator(screen);
while( (cl = rfbClientIteratorNext(iter)) ) {
if (cl->state != RFB_NORMAL) {
ok = 0;
break;
}
}
rfbReleaseClientIterator(iter);
return ok;
}
char *list_clients(void) {
rfbClientIteratorPtr iter;
rfbClientPtr cl;
char *list, tmp[32];
int count = 0;
if (!screen) {
return strdup("");
}
iter = rfbGetClientIterator(screen);
while( (cl = rfbClientIteratorNext(iter)) ) {
count++;
}
rfbReleaseClientIterator(iter);
/*
* each client:
* <id>:<ip>:<port>:<user>:<hostname>:<input>:<loginview>,
* 8+1+16+1+5+1+24+1+256+1+5+1+1+1
* 123.123.123.123:60000/0x11111111-rw,
* so count+1 * 400 must cover it.
*/
list = (char *) malloc((count+1)*400);
list[0] = '\0';
iter = rfbGetClientIterator(screen);
while( (cl = rfbClientIteratorNext(iter)) ) {
ClientData *cd = (ClientData *) cl->clientData;
if (*list != '\0') {
strcat(list, ",");
}
sprintf(tmp, "0x%x:", cd->uid);
strcat(list, tmp);
strcat(list, cl->host);
strcat(list, ":");
sprintf(tmp, "%d:", cd->client_port);
strcat(list, tmp);
if (*(cd->username) == '\0') {
char *s = ident_username(cl);
if (s) free(s);
}
strcat(list, cd->username);
strcat(list, ":");
strcat(list, cd->hostname);
strcat(list, ":");
strcat(list, cd->input);
strcat(list, ":");
sprintf(tmp, "%d", cd->login_viewonly);
strcat(list, tmp);
}
rfbReleaseClientIterator(iter);
return list;
}
/* count number of clients supporting NewFBSize */
int new_fb_size_clients(rfbScreenInfoPtr s) {
rfbClientIteratorPtr iter;
rfbClientPtr cl;
int count = 0;
if (! s) {
return 0;
}
iter = rfbGetClientIterator(s);
while( (cl = rfbClientIteratorNext(iter)) ) {
if (cl->useNewFBSize) {
count++;
}
}
rfbReleaseClientIterator(iter);
return count;
}
void close_all_clients(void) {
rfbClientIteratorPtr iter;
rfbClientPtr cl;
if (! screen) {
return;
}
iter = rfbGetClientIterator(screen);
while( (cl = rfbClientIteratorNext(iter)) ) {
rfbCloseClient(cl);
rfbClientConnectionGone(cl);
}
rfbReleaseClientIterator(iter);
}
static rfbClientPtr *client_match(char *str) {
rfbClientIteratorPtr iter;
rfbClientPtr cl, *cl_list;
int i, n, host_warn = 0, hex_warn = 0;
n = client_count + 10;
cl_list = (rfbClientPtr *) malloc(n * sizeof(rfbClientPtr));
i = 0;
iter = rfbGetClientIterator(screen);
while( (cl = rfbClientIteratorNext(iter)) ) {
if (strstr(str, "0x") == str) {
unsigned int in;
int id;
ClientData *cd = (ClientData *) cl->clientData;
if (sscanf(str, "0x%x", &in) != 1) {
if (hex_warn++) {
continue;
}
rfbLog("skipping invalid client hex id: %s\n",
str);
continue;
}
id = (unsigned int) in;
if (cd->uid == id) {
cl_list[i++] = cl;
}
} else {
char *rstr = str;
if (! dotted_ip(str)) {
rstr = host2ip(str);
if (rstr == NULL || *rstr == '\0') {
if (host_warn++) {
continue;
}
rfbLog("skipping bad lookup: \"%s\"\n",
str);
continue;
}
rfbLog("lookup: %s -> %s\n", str, rstr);
}
if (!strcmp(rstr, cl->host)) {
cl_list[i++] = cl;
}
if (rstr != str) {
free(rstr);
}
}
if (i >= n - 1) {
break;
}
}
rfbReleaseClientIterator(iter);
cl_list[i] = NULL;
return cl_list;
}
void close_clients(char *str) {
rfbClientPtr *cl_list, *cp;
if (!strcmp(str, "all") || !strcmp(str, "*")) {
close_all_clients();
return;
}
if (! screen) {
return;
}
cl_list = client_match(str);
cp = cl_list;
while (*cp) {
rfbCloseClient(*cp);
rfbClientConnectionGone(*cp);
cp++;
}
free(cl_list);
}
void set_client_input(char *str) {
rfbClientPtr *cl_list, *cp;
char *p, *val;
/* str is "match:value" */
if (! screen) {
return;
}
p = strchr(str, ':');
if (! p) {
return;
}
*p = '\0';
p++;
val = short_kmb(p);
cl_list = client_match(str);
cp = cl_list;
while (*cp) {
ClientData *cd = (ClientData *) (*cp)->clientData;
cd->input[0] = '\0';
strcat(cd->input, "_");
strcat(cd->input, val);
cp++;
}
free(val);
free(cl_list);
}
void set_child_info(void) {
char pid[16];
/* set up useful environment for child process */
sprintf(pid, "%d", (int) getpid());
set_env("X11VNC_PID", pid);
if (program_name) {
/* e.g. for remote control -R */
set_env("X11VNC_PROG", program_name);
}
if (program_cmdline) {
set_env("X11VNC_CMDLINE", program_cmdline);
}
if (raw_fb_str) {
set_env("X11VNC_RAWFB_STR", raw_fb_str);
} else {
set_env("X11VNC_RAWFB_STR", "");
}
}
/*
* utility to run a user supplied command setting some RFB_ env vars.
* used by, e.g., accept_client() and client_gone()
*/
static int run_user_command(char *cmd, rfbClientPtr client, char *mode) {
char *old_display = NULL;
char *addr = client->host;
char str[100];
int rc;
ClientData *cd = (ClientData *) client->clientData;
if (addr == NULL || addr[0] == '\0') {
addr = "unknown-host";
}
/* set RFB_CLIENT_ID to semi unique id for command to use */
if (cd && cd->uid) {
sprintf(str, "0x%x", cd->uid);
} else {
/* not accepted yet: */
sprintf(str, "0x%x", clients_served);
}
set_env("RFB_CLIENT_ID", str);
/* set RFB_CLIENT_IP to IP addr for command to use */
set_env("RFB_CLIENT_IP", addr);
/* set RFB_X11VNC_PID to our pid for command to use */
sprintf(str, "%d", (int) getpid());
set_env("RFB_X11VNC_PID", str);
if (client->state == RFB_PROTOCOL_VERSION) {
set_env("RFB_STATE", "PROTOCOL_VERSION");
} else if (client->state == RFB_SECURITY_TYPE) {
set_env("RFB_STATE", "SECURITY_TYPE");
} else if (client->state == RFB_AUTHENTICATION) {
set_env("RFB_STATE", "AUTHENTICATION");
} else if (client->state == RFB_INITIALISATION) {
set_env("RFB_STATE", "INITIALISATION");
} else if (client->state == RFB_NORMAL) {
set_env("RFB_STATE", "NORMAL");
} else {
set_env("RFB_STATE", "UNKNOWN");
}
/* set RFB_CLIENT_PORT to peer port for command to use */
if (cd && cd->client_port > 0) {
sprintf(str, "%d", cd->client_port);
} else {
sprintf(str, "%d", get_remote_port(client->sock));
}
set_env("RFB_CLIENT_PORT", str);
set_env("RFB_MODE", mode);
/*
* now do RFB_SERVER_IP and RFB_SERVER_PORT (i.e. us!)
* This will establish a 5-tuple (including tcp) the external
* program can potentially use to work out the virtual circuit
* for this connection.
*/
if (cd && cd->server_ip) {
set_env("RFB_SERVER_IP", cd->server_ip);
} else {
char *sip = get_local_host(client->sock);
set_env("RFB_SERVER_IP", sip);
if (sip) free(sip);
}
if (cd && cd->server_port > 0) {
sprintf(str, "%d", cd->server_port);
} else {
sprintf(str, "%d", get_local_port(client->sock));
}
set_env("RFB_SERVER_PORT", str);
if (cd) {
sprintf(str, "%d", cd->login_viewonly);
} else {
sprintf(str, "%d", -1);
}
set_env("RFB_LOGIN_VIEWONLY", str);
if (cd) {
sprintf(str, "%d", (int) cd->login_time);
} else {
sprintf(str, ">%d", (int) time(0));
}
set_env("RFB_LOGIN_TIME", str);
sprintf(str, "%d", (int) time(0));
set_env("RFB_CURRENT_TIME", str);
if (!cd || !cd->username || cd->username[0] == '\0') {
set_env("RFB_USERNAME", "unknown-user");
} else {
set_env("RFB_USERNAME", cd->username);
}
/*
* Better set DISPLAY to the one we are polling, if they
* want something trickier, they can handle on their own
* via environment, etc.
*/
if (getenv("DISPLAY")) {
old_display = strdup(getenv("DISPLAY"));
}
if (raw_fb && ! dpy) { /* raw_fb hack */
set_env("DISPLAY", "rawfb");
} else {
set_env("DISPLAY", DisplayString(dpy));
}
/*
* work out the number of clients (have to use client_count
* since there is deadlock in rfbGetClientIterator)
*/
sprintf(str, "%d", client_count);
set_env("RFB_CLIENT_COUNT", str);
if (no_external_cmds) {
rfbLogEnable(1);
rfbLog("cannot run external commands in -nocmds mode:\n");
rfbLog(" \"%s\"\n", cmd);
rfbLog(" exiting.\n");
clean_up_exit(1);
}
rfbLog("running command:\n");
rfbLog(" %s\n", cmd);
#if LIBVNCSERVER_HAVE_FORK
{
pid_t pid, pidw;
if ((pid = fork()) > 0) {
pidw = waitpid(pid, &rc, 0);
} else if (pid == -1) {
fprintf(stderr, "could not fork\n");
rfbLogPerror("fork");
rc = system(cmd);
} else {
/* this should close port 5900, etc.. */
int fd;
for (fd=3; fd<256; fd++) {
close(fd);
}
execlp("/bin/sh", "/bin/sh", "-c", cmd, (char *) NULL);
exit(1);
}
}
#else
/* this will still have port 5900 open */
rc = system(cmd);
#endif
if (rc >= 256) {
rc = rc/256;
}
rfbLog("command returned: %d\n", rc);
if (old_display) {
set_env("DISPLAY", old_display);
free(old_display);
}
return rc;
}
static int accepted_client = 0;
/*
* callback for when a client disconnects
*/
static void client_gone(rfbClientPtr client) {
client_count--;
if (client_count < 0) client_count = 0;
speeds_net_rate_measured = 0;
speeds_net_latency_measured = 0;
rfbLog("client_count: %d\n", client_count);
if (no_autorepeat && client_count == 0) {
autorepeat(1, 0);
}
if (use_solid_bg && client_count == 0) {
solid_bg(1);
}
if (gone_cmd && *gone_cmd != '\0') {
rfbLog("client_gone: using cmd for: %s\n", client->host);
run_user_command(gone_cmd, client, "gone");
}
if (client->clientData) {
ClientData *cd = (ClientData *) client->clientData;
if (cd) {
if (cd->server_ip) {
free(cd->server_ip);
cd->server_ip = NULL;
}
if (cd->hostname) {
free(cd->hostname);
cd->hostname = NULL;
}
if (cd->username) {
free(cd->username);
cd->username = NULL;
}
}
free(client->clientData);
client->clientData = NULL;
}
if (inetd) {
rfbLog("viewer exited.\n");
clean_up_exit(0);
}
if (connect_once) {
/*
* This non-exit is done for a bad passwd to be consistent
* with our RFB_CLIENT_REFUSE behavior in new_client() (i.e.
* we disconnect after 1 successful connection).
*/
if ((client->state == RFB_PROTOCOL_VERSION ||
client->state == RFB_AUTHENTICATION) && accepted_client) {
rfbLog("connect_once: invalid password or early "
"disconnect.\n");
rfbLog("connect_once: waiting for next connection.\n");
accepted_client = 0;
return;
}
if (shared && client_count > 0) {
rfbLog("connect_once: other shared clients still "
"connected, not exiting.\n");
return;
}
rfbLog("viewer exited.\n");
clean_up_exit(0);
}
}
/*
* Simple routine to limit access via string compare. A power user will
* want to compile libvncserver with libwrap support and use /etc/hosts.allow.
*/
static int check_access(char *addr) {
int allowed = 0;
char *p, *list;
if (deny_all) {
rfbLog("check_access: new connections are currently "
"blocked.\n");
return 0;
}
if (addr == NULL || *addr == '\0') {
rfbLog("check_access: denying empty host IP address string.\n");
return 0;
}
if (allow_list == NULL) {
/* set to "" to possibly append allow_once */
allow_list = strdup("");
}
if (*allow_list == '\0' && allow_once == NULL) {
/* no constraints, accept it */
return 1;
}
if (strchr(allow_list, '/')) {
/* a file of IP addresess or prefixes */
int len, len2 = 0;
struct stat sbuf;
FILE *in;
char line[1024], *q;
if (stat(allow_list, &sbuf) != 0) {
rfbLogEnable(1);
rfbLog("check_access: failure stating file: %s\n",
allow_list);
rfbLogPerror("stat");
clean_up_exit(1);
}
len = sbuf.st_size + 1; /* 1 more for '\0' at end */
if (allow_once) {
len2 = strlen(allow_once) + 2;
len += len2;
}
list = (char *) malloc(len);
list[0] = '\0';
in = fopen(allow_list, "r");
if (in == NULL) {
rfbLogEnable(1);
rfbLog("check_access: cannot open: %s\n", allow_list);
rfbLogPerror("fopen");
clean_up_exit(1);
}
while (fgets(line, 1024, in) != NULL) {
if ( (q = strchr(line, '#')) != NULL) {
*q = '\0';
}
if (strlen(list) + strlen(line) >=
(size_t) (len - len2)) {
/* file grew since our stat() */
break;
}
strcat(list, line);
}
fclose(in);
if (allow_once) {
strcat(list, "\n");
strcat(list, allow_once);
strcat(list, "\n");
}
} else {
int len = strlen(allow_list) + 1;
if (allow_once) {
len += strlen(allow_once) + 1;
}
list = (char *) malloc(len);
list[0] = '\0';
strcat(list, allow_list);
if (allow_once) {
strcat(list, ",");
strcat(list, allow_once);
}
}
if (allow_once) {
free(allow_once);
allow_once = NULL;
}
p = strtok(list, ", \t\n\r");
while (p) {
char *chk, *q, *r = NULL;
if (*p == '\0') {
p = strtok(NULL, ", \t\n\r");
continue;
}
if (! dotted_ip(p)) {
r = host2ip(p);
if (r == NULL || *r == '\0') {
rfbLog("check_access: bad lookup \"%s\"\n", p);
p = strtok(NULL, ", \t\n\r");
continue;
}
rfbLog("check_access: lookup %s -> %s\n", p, r);
chk = r;
} else {
chk = p;
}
q = strstr(addr, chk);
if (chk[strlen(chk)-1] != '.') {
if (!strcmp(addr, chk)) {
if (chk != p) {
rfbLog("check_access: client %s "
"matches host %s=%s\n", addr,
chk, p);
} else {
rfbLog("check_access: client %s "
"matches host %s\n", addr, chk);
}
allowed = 1;
} else if(!strcmp(chk, "localhost") &&
!strcmp(addr, "127.0.0.1")) {
allowed = 1;
}
} else if (q == addr) {
rfbLog("check_access: client %s matches pattern %s\n",
addr, chk);
allowed = 1;
}
p = strtok(NULL, ", \t\n\r");
if (r) {
free(r);
}
if (allowed) {
break;
}
}
free(list);
return allowed;
}
/*
* x11vnc's first (and only) visible widget: accept/reject dialog window.
* We go through this pain to avoid dependency on libXt...
*/
static int ugly_accept_window(char *addr, char *userhost, int X, int Y,
int timeout, char *mode) {
#define t2x2_width 16
#define t2x2_height 16
static unsigned char t2x2_bits[] = {
0xff, 0xff, 0xff, 0xff, 0x33, 0x33, 0x33, 0x33, 0xff, 0xff, 0xff, 0xff,
0x33, 0x33, 0x33, 0x33, 0xff, 0xff, 0xff, 0xff, 0x33, 0x33, 0x33, 0x33,
0xff, 0xff, 0xff, 0xff, 0x33, 0x33, 0x33, 0x33};
Window awin;
GC gc;
XSizeHints hints;
XGCValues values;
static XFontStruct *font_info = NULL;
static Pixmap ico = 0;
unsigned long valuemask = 0;
static char dash_list[] = {20, 40};
int list_length = sizeof(dash_list);
Atom wm_protocols;
Atom wm_delete_window;
XEvent ev;
long evmask = ExposureMask | KeyPressMask | ButtonPressMask
| StructureNotifyMask;
double waited = 0.0;
/* strings and geometries y/n */
KeyCode key_y, key_n, key_v;
char strh[100];
char stri[100];
char str1_b[] = "To accept: press \"y\" or click the \"Yes\" button";
char str2_b[] = "To reject: press \"n\" or click the \"No\" button";
char str3_b[] = "View only: press \"v\" or click the \"View\" button";
char str1_m[] = "To accept: click the \"Yes\" button";
char str2_m[] = "To reject: click the \"No\" button";
char str3_m[] = "View only: click the \"View\" button";
char str1_k[] = "To accept: press \"y\"";
char str2_k[] = "To reject: press \"n\"";
char str3_k[] = "View only: press \"v\"";
char *str1, *str2, *str3;
char str_y[] = "Yes";
char str_n[] = "No";
char str_v[] = "View";
int x, y, w = 345, h = 175, ret = 0;
int X_sh = 20, Y_sh = 30, dY = 20;
int Ye_x = 20, Ye_y = 0, Ye_w = 45, Ye_h = 20;
int No_x = 75, No_y = 0, No_w = 45, No_h = 20;
int Vi_x = 130, Vi_y = 0, Vi_w = 45, Vi_h = 20;
if (raw_fb && ! dpy) return 0; /* raw_fb hack */
if (!strcmp(mode, "mouse_only")) {
str1 = str1_m;
str2 = str2_m;
str3 = str3_m;
} else if (!strcmp(mode, "key_only")) {
str1 = str1_k;
str2 = str2_k;
str3 = str3_k;
h -= dY;
} else {
str1 = str1_b;
str2 = str2_b;
str3 = str3_b;
}
if (view_only) {
h -= dY;
}
/* XXX handle coff_x/coff_y? */
if (X < -dpy_x) {
x = (dpy_x - w)/2; /* large negative: center */
if (x < 0) x = 0;
} else if (X < 0) {
x = dpy_x + X - w; /* from lower right */
} else {
x = X; /* from upper left */
}
if (Y < -dpy_y) {
y = (dpy_y - h)/2;
if (y < 0) y = 0;
} else if (Y < 0) {
y = dpy_y + Y - h;
} else {
y = Y;
}
X_LOCK;
awin = XCreateSimpleWindow(dpy, window, x, y, w, h, 4,
BlackPixel(dpy, scr), WhitePixel(dpy, scr));
wm_protocols = XInternAtom(dpy, "WM_PROTOCOLS", False);
wm_delete_window = XInternAtom(dpy, "WM_DELETE_WINDOW", False);
XSetWMProtocols(dpy, awin, &wm_delete_window, 1);
if (! ico) {
ico = XCreateBitmapFromData(dpy, awin, (char *) t2x2_bits,
t2x2_width, t2x2_height);
}
hints.flags = PPosition | PSize | PMinSize;
hints.x = x;
hints.y = y;
hints.width = w;
hints.height = h;
hints.min_width = w;
hints.min_height = h;
XSetStandardProperties(dpy, awin, "new x11vnc client", "x11vnc query",
ico, NULL, 0, &hints);
XSelectInput(dpy, awin, evmask);
if (! font_info && (font_info = XLoadQueryFont(dpy, "fixed")) == NULL) {
rfbLogEnable(1);
rfbLog("ugly_accept_window: cannot locate font fixed.\n");
X_UNLOCK;
clean_up_exit(1);
}
gc = XCreateGC(dpy, awin, valuemask, &values);
XSetFont(dpy, gc, font_info->fid);
XSetForeground(dpy, gc, BlackPixel(dpy, scr));
XSetLineAttributes(dpy, gc, 1, LineSolid, CapButt, JoinMiter);
XSetDashes(dpy, gc, 0, dash_list, list_length);
XMapWindow(dpy, awin);
XFlush(dpy);
snprintf(strh, 100, "x11vnc: accept connection from %s?", addr);
snprintf(stri, 100, " (%s)", userhost);
key_y = XKeysymToKeycode(dpy, XStringToKeysym("y"));
key_n = XKeysymToKeycode(dpy, XStringToKeysym("n"));
key_v = XKeysymToKeycode(dpy, XStringToKeysym("v"));
while (1) {
int out = -1, x, y, tw, k;
if (XCheckWindowEvent(dpy, awin, evmask, &ev)) {
; /* proceed to handling */
} else if (XCheckTypedEvent(dpy, ClientMessage, &ev)) {
; /* proceed to handling */
} else {
int ms = 100; /* sleep a bit */
usleep(ms * 1000);
waited += ((double) ms)/1000.;
if (timeout && (int) waited >= timeout) {
rfbLog("accept_client: popup timed out after "
"%d seconds.\n", timeout);
out = 0;
ev.type = 0;
} else {
continue;
}
}
switch(ev.type) {
case Expose:
while (XCheckTypedEvent(dpy, Expose, &ev)) {
;
}
k=0;
/* instructions */
XDrawString(dpy, awin, gc, X_sh, Y_sh+(k++)*dY,
strh, strlen(strh));
XDrawString(dpy, awin, gc, X_sh, Y_sh+(k++)*dY,
stri, strlen(stri));
XDrawString(dpy, awin, gc, X_sh, Y_sh+(k++)*dY,
str1, strlen(str1));
XDrawString(dpy, awin, gc, X_sh, Y_sh+(k++)*dY,
str2, strlen(str2));
if (! view_only) {
XDrawString(dpy, awin, gc, X_sh, Y_sh+(k++)*dY,
str3, strlen(str3));
}
if (!strcmp(mode, "key_only")) {
break;
}
/* buttons */
Ye_y = Y_sh+k*dY;
No_y = Y_sh+k*dY;
Vi_y = Y_sh+k*dY;
XDrawRectangle(dpy, awin, gc, Ye_x, Ye_y, Ye_w, Ye_h);
XDrawRectangle(dpy, awin, gc, No_x, No_y, No_w, No_h);
if (! view_only) {
XDrawRectangle(dpy, awin, gc, Vi_x, Vi_y,
Vi_w, Vi_h);
}
tw = XTextWidth(font_info, str_y, strlen(str_y));
tw = (Ye_w - tw)/2;
if (tw < 0) tw = 1;
XDrawString(dpy, awin, gc, Ye_x+tw, Ye_y+Ye_h-5,
str_y, strlen(str_y));
tw = XTextWidth(font_info, str_n, strlen(str_n));
tw = (No_w - tw)/2;
if (tw < 0) tw = 1;
XDrawString(dpy, awin, gc, No_x+tw, No_y+No_h-5,
str_n, strlen(str_n));
if (! view_only) {
tw = XTextWidth(font_info, str_v,
strlen(str_v));
tw = (Vi_w - tw)/2;
if (tw < 0) tw = 1;
XDrawString(dpy, awin, gc, Vi_x+tw,
Vi_y+Vi_h-5, str_v, strlen(str_v));
}
break;
case ClientMessage:
if (ev.xclient.message_type == wm_protocols &&
(Atom) ev.xclient.data.l[0] == wm_delete_window) {
out = 0;
}
break;
case ButtonPress:
x = ev.xbutton.x;
y = ev.xbutton.y;
if (!strcmp(mode, "key_only")) {
;
} else if (x > No_x && x < No_x+No_w && y > No_y
&& y < No_y+No_h) {
out = 0;
} else if (x > Ye_x && x < Ye_x+Ye_w && y > Ye_y
&& y < Ye_y+Ye_h) {
out = 1;
} else if (! view_only && x > Vi_x && x < Vi_x+Vi_w
&& y > Vi_y && y < Vi_y+Ye_h) {
out = 2;
}
break;
case KeyPress:
if (!strcmp(mode, "mouse_only")) {
;
} else if (ev.xkey.keycode == key_y) {
out = 1;
} else if (ev.xkey.keycode == key_n) {
out = 0;
} else if (! view_only && ev.xkey.keycode == key_v) {
out = 2;
}
break;
default:
break;
}
if (out != -1) {
ret = out;
XSelectInput(dpy, awin, 0);
XUnmapWindow(dpy, awin);
XFreeGC(dpy, gc);
XDestroyWindow(dpy, awin);
XFlush(dpy);
break;
}
}
X_UNLOCK;
return ret;
}
/*
* process a "yes:0,no:*,view:3" type action list comparing to command
* return code rc. * means the default action with no other match.
*/
static int action_match(char *action, int rc) {
char *p, *q, *s = strdup(action);
int cases[4], i, result;
char *labels[4];
labels[1] = "yes";
labels[2] = "no";
labels[3] = "view";
rfbLog("accept_client: process action line: %s\n",
action);
for (i=1; i <= 3; i++) {
cases[i] = -2;
}
p = strtok(s, ",");
while (p) {
if ((q = strchr(p, ':')) != NULL) {
int in, k = 1;
*q = '\0';
q++;
if (strstr(p, "yes") == p) {
k = 1;
} else if (strstr(p, "no") == p) {
k = 2;
} else if (strstr(p, "view") == p) {
k = 3;
} else {
rfbLogEnable(1);
rfbLog("invalid action line: %s\n", action);
clean_up_exit(1);
}
if (*q == '*') {
cases[k] = -1;
} else if (sscanf(q, "%d", &in) == 1) {
if (in < 0) {
rfbLogEnable(1);
rfbLog("invalid action line: %s\n",
action);
clean_up_exit(1);
}
cases[k] = in;
} else {
rfbLogEnable(1);
rfbLog("invalid action line: %s\n", action);
clean_up_exit(1);
}
} else {
rfbLogEnable(1);
rfbLog("invalid action line: %s\n", action);
clean_up_exit(1);
}
p = strtok(NULL, ",");
}
free(s);
result = -1;
for (i=1; i <= 3; i++) {
if (cases[i] == -1) {
rfbLog("accept_client: default action is case=%d %s\n",
i, labels[i]);
result = i;
break;
}
}
if (result == -1) {
rfbLog("accept_client: no default action\n");
}
for (i=1; i <= 3; i++) {
if (cases[i] >= 0 && cases[i] == rc) {
rfbLog("accept_client: matched action is case=%d %s\n",
i, labels[i]);
result = i;
break;
}
}
if (result < 0) {
rfbLog("no action match: %s rc=%d set to no\n", action, rc);
result = 2;
}
return result;
}
/*
* Simple routine to prompt the user on the X display whether an incoming
* client should be allowed to connect or not. If a gui is involved it
* will be running in the environment/context of the X11 DISPLAY.
*
* The command supplied via -accept is run as is (i.e. no string
* substitution) with the RFB_CLIENT_IP environment variable set to the
* incoming client's numerical IP address.
*
* If the external command exits with 0 the client is accepted, otherwise
* the client is rejected.
*
* Some builtins are provided:
*
* xmessage: use homebrew xmessage(1) for the external command.
* popup: use internal X widgets for prompting.
*
*/
static int accept_client(rfbClientPtr client) {
char xmessage[200], *cmd = NULL;
char *addr = client->host;
char *action = NULL;
if (accept_cmd == NULL || *accept_cmd == '\0') {
return 1; /* no command specified, so we accept */
}
if (addr == NULL || addr[0] == '\0') {
addr = "unknown-host";
}
if (strstr(accept_cmd, "popup") == accept_cmd) {
/* use our builtin popup button */
/* (popup|popupkey|popupmouse)[+-X+-Y][:timeout] */
int ret, timeout = 120;
int x = -64000, y = -64000;
char *p, *mode;
char *userhost = ident_username(client);
/* extract timeout */
if ((p = strchr(accept_cmd, ':')) != NULL) {
int in;
if (sscanf(p+1, "%d", &in) == 1) {
timeout = in;
}
}
/* extract geometry */
if ((p = strpbrk(accept_cmd, "+-")) != NULL) {
int x1, y1;
if (sscanf(p, "+%d+%d", &x1, &y1) == 2) {
x = x1;
y = y1;
} else if (sscanf(p, "+%d-%d", &x1, &y1) == 2) {
x = x1;
y = -y1;
} else if (sscanf(p, "-%d+%d", &x1, &y1) == 2) {
x = -x1;
y = y1;
} else if (sscanf(p, "-%d-%d", &x1, &y1) == 2) {
x = -x1;
y = -y1;
}
}
/* find mode: mouse, key, or both */
if (strstr(accept_cmd, "popupmouse") == accept_cmd) {
mode = "mouse_only";
} else if (strstr(accept_cmd, "popupkey") == accept_cmd) {
mode = "key_only";
} else {
mode = "both";
}
rfbLog("accept_client: using builtin popup for: %s\n", addr);
if ((ret = ugly_accept_window(addr, userhost, x, y, timeout,
mode))) {
free(userhost);
if (ret == 2) {
rfbLog("accept_client: viewonly: %s\n", addr);
client->viewOnly = TRUE;
}
rfbLog("accept_client: popup accepted: %s\n", addr);
return 1;
} else {
free(userhost);
rfbLog("accept_client: popup rejected: %s\n", addr);
return 0;
}
} else if (!strcmp(accept_cmd, "xmessage")) {
/* make our own command using xmessage(1) */
if (view_only) {
sprintf(xmessage, "xmessage -buttons yes:0,no:2 -center"
" 'x11vnc: accept connection from %s?'", addr);
} else {
sprintf(xmessage, "xmessage -buttons yes:0,no:2,"
"view-only:3 -center" " 'x11vnc: accept connection"
" from %s?'", addr);
action = "yes:0,no:*,view:3";
}
cmd = xmessage;
} else {
/* use the user supplied command: */
cmd = accept_cmd;
/* extract any action prefix: yes:N,no:M,view:K */
if (strstr(accept_cmd, "yes:") == accept_cmd) {
char *p;
if ((p = strpbrk(accept_cmd, " \t")) != NULL) {
int i;
cmd = p;
p = accept_cmd;
for (i=0; i<200; i++) {
if (*p == ' ' || *p == '\t') {
xmessage[i] = '\0';
break;
}
xmessage[i] = *p;
p++;
}
xmessage[200-1] = '\0';
action = xmessage;
}
}
}
if (cmd) {
int rc;
rfbLog("accept_client: using cmd for: %s\n", addr);
rc = run_user_command(cmd, client, "accept");
if (action) {
int result;
if (rc < 0) {
rfbLog("accept_client: cannot use negative "
"rc: %d, action %s\n", rc, action);
result = 2;
} else {
result = action_match(action, rc);
}
if (result == 1) {
rc = 0;
} else if (result == 2) {
rc = 1;
} else if (result == 3) {
rc = 0;
rfbLog("accept_client: viewonly: %s\n", addr);
client->viewOnly = TRUE;
} else {
rc = 1; /* NOTREACHED */
}
}
if (rc == 0) {
rfbLog("accept_client: accepted: %s\n", addr);
return 1;
} else {
rfbLog("accept_client: rejected: %s\n", addr);
return 0;
}
} else {
rfbLog("accept_client: no command, rejecting %s\n", addr);
return 0;
}
/* return 0; NOTREACHED */
}
/*
* For the -connect <file> option: periodically read the file looking for
* a connect string. If one is found set client_connect to it.
*/
static void check_connect_file(char *file) {
FILE *in;
char line[VNC_CONNECT_MAX], host[VNC_CONNECT_MAX];
static int first_warn = 1, truncate_ok = 1;
static time_t last_time = 0;
time_t now = time(0);
if (last_time == 0) {
last_time = now;
}
if (now - last_time < 1) {
/* check only once a second */
return;
}
last_time = now;
if (! truncate_ok) {
/* check if permissions changed */
if (access(file, W_OK) == 0) {
truncate_ok = 1;
} else {
return;
}
}
in = fopen(file, "r");
if (in == NULL) {
if (first_warn) {
rfbLog("check_connect_file: fopen failure: %s\n", file);
rfbLogPerror("fopen");
first_warn = 0;
}
return;
}
if (fgets(line, VNC_CONNECT_MAX, in) != NULL) {
if (sscanf(line, "%s", host) == 1) {
if (strlen(host) > 0) {
char *str = strdup(host);
if (strlen(str) > 38) {
char trim[100];
trim[0] = '\0';
strncat(trim, str, 38);
rfbLog("read connect file: %s ...\n",
trim);
} else {
rfbLog("read connect file: %s\n", str);
}
client_connect = str;
}
}
}
fclose(in);
/* truncate file */
in = fopen(file, "w");
if (in != NULL) {
fclose(in);
} else {
/* disable if we cannot truncate */
rfbLog("check_connect_file: could not truncate %s, "
"disabling checking.\n", file);
truncate_ok = 0;
}
}
/*
* Do a reverse connect for a single "host" or "host:port"
*/
static int do_reverse_connect(char *str) {
rfbClientPtr cl;
char *host, *p;
int rport = 5500, len = strlen(str);
if (len < 1) {
return 0;
}
if (len > 1024) {
rfbLog("reverse_connect: string too long: %d bytes\n", len);
return 0;
}
if (!screen) {
rfbLog("reverse_connect: screen not setup yet.\n");
return 0;
}
/* copy in to host */
host = (char *) malloc(len+1);
if (! host) {
rfbLog("reverse_connect: could not malloc string %d\n", len);
return 0;
}
strncpy(host, str, len);
host[len] = '\0';
/* extract port, if any */
if ((p = strchr(host, ':')) != NULL) {
rport = atoi(p+1);
*p = '\0';
}
cl = rfbReverseConnection(screen, host, rport);
free(host);
if (cl == NULL) {
rfbLog("reverse_connect: %s failed\n", str);
return 0;
} else {
rfbLog("reverse_connect: %s/%s OK\n", str, cl->host);
return 1;
}
}
/*
* Break up comma separated list of hosts and call do_reverse_connect()
*/
void reverse_connect(char *str) {
char *p, *tmp = strdup(str);
int sleep_between_host = 300;
int sleep_min = 1500, sleep_max = 4500, n_max = 5;
int n, tot, t, dt = 100, cnt = 0;
p = strtok(tmp, ", \t\r\n");
while (p) {
if ((n = do_reverse_connect(p)) != 0) {
rfbPE(-1);
}
cnt += n;
p = strtok(NULL, ", \t\r\n");
if (p) {
t = 0;
while (t < sleep_between_host) {
usleep(dt * 1000);
rfbPE(-1);
t += dt;
}
}
}
free(tmp);
if (cnt == 0) {
return;
}
/*
* XXX: we need to process some of the initial handshaking
* events, otherwise the client can get messed up (why??)
* so we send rfbProcessEvents() all over the place.
*/
n = cnt;
if (n >= n_max) {
n = n_max;
}
t = sleep_max - sleep_min;
tot = sleep_min + ((n-1) * t) / (n_max-1);
t = 0;
while (t < tot) {
rfbPE(-1);
usleep(dt * 1000);
t += dt;
}
}
/*
* Routines for monitoring the VNC_CONNECT property for changes.
* The vncconnect(1) will set it on our X display.
*/
void set_vnc_connect_prop(char *str) {
XChangeProperty(dpy, rootwin, vnc_connect_prop, XA_STRING, 8,
PropModeReplace, (unsigned char *)str, strlen(str));
}
void read_vnc_connect_prop(void) {
Atom type;
int format, slen, dlen;
unsigned long nitems = 0, bytes_after = 0;
unsigned char* data = NULL;
int db = 1;
vnc_connect_str[0] = '\0';
slen = 0;
if (! vnc_connect || vnc_connect_prop == None) {
/* not active or problem with VNC_CONNECT atom */
return;
}
/* read the property value into vnc_connect_str: */
do {
if (XGetWindowProperty(dpy, DefaultRootWindow(dpy),
vnc_connect_prop, nitems/4, VNC_CONNECT_MAX/16, False,
AnyPropertyType, &type, &format, &nitems, &bytes_after,
&data) == Success) {
dlen = nitems * (format/8);
if (slen + dlen > VNC_CONNECT_MAX) {
/* too big */
rfbLog("warning: truncating large VNC_CONNECT"
" string > %d bytes.\n", VNC_CONNECT_MAX);
XFree(data);
break;
}
memcpy(vnc_connect_str+slen, data, dlen);
slen += dlen;
vnc_connect_str[slen] = '\0';
XFree(data);
}
} while (bytes_after > 0);
vnc_connect_str[VNC_CONNECT_MAX] = '\0';
if (! db) {
;
} else if (strstr(vnc_connect_str, "ans=stop:N/A,ans=quit:N/A,ans=")) {
;
} else if (strstr(vnc_connect_str, "qry=stop,quit,exit")) {
;
} else if (strstr(vnc_connect_str, "ack=") == vnc_connect_str) {
;
} else if (quiet && strstr(vnc_connect_str, "qry=ping") ==
vnc_connect_str) {
;
} else if (strstr(vnc_connect_str, "cmd=") &&
strstr(vnc_connect_str, "passwd")) {
rfbLog("read VNC_CONNECT: *\n");
} else if (strlen(vnc_connect_str) > 38) {
char trim[100];
trim[0] = '\0';
strncat(trim, vnc_connect_str, 38);
rfbLog("read VNC_CONNECT: %s ...\n", trim);
} else {
rfbLog("read VNC_CONNECT: %s\n", vnc_connect_str);
}
}
/*
* check if client_connect has been set, if so make the reverse connections.
*/
static void send_client_connect(void) {
if (client_connect != NULL) {
char *str = client_connect;
if (strstr(str, "cmd=") == str || strstr(str, "qry=") == str) {
process_remote_cmd(client_connect, 0);
} else if (strstr(str, "ans=") == str
|| strstr(str, "aro=") == str) {
;
} else if (strstr(str, "ack=") == str) {
;
} else {
reverse_connect(client_connect);
}
free(client_connect);
client_connect = NULL;
}
}
/*
* monitor the various input methods
*/
void check_connect_inputs(void) {
/* flush any already set: */
send_client_connect();
/* connect file: */
if (client_connect_file != NULL) {
check_connect_file(client_connect_file);
}
send_client_connect();
/* VNC_CONNECT property (vncconnect program) */
if (vnc_connect && *vnc_connect_str != '\0') {
client_connect = strdup(vnc_connect_str);
vnc_connect_str[0] = '\0';
}
send_client_connect();
}
void check_gui_inputs(void) {
int i, gnmax = 0, n = 0, nfds;
int socks[ICON_MODE_SOCKS];
fd_set fds;
struct timeval tv;
char buf[VNC_CONNECT_MAX+1];
ssize_t nbytes;
for (i=0; i<ICON_MODE_SOCKS; i++) {
if (icon_mode_socks[i] >= 0) {
socks[n++] = i;
if (icon_mode_socks[i] > gnmax) {
gnmax = icon_mode_socks[i];
}
}
}
if (! n) {
return;
}
FD_ZERO(&fds);
for (i=0; i<n; i++) {
FD_SET(icon_mode_socks[socks[i]], &fds);
}
tv.tv_sec = 0;
tv.tv_usec = 0;
nfds = select(gnmax+1, &fds, NULL, NULL, &tv);
if (nfds <= 0) {
return;
}
for (i=0; i<n; i++) {
int k, fd = icon_mode_socks[socks[i]];
char *p;
if (! FD_ISSET(fd, &fds)) {
continue;
}
for (k=0; k<=VNC_CONNECT_MAX; k++) {
buf[k] = '\0';
}
nbytes = read(fd, buf, VNC_CONNECT_MAX);
if (nbytes <= 0) {
close(fd);
icon_mode_socks[socks[i]] = -1;
continue;
}
p = strtok(buf, "\r\n");
while (p) {
if (strstr(p, "cmd=") == p ||
strstr(p, "qry=") == p) {
char *str = process_remote_cmd(p, 1);
if (! str) {
str = strdup("");
}
nbytes = write(fd, str, strlen(str));
write(fd, "\n", 1);
free(str);
if (nbytes < 0) {
close(fd);
icon_mode_socks[socks[i]] = -1;
break;
}
}
p = strtok(NULL, "\r\n");
}
}
}
/*
* libvncserver callback for when a new client connects
*/
enum rfbNewClientAction new_client(rfbClientPtr client) {
ClientData *cd;
last_event = last_input = time(0);
if (inetd) {
/*
* Set this so we exit as soon as connection closes,
* otherwise client_gone is only called after RFB_CLIENT_ACCEPT
*/
client->clientGoneHook = client_gone;
}
clients_served++;
if (connect_once) {
if (screen->dontDisconnect && screen->neverShared) {
if (! shared && accepted_client) {
rfbLog("denying additional client: %s\n",
client->host);
return(RFB_CLIENT_REFUSE);
}
}
}
if (! check_access(client->host)) {
rfbLog("denying client: %s does not match %s\n", client->host,
allow_list ? allow_list : "(null)" );
return(RFB_CLIENT_REFUSE);
}
if (! accept_client(client)) {
rfbLog("denying client: %s local user rejected connection.\n",
client->host);
rfbLog("denying client: accept_cmd=\"%s\"\n",
accept_cmd ? accept_cmd : "(null)" );
return(RFB_CLIENT_REFUSE);
}
client->clientData = (void *) calloc(sizeof(ClientData), 1);
cd = (ClientData *) client->clientData;
cd->uid = clients_served;
cd->client_port = get_remote_port(client->sock);
cd->server_port = get_local_port(client->sock);
cd->server_ip = get_local_host(client->sock);
cd->hostname = ip2host(client->host);
cd->username = strdup("");
cd->input[0] = '-';
cd->login_viewonly = -1;
cd->login_time = time(0);
client->clientGoneHook = client_gone;
if (client_count) {
speeds_net_rate_measured = 0;
speeds_net_latency_measured = 0;
}
client_count++;
last_keyboard_input = last_pointer_input = time(0);
if (no_autorepeat && client_count == 1 && ! view_only) {
/*
* first client, turn off X server autorepeat
* XXX handle dynamic change of view_only and per-client.
*/
autorepeat(0, 0);
}
if (use_solid_bg && client_count == 1) {
solid_bg(0);
}
if (pad_geometry) {
install_padded_fb(pad_geometry);
}
cd->timer = dnow();
cd->send_cmp_rate = 0.0;
cd->send_raw_rate = 0.0;
cd->latency = 0.0;
cd->cmp_bytes_sent = 0;
cd->raw_bytes_sent = 0;
accepted_client = 1;
last_client = time(0);
return(RFB_CLIENT_ACCEPT);
}
void start_client_info_sock(char *host_port_cookie) {
char *host = NULL, *cookie = NULL, *p;
char *str = strdup(host_port_cookie);
int i, port, sock, next = -1;
static time_t start_time[ICON_MODE_SOCKS];
time_t oldest = 0;
int db = 0;
port = -1;
for (i = 0; i < ICON_MODE_SOCKS; i++) {
if (icon_mode_socks[i] < 0) {
next = i;
break;
}
if (oldest == 0 || start_time[i] < oldest) {
next = i;
oldest = start_time[i];
}
}
p = strtok(str, ":");
i = 0;
while (p) {
if (i == 0) {
host = strdup(p);
} else if (i == 1) {
port = atoi(p);
} else if (i == 2) {
cookie = strdup(p);
}
i++;
p = strtok(NULL, ":");
}
free(str);
if (db) fprintf(stderr, "%s/%d/%s next=%d\n", host, port, cookie, next);
if (host && port && cookie) {
if (*host == '\0') {
free(host);
host = strdup("localhost");
}
sock = rfbConnectToTcpAddr(host, port);
if (sock < 0) {
usleep(200 * 1000);
sock = rfbConnectToTcpAddr(host, port);
}
if (sock >= 0) {
char *lst = list_clients();
icon_mode_socks[next] = sock;
start_time[next] = time(0);
write(sock, "COOKIE:", strlen("COOKIE:"));
write(sock, cookie, strlen(cookie));
write(sock, "\n", strlen("\n"));
write(sock, "none\n", strlen("none\n"));
write(sock, "none\n", strlen("none\n"));
write(sock, lst, strlen(lst));
write(sock, "\n", strlen("\n"));
if (db) {
fprintf(stderr, "list: %s\n", lst);
}
free(lst);
rfbLog("client_info_sock to: %s:%d\n", host, port);
} else {
rfbLog("failed client_info_sock: %s:%d\n", host, port);
}
} else {
rfbLog("malformed client_info_sock: %s\n", host_port_cookie);
}
if (host) free(host);
if (cookie) free(cookie);
}
void send_client_info(char *str) {
int i;
static char *pstr = NULL;
static int len = 128;
if (!str || strlen(str) == 0) {
return;
}
if (!pstr) {
pstr = (char *)malloc(len);
}
if (strlen(str) + 2 > (size_t) len) {
free(pstr);
len *= 2;
pstr = (char *)malloc(len);
}
strcpy(pstr, str);
strcat(pstr, "\n");
if (icon_mode_fh) {
fprintf(icon_mode_fh, "%s", pstr);
fflush(icon_mode_fh);
}
for (i=0; i<ICON_MODE_SOCKS; i++) {
int len, n, sock = icon_mode_socks[i];
char *buf = pstr;
if (sock < 0) {
continue;
}
len = strlen(pstr);
while (len > 0) {
n = write(sock, buf, len);
if (n > 0) {
buf += n;
len -= n;
continue;
}
if (n < 0 && errno == EINTR) {
continue;
}
close(sock);
icon_mode_socks[i] = -1;
break;
}
}
}
void check_new_clients(void) {
static int last_count = 0;
rfbClientIteratorPtr iter;
rfbClientPtr cl;
int i, send_info = 0;
int run_after_accept = 0;
if (client_count == last_count) {
return;
}
if (! all_clients_initialized()) {
return;
}
if (client_count > last_count) {
if (afteraccept_cmd != NULL && afteraccept_cmd[0] != '\0') {
run_after_accept = 1;
}
}
last_count = client_count;
if (! screen) {
return;
}
if (! client_count) {
send_client_info("none");
return;
}
iter = rfbGetClientIterator(screen);
while( (cl = rfbClientIteratorNext(iter)) ) {
ClientData *cd = (ClientData *) cl->clientData;
if (cd->login_viewonly < 0) {
/* this is a general trigger to initialize things */
if (cl->viewOnly) {
cd->login_viewonly = 1;
if (allowed_input_view_only) {
cl->viewOnly = FALSE;
cd->input[0] = '\0';
strncpy(cd->input,
allowed_input_view_only, CILEN);
}
} else {
cd->login_viewonly = 0;
if (allowed_input_normal) {
cd->input[0] = '\0';
strncpy(cd->input,
allowed_input_normal, CILEN);
}
}
if (run_after_accept) {
run_user_command(afteraccept_cmd, cl,
"afteraccept");
}
}
}
rfbReleaseClientIterator(iter);
if (icon_mode_fh) {
send_info++;
}
for (i = 0; i < ICON_MODE_SOCKS; i++) {
if (send_info || icon_mode_socks[i] >= 0) {
send_info++;
break;
}
}
if (send_info) {
char *str = list_clients();
send_client_info(str);
free(str);
}
}