/* forked program */ static int fork_helper(void) { pid_t pid; int i, client, max_rounds = 10; pid = fork(); if (pid == -1) { DEBUG(DEBUG_CRIT,("Unable to fork: %s\n", strerror(errno))); return -1; } if (pid == 0) { // Child client = socket_client_connect(); if (client < 0) { exit(1); } socket_client_write(client); for (i = 1 ; i <= max_rounds ; i++ ) { DEBUG(DEBUG_DEBUG,("Child process waiting ( %d/%d)\n", i, max_rounds)); sleep(1); } socket_client_close(client); exit(0); } else { globals.helper_pid = pid; } return 0; }
/* Checks if a packet size is correct */ int check_size(int length, int target, char* error) { if (length != target) { error("Invalid %s packet (%d != %d)", error, length, target); socket_client_close(0); return 0; } return 1; }
/* Unrequested data came in from WebSocket client. */ static void socket_client_read() { char buffer[BUFFERSIZE]; int length; length = socket_client_read_frame(buffer, sizeof(buffer)); if (length < 0) { socket_client_close(1); return; } if (length >= BUFFERSIZE) { error("Unrequested command too long: (>%d bytes).", length); socket_client_close(1); return; } /* Ignore return value (connection gets closed on error) */ socket_client_handle_unrequested(buffer, length); }
int main(int argc, char** argv) { int c; while ((c = getopt(argc, argv, "v:")) != -1) { switch (c) { case 'v': verbose = atoi(optarg); break; default: usage(argv[0]); } } if (optind != argc-1) usage(argv[0]); char* display = argv[optind]; trueorabort(display[0] == ':', "Invalid display: '%s'", display); char* endptr; int displaynum = (int)strtol(display+1, &endptr, 10); trueorabort(display+1 != endptr && (*endptr == '\0' || *endptr == '.'), "Invalid display number: '%s'", display); init_display(display); socket_server_init(PORT_BASE + displaynum); unsigned char buffer[BUFFERSIZE]; int length; while (1) { set_connected(dpy, False); socket_server_accept(VERSION); write_init(); set_connected(dpy, True); while (1) { length = socket_client_read_frame((char*)buffer, sizeof(buffer)); if (length < 0) { socket_client_close(1); break; } if (length < 1) { error("Invalid packet from client (size <1)."); socket_client_close(0); break; } switch (buffer[0]) { case 'S': /* Screen */ if (!check_size(length, sizeof(struct screen), "screen")) break; write_image((struct screen*)buffer); break; case 'P': /* Cursor */ if (!check_size(length, sizeof(struct cursor), "cursor")) break; write_cursor(); break; case 'R': /* Resolution */ if (!check_size(length, sizeof(struct resolution), "resolution")) break; change_resolution((struct resolution*)buffer); break; case 'K': { /* Key */ if (!check_size(length, sizeof(struct key), "key")) break; struct key* k = (struct key*)buffer; log(2, "Key: kc=%04x\n", k->keycode); XTestFakeKeyEvent(dpy, k->keycode, k->down, CurrentTime); if (k->down) { kb_add(KEYBOARD, k->keycode); } else { kb_remove(KEYBOARD, k->keycode); } break; } case 'C': { /* Click */ if (!check_size(length, sizeof(struct mouseclick), "mouseclick")) break; struct mouseclick* mc = (struct mouseclick*)buffer; XTestFakeButtonEvent(dpy, mc->button, mc->down, CurrentTime); if (mc->down) { kb_add(MOUSE, mc->button); } else { kb_remove(MOUSE, mc->button); } break; } case 'M': { /* Mouse move */ if (!check_size(length, sizeof(struct mousemove), "mousemove")) break; struct mousemove* mm = (struct mousemove*)buffer; XTestFakeMotionEvent(dpy, 0, mm->x, mm->y, CurrentTime); break; } case 'Q': /* "Quit": release all keys */ kb_release_all(); break; default: error("Invalid packet from client (%d).", buffer[0]); socket_client_close(0); } } socket_client_close(0); kb_release_all(); close_mmap(&cache[0]); close_mmap(&cache[1]); } return 0; }
int main(int argc, char **argv) { int n; /* Poll array: * 0 - server_fd * 1 - pipein_fd * 2 - client_fd (if any) */ struct pollfd fds[3]; int nfds = 3; sigset_t sigmask; sigset_t sigmask_orig; struct sigaction act; int c; while ((c = getopt(argc, argv, "v:")) != -1) { switch (c) { case 'v': verbose = atoi(optarg); break; default: fprintf(stderr, "%s [-v 0-3]\n", argv[0]); return 1; } } /* Termination signal handler. */ memset(&act, 0, sizeof(act)); act.sa_handler = signal_handler; if (sigaction(SIGHUP, &act, 0) < 0 || sigaction(SIGINT, &act, 0) < 0 || sigaction(SIGTERM, &act, 0) < 0) { syserror("sigaction error."); return 2; } /* Ignore SIGPIPE in all cases: it may happen, since we write to pipes, but * it is not fatal. */ sigemptyset(&sigmask); sigaddset(&sigmask, SIGPIPE); if (sigprocmask(SIG_BLOCK, &sigmask, NULL) < 0) { syserror("sigprocmask error."); return 2; } /* Ignore terminating signals, except when ppoll is running. Save current * mask in sigmask_orig. */ sigemptyset(&sigmask); sigaddset(&sigmask, SIGHUP); sigaddset(&sigmask, SIGINT); sigaddset(&sigmask, SIGTERM); if (sigprocmask(SIG_BLOCK, &sigmask, &sigmask_orig) < 0) { syserror("sigprocmask error."); return 2; } /* Prepare pollfd structure. */ memset(fds, 0, sizeof(fds)); fds[0].events = POLLIN; fds[1].events = POLLIN; fds[2].events = POLLIN; /* Initialise pipe and WebSocket server */ socket_server_init(PORT); pipe_init(); while (!terminate) { /* Make sure fds is up to date. */ fds[0].fd = server_fd; fds[1].fd = pipein_fd; fds[2].fd = client_fd; /* Only handle signals in ppoll: this makes sure we complete processing * the current request before bailing out. */ n = ppoll(fds, nfds, NULL, &sigmask_orig); log(3, "poll ret=%d (%d, %d, %d)\n", n, fds[0].revents, fds[1].revents, fds[2].revents); if (n < 0) { /* Do not print error when ppoll is interupted by a signal. */ if (errno != EINTR || verbose >= 1) syserror("ppoll error."); break; } if (fds[0].revents & POLLIN) { log(1, "WebSocket accept."); socket_server_accept(VERSION); n--; } if (fds[1].revents & POLLIN) { log(2, "Pipe fd ready."); pipein_read(); n--; } if (fds[2].revents & POLLIN) { log(2, "Client fd ready."); socket_client_read(); n--; } if (n > 0) { /* Some events were not handled, this is a problem */ error("Some poll events could not be handled: " "ret=%d (%d, %d, %d).", n, fds[0].revents, fds[1].revents, fds[2].revents); break; } } log(1, "Terminating..."); if (client_fd) socket_client_close(1); return 0; }
/* Handle unrequested packet from extension. * Returns 0 on success. On error, returns -1 and closes websocket connection. */ static int socket_client_handle_unrequested(const char* buffer, const int length) { /* Process the client request. */ switch (buffer[0]) { case 'C': { /* Send a command to croutoncycle */ char reply[BUFFERSIZE]; int replylength = 1; reply[FRAMEMAXHEADERSIZE] = 'C'; char* cmd = "croutoncycle"; char param[length]; memcpy(param, buffer+1, length-1); param[length-1] = '\0'; char* args[] = { cmd, param, NULL }; log(2, "Received croutoncycle command (%s)", param); /* We are only interested in the output for list commands */ if (param[0] == 'l') { int n = popen2(cmd, args, NULL, 0, &reply[FRAMEMAXHEADERSIZE+1], BUFFERSIZE-FRAMEMAXHEADERSIZE-1); if (n < 0) { error("Call to croutoncycle failed."); socket_client_close(0); return -1; } replylength += n; } else if (param[0] == 'O') { /* Extra OK response from a C back-and-forth. Disregard. */ break; } else { /* Launch command in background (this is necessary as croutoncycle may send a websocket command, leaving us deadlocked...) */ pid_t pid = fork(); if (pid < 0) { syserror("Fork error."); exit(1); } else if (pid == 0) { /* Double-fork to avoid zombies */ pid_t pid2 = fork(); if (pid2 < 0) { syserror("Fork error."); exit(1); } else if (pid2 == 0) { execvp(cmd, args); error("Error running '%s'.", cmd); exit(127); } exit(0); } /* Wait for first fork to complete. */ waitpid(pid, NULL, 0); } if (socket_client_write_frame(reply, replylength, WS_OPCODE_TEXT, 1) < 0) { error("Write error."); socket_client_close(0); return -1; } break; } default: { int len = length > 64 ? 64 : length; char dump[len+1]; memcpy(dump, buffer, len); dump[len] = '\0'; error("Received an unexpected packet from client (%s).", dump); socket_client_close(0); return -1; } } return 0; }
/* Read data from the pipe, and forward it to the socket client. */ static void pipein_read() { int n; char buffer[FRAMEMAXHEADERSIZE+BUFFERSIZE]; int first = 1; char firstchar = '\0'; if (client_fd < 0) { log(1, "No client FD."); pipein_reopen(); pipeout_error("EError: not connected."); return; } while (1) { n = read(pipein_fd, buffer+FRAMEMAXHEADERSIZE, BUFFERSIZE); log(3, "n=%d", n); if (n < 0) { /* This is very unlikely, and fatal. */ syserror("Error reading from pipe."); exit(1); } else if (n == 0) { break; } if (first) firstchar = buffer[FRAMEMAXHEADERSIZE]; /* Write a text frame for the first packet, then cont frames. */ n = socket_client_write_frame(buffer, n, first ? WS_OPCODE_TEXT : WS_OPCODE_CONT, 0); if (n < 0) { error("Error writing frame."); pipein_reopen(); pipeout_error("EError: socket write error."); return; } first = 0; } log(3, "EOF"); pipein_reopen(); /* Empty FIN frame to finish the message. */ n = socket_client_write_frame(buffer, 0, first ? WS_OPCODE_TEXT : WS_OPCODE_CONT, 1); if (n < 0) { error("Error writing frame."); pipeout_error("EError: socket write error"); return; } log(2, "Reading answer from client..."); int fin = 0; uint32_t maskkey; int retry = 0; first = 1; /* Ignore return value, so we still read the frame even if pipeout * cannot be open. */ pipeout_open(); /* Read possibly fragmented message from WebSocket. */ while (fin != 1) { int len = socket_client_read_frame_header(&fin, &maskkey, &retry); log(3, "len=%d fin=%d retry=%d...", len, fin, retry); if (retry) continue; if (len < 0) goto exit; /* Read the whole frame, and write it to pipeout */ while (len > 0) { int rlen = (len > BUFFERSIZE) ? BUFFERSIZE: len; if (socket_client_read_frame_data(buffer, rlen, maskkey) < 0) goto exit; /* Check first byte */ if (first && buffer[0] != firstchar && buffer[0] != 'E') { /* This is not a response: unrequested packet */ if (!fin && len < BUFFERSIZE) { /* !fin, and buffer not full, finish reading... */ rlen = socket_client_read_frame(buffer+len, sizeof(buffer)-len); if (rlen < 0) goto exit; len += rlen; } if (len >= BUFFERSIZE) { error("Unrequested command too long: (>%d bytes).", len); socket_client_close(1); goto exit; } if (socket_client_handle_unrequested(buffer, len) < 0) goto exit; /* Command was handled, try reading the answer again. */ fin = 0; break; } /* Ignore return value as well */ pipeout_write(buffer, rlen); len -= rlen; first = 0; } } exit: pipeout_close(); }