From 05a8d11903b9b675dba5056a2ddf8f980ac985ef Mon Sep 17 00:00:00 2001 From: runge Date: Sat, 8 May 2004 14:43:03 +0000 Subject: [PATCH] x11vnc: add -accept some-command/xmessage/popup --- ChangeLog | 7 +- contrib/ChangeLog | 4 + contrib/x11vnc.c | 271 ++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 273 insertions(+), 9 deletions(-) diff --git a/ChangeLog b/ChangeLog index 36d3a5e..e56d532 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,6 +1,9 @@ +2004-05-08 Karl Runge + * x11vnc: add -accept some-command/xmessage/popup + 2004-05-05 Karl Runge - * mouse button -> keystrokes and keystroke -> mouse button mappings - in -buttonmap and -remap + * x11vnc: mouse button -> keystrokes and keystroke -> mouse button + mappings in -buttonmap and -remap * shm OS blacklist revert to -onetile 2004-04-28 Karl Runge diff --git a/contrib/ChangeLog b/contrib/ChangeLog index 758308e..0623417 100644 --- a/contrib/ChangeLog +++ b/contrib/ChangeLog @@ -1,3 +1,7 @@ +2004-05-08 Karl Runge + * add -accept some-command/xmessage/popup to prompt local X11 user + or otherwise decide to accept an incoming client. + 2004-05-05 Karl Runge * enable mouse button -> keystrokes mapping in -buttonmap (mousewheel) * enable keystroke -> mouse button mapping in -remap (touchpad paste) diff --git a/contrib/x11vnc.c b/contrib/x11vnc.c index 450ce1c..48941ff 100644 --- a/contrib/x11vnc.c +++ b/contrib/x11vnc.c @@ -130,7 +130,7 @@ #endif /* date +'"lastmod: %Y-%m-%d";' */ -char lastmod[] = "lastmod: 2004-05-05"; +char lastmod[] = "lastmod: 2004-05-08"; /* X and rfb framebuffer */ @@ -205,6 +205,7 @@ hint_t *hint_list; int shared = 0; /* share vnc display. */ char *allow_list = NULL; /* for -allow and -localhost */ +char *accept_cmd = NULL; /* for -accept */ int view_only = 0; /* clients can only watch. */ int inetd = 0; /* spawned from inetd(1) */ int connect_once = 1; /* disconnect after first connection session. */ @@ -477,6 +478,240 @@ int check_access(char *addr) { return allowed; } +/* + * x11vnc's first (and only) visible widget: accept/reject dialog window. + * We go through this pain to avoid dependency on libXt. + */ + +int ugly_accept_window(char *addr) { + +#define t2x2_width 16 +#define t2x2_height 16 +static 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; + XFontStruct *font_info; + XSizeHints hints; + Pixmap ico; + XGCValues values; + 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; + KeyCode key_y, key_n; + char str1[100]; + char str2[] = "To accept: press \"y\" or click Mouse Button1"; + char str3[] = "To reject: press \"n\" or click Mouse Button3"; + + int x, y, w = 345, h = 120, ret = 0; + + x = (dpy_x - w)/2; + y = (dpy_y - h)/2; + if (x < 0) x = 0; + if (y < 0) y = 0; + + 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); + + ico = XCreateBitmapFromData(dpy, awin, 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, ExposureMask | KeyPressMask | ButtonPressMask + | StructureNotifyMask); + + if ((font_info = XLoadQueryFont(dpy, "fixed")) == NULL) { + 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); + + sprintf(str1, "x11vnc: accept connection from %s?", addr); + key_y = XKeysymToKeycode(dpy, XStringToKeysym("y")); + key_n = XKeysymToKeycode(dpy, XStringToKeysym("n")); + + while (1) { + int out = -1; + + XNextEvent(dpy, &ev); + + switch(ev.type) { + case Expose: + while (XCheckTypedEvent(dpy, Expose, &ev)) { + ; + } + XDrawString(dpy, awin, gc, 20, 30, str1, strlen(str1)); + XDrawString(dpy, awin, gc, 20, 50, str2, strlen(str2)); + XDrawString(dpy, awin, gc, 20, 70, str3, strlen(str3)); + break; + + case ClientMessage: + if (ev.xclient.message_type == wm_protocols && + ev.xclient.data.l[0] == wm_delete_window) { + out = 0; + } + break; + + case ButtonPress: + if (ev.xbutton.button == 1) { + out = 1; + } else if (ev.xbutton.button == 3) { + out = 0; + } + break; + + case KeyPress: + if (ev.xkey.keycode == key_y) { + out = 1; + } else if (ev.xkey.keycode == key_n) { + out = 0; + } + break; + default: + break; + } + if (out != -1) { + ret = out; + XUnmapWindow(dpy, awin); + XFreeGC(dpy, gc); + XDestroyWindow(dpy, awin); + XFlush(dpy); + break; + } + } + X_UNLOCK; + + return ret; +} + +/* + * 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 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. + * + */ +int accept_client(char *addr) { + char xmessage[200], *cmd = NULL; + static char rfb_client_env[100], *display_env = NULL; + + if (accept_cmd == NULL) { + return 1; /* no command specified, so we accept */ + } + + if (addr == NULL || addr[0] == '\0') { + addr = "unknown-host"; + } + + if (!strcmp(accept_cmd, "xmessage")) { + /* make our own command using xmessage(1) */ + sprintf(xmessage, "xmessage -buttons yes:0,no:2 -center " + "'x11vnc: accept connection from %s?'", addr); + cmd = xmessage; + + } else if (!strcmp(accept_cmd, "popup") || !strcmp(accept_cmd, + "builtin")) { + rfbLog("accept_client: using builtin popup for: %s\n", addr); + if (ugly_accept_window(addr)) { + rfbLog("accept_client: popup accepted: %s\n", addr); + return 1; + } else { + rfbLog("accept_client: popup rejected: %s\n", addr); + return 0; + } + } else { + cmd = accept_cmd; + } + + if (cmd) { + int rc; + char *dpystr = DisplayString(dpy); + + /* set RFB_CLIENT to IP addr for command to use */ + sprintf(rfb_client_env, "RFB_CLIENT=%s", addr); + putenv(rfb_client_env); + + /* + * Better set DISPLAY to the one we are polling, if they + * want something trickier, they can handle on their own + * via environment, etc. XXX really should save/restore old. + */ + if (display_env == NULL) { + display_env = (char *) malloc(strlen(dpystr)+10); + } + sprintf(display_env, "DISPLAY=%s", dpystr); + putenv(display_env); + + rfbLog("accept_client: running command:\n"); + rfbLog(" %s\n", cmd); + rc = system(cmd); + if (rc >= 256) { + rc = rc/256; + } + rfbLog("accept_client: command returned: %d\n", rc); + + sprintf(rfb_client_env, "RFB_CLIENT="); + putenv(rfb_client_env); + + 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 option: periodically read the file looking for * a connect string. If one is found set client_connect to it. @@ -675,11 +910,6 @@ enum rfbNewClientAction new_client(rfbClientPtr client) { static accepted_client = 0; last_event = last_input = time(0); - 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 (connect_once) { if (screen->rfbDontDisconnect && screen->rfbNeverShared) { if (! shared && accepted_client) { @@ -689,6 +919,19 @@ enum rfbNewClientAction new_client(rfbClientPtr client) { } } } + 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->host)) { + 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->clientGoneHook = client_gone; if (view_only) { client->clientData = (void *) -1; @@ -5125,6 +5368,16 @@ void print_help() { " a prefix, e.g. \"192.168.100.\" to match a simple subnet,\n" " for more control build libvncserver with libwrap support.\n" "-localhost Same as -allow 127.0.0.1\n" +"-accept string Prompt user at the X11 display whether an incoming\n" +" client should be allowed to connect. \"string\" is an\n" +" external command run via system(3). The RFB_CLIENT env.\n" +" variable will be set to the client's IP number. If the\n" +" command returns 0 the client is accepted, otherwise it\n" +" is rejected. If string is \"xmessage\" then an xmessage(1)\n" +" invocation is used for the command. If string is \"popup\"\n" +" then a builtin popup window is used. Note that x11vnc\n" +" blocks while the external command is running (other\n" +" clients may see no updates during this period).\n" "-inetd Launched by inetd(1): stdio instead of listening socket.\n" "\n" "-noshm Do not use the MIT-SHM extension for the polling.\n" @@ -5184,7 +5437,7 @@ void print_help() { " If you include a modifier like \"Shift_L\" the modifier's\n" " up/down state is toggled, e.g. to send \"The\" use\n" " :Shift_L+t+Shift_L+h+e: (the 1st one is shift down and\n" -" the 2nd one is shift up. (note: the initial state of\n" +" the 2nd one is shift up). (note: the initial state of\n" " the modifier is ignored and not reset)\n" " To include button events use \"Button1\", ... etc.\n" "\n" @@ -5382,6 +5635,8 @@ int main(int argc, char** argv) { allow_list = argv[++i]; } else if (!strcmp(arg, "-localhost")) { allow_list = "127.0.0.1"; + } else if (!strcmp(arg, "-accept")) { + accept_cmd = argv[++i]; } else if (!strcmp(arg, "-many") || !strcmp(arg, "-forever")) { connect_once = 0; @@ -5611,6 +5866,8 @@ int main(int argc, char** argv) { : "null"); fprintf(stderr, "allow: %s\n", allow_list ? allow_list : "null"); + fprintf(stderr, "accept: %s\n", accept_cmd ? accept_cmd + : "null"); fprintf(stderr, "conn_once: %d\n", connect_once); fprintf(stderr, "connect: %s\n", client_connect ? client_connect : "null");