/* * Scatter the fds read from the input process to multiple outputs. */ STATIC void scatter_input_fds(struct dgsh_negotiation *mb) { struct dgsh_conc *this_conc = find_conc(mb, pid); if (!this_conc) { printf("%s(): Concentrator with pid %d not registered", __func__, pid); exit(1); // XXX } int n_to_read = this_conc->input_fds; int *read_fds = (int *)malloc(n_to_read * sizeof(int)); int i, j, write_index = 0; bool ignore = false; DPRINTF(4, "%s(): fds to read: %d", __func__, n_to_read); for (i = 0; i < n_to_read; i++) read_fds[i] = read_fd(STDIN_FILENO); for (i = STDOUT_FILENO; i != STDIN_FILENO; i = next_fd(i, &ignore)) { int n_to_write = get_expected_fds_n(mb, pi[i].pid); DPRINTF(4, "%s(): fds to write for p[%d].pid %d: %d", __func__, i, pi[i].pid, n_to_write); for (j = write_index; j < write_index + n_to_write; j++) { write_fd(i, read_fds[j]); DPRINTF(4, "%s(): Write fd: %d to output channel: %d", __func__, read_fds[j], i); } write_index += n_to_write; } assert(write_index == n_to_read); }
void do_dup(PCB *pcb) { int fd_old = pcb->registers[5], fd_new; struct File_Descriptor *new_fd_obj, *old_fd_obj; if(valid_fd(pcb, fd_old, -1)) { old_fd_obj = pcb->fd_table[fd_old]; fd_new = next_fd(pcb); if(fd_new > -1) { new_fd_obj = new_fd(fd_new, old_fd_obj->permission); dup_fd(old_fd_obj,new_fd_obj); pcb->fd_table[new_fd_obj->id] = new_fd_obj; } else { syscall_return(pcb, -1*EMFILE); } //printf("process %d duping to new fd %d!\n",pcb->pid, fd_new); syscall_return(pcb, fd_new); } else { syscall_return(pcb,-1*EBADF); } }
/** * Register current concentrator to message block's * concentrator array */ STATIC int set_io_channels(struct dgsh_negotiation *mb) { if (find_conc(mb, pid)) return 0; struct dgsh_conc c; c.pid = pid; c.input_fds = -1; c.output_fds = -1; c.n_proc_pids = (nfd > 2 ? nfd - 2 : 1); c.multiple_inputs = multiple_inputs; c.proc_pids = (int *)malloc(sizeof(int) * c.n_proc_pids); int j = 0, i; DPRINTF(4, "%s: n_proc_pids: %d", __func__, c.n_proc_pids); if (multiple_inputs) { c.endpoint_pid = pi[STDOUT_FILENO].pid; if (c.endpoint_pid == 0) return 1; for (i = STDIN_FILENO; i < nfd; i == STDIN_FILENO ? i = FREE_FILENO : i++) if (pi[i].pid == 0) { free(c.proc_pids); return 1; } else c.proc_pids[j++] = pi[i].pid; } else { bool ignore; c.endpoint_pid = pi[STDIN_FILENO].pid; if (c.endpoint_pid == 0) return 1; for (i = STDOUT_FILENO; i != STDIN_FILENO; i = next_fd(i, &ignore)) if (pi[i].pid == 0) { free(c.proc_pids); return 1; } else c.proc_pids[j++] = pi[i].pid; } if (!mb->conc_array) { mb->conc_array = (struct dgsh_conc *)malloc(sizeof(struct dgsh_conc)); mb->n_concs = 1; } else { mb->n_concs++; mb->conc_array = (struct dgsh_conc *)realloc(mb->conc_array, sizeof(struct dgsh_conc) * mb->n_concs); } memcpy(&mb->conc_array[mb->n_concs - 1], &c, sizeof(struct dgsh_conc)); DPRINTF(4, "%s(): Added conc with pid: %d, now n_concs: %d", __func__, mb->conc_array[mb->n_concs - 1].pid, mb->n_concs); return 0; }
int first_fd(int *state, int *rwx) { *state = 0; return next_fd(state, rwx); }
int main(int argc, char **argv) { bool sending; int *fdlist; int fd; int i, fdstate; size_t fdsize; int exitcode; bool errors; enum TriState sanitise_stdout = AUTO, sanitise_stderr = AUTO; bool use_subsystem = false; bool just_test_share_exists = false; unsigned long now; struct winsize size; const struct BackendVtable *backvt; fdlist = NULL; fdsize = 0; /* * Initialise port and protocol to sensible defaults. (These * will be overridden by more or less anything.) */ default_protocol = PROT_SSH; default_port = 22; bufchain_init(&stdout_data); bufchain_init(&stderr_data); bufchain_sink_init(&stdout_bcs, &stdout_data); bufchain_sink_init(&stderr_bcs, &stderr_data); stdout_bs = BinarySink_UPCAST(&stdout_bcs); stderr_bs = BinarySink_UPCAST(&stderr_bcs); outgoingeof = EOF_NO; flags = FLAG_STDERR_TTY; cmdline_tooltype |= (TOOLTYPE_HOST_ARG | TOOLTYPE_HOST_ARG_CAN_BE_SESSION | TOOLTYPE_HOST_ARG_PROTOCOL_PREFIX | TOOLTYPE_HOST_ARG_FROM_LAUNCHABLE_LOAD); stderr_tty_init(); /* * Process the command line. */ conf = conf_new(); do_defaults(NULL, conf); loaded_session = false; default_protocol = conf_get_int(conf, CONF_protocol); default_port = conf_get_int(conf, CONF_port); errors = false; { /* * Override the default protocol if PLINK_PROTOCOL is set. */ char *p = getenv("PLINK_PROTOCOL"); if (p) { const struct BackendVtable *vt = backend_vt_from_name(p); if (vt) { default_protocol = vt->protocol; default_port = vt->default_port; conf_set_int(conf, CONF_protocol, default_protocol); conf_set_int(conf, CONF_port, default_port); } } } while (--argc) { char *p = *++argv; int ret = cmdline_process_param(p, (argc > 1 ? argv[1] : NULL), 1, conf); if (ret == -2) { fprintf(stderr, "plink: option \"%s\" requires an argument\n", p); errors = true; } else if (ret == 2) { --argc, ++argv; } else if (ret == 1) { continue; } else if (!strcmp(p, "-batch")) { console_batch_mode = true; } else if (!strcmp(p, "-s")) { /* Save status to write to conf later. */ use_subsystem = true; } else if (!strcmp(p, "-V") || !strcmp(p, "--version")) { version(); } else if (!strcmp(p, "--help")) { usage(); exit(0); } else if (!strcmp(p, "-pgpfp")) { pgp_fingerprints(); exit(1); } else if (!strcmp(p, "-o")) { if (argc <= 1) { fprintf(stderr, "plink: option \"-o\" requires an argument\n"); errors = true; } else { --argc; /* Explicitly pass "plink" in place of appname for * error reporting purposes. appname will have been * set by be_foo.c to something more generic, probably * "PuTTY". */ provide_xrm_string(*++argv, "plink"); } } else if (!strcmp(p, "-shareexists")) { just_test_share_exists = true; } else if (!strcmp(p, "-fuzznet")) { conf_set_int(conf, CONF_proxy_type, PROXY_FUZZ); conf_set_str(conf, CONF_proxy_telnet_command, "%host"); } else if (!strcmp(p, "-sanitise-stdout") || !strcmp(p, "-sanitize-stdout")) { sanitise_stdout = FORCE_ON; } else if (!strcmp(p, "-no-sanitise-stdout") || !strcmp(p, "-no-sanitize-stdout")) { sanitise_stdout = FORCE_OFF; } else if (!strcmp(p, "-sanitise-stderr") || !strcmp(p, "-sanitize-stderr")) { sanitise_stderr = FORCE_ON; } else if (!strcmp(p, "-no-sanitise-stderr") || !strcmp(p, "-no-sanitize-stderr")) { sanitise_stderr = FORCE_OFF; } else if (!strcmp(p, "-no-antispoof")) { console_antispoof_prompt = false; } else if (*p != '-') { strbuf *cmdbuf = strbuf_new(); while (argc > 0) { if (cmdbuf->len > 0) put_byte(cmdbuf, ' '); /* add space separator */ put_datapl(cmdbuf, ptrlen_from_asciz(p)); if (--argc > 0) p = *++argv; } conf_set_str(conf, CONF_remote_cmd, cmdbuf->s); conf_set_str(conf, CONF_remote_cmd2, ""); conf_set_bool(conf, CONF_nopty, true); /* command => no tty */ strbuf_free(cmdbuf); break; /* done with cmdline */ } else { fprintf(stderr, "plink: unknown option \"%s\"\n", p); errors = true; } } if (errors) return 1; if (!cmdline_host_ok(conf)) { usage(); } prepare_session(conf); /* * Perform command-line overrides on session configuration. */ cmdline_run_saved(conf); /* * If we have no better ideas for the remote username, use the local * one, as 'ssh' does. */ if (conf_get_str(conf, CONF_username)[0] == '\0') { char *user = get_username(); if (user) { conf_set_str(conf, CONF_username, user); sfree(user); } } /* * Apply subsystem status. */ if (use_subsystem) conf_set_bool(conf, CONF_ssh_subsys, true); if (!*conf_get_str(conf, CONF_remote_cmd) && !*conf_get_str(conf, CONF_remote_cmd2) && !*conf_get_str(conf, CONF_ssh_nc_host)) flags |= FLAG_INTERACTIVE; /* * Select protocol. This is farmed out into a table in a * separate file to enable an ssh-free variant. */ backvt = backend_vt_from_proto(conf_get_int(conf, CONF_protocol)); if (!backvt) { fprintf(stderr, "Internal fault: Unsupported protocol found\n"); return 1; } /* * Block SIGPIPE, so that we'll get EPIPE individually on * particular network connections that go wrong. */ putty_signal(SIGPIPE, SIG_IGN); /* * Set up the pipe we'll use to tell us about SIGWINCH. */ if (pipe(signalpipe) < 0) { perror("pipe"); exit(1); } /* We don't want the signal handler to block if the pipe's full. */ nonblock(signalpipe[0]); nonblock(signalpipe[1]); cloexec(signalpipe[0]); cloexec(signalpipe[1]); putty_signal(SIGWINCH, sigwinch); /* * Now that we've got the SIGWINCH handler installed, try to find * out the initial terminal size. */ if (ioctl(STDIN_FILENO, TIOCGWINSZ, &size) >= 0) { conf_set_int(conf, CONF_width, size.ws_col); conf_set_int(conf, CONF_height, size.ws_row); } /* * Decide whether to sanitise control sequences out of standard * output and standard error. * * If we weren't given a command-line override, we do this if (a) * the fd in question is pointing at a terminal, and (b) we aren't * trying to allocate a terminal as part of the session. * * (Rationale: the risk of control sequences is that they cause * confusion when sent to a local terminal, so if there isn't one, * no problem. Also, if we allocate a remote terminal, then we * sent a terminal type, i.e. we told it what kind of escape * sequences we _like_, i.e. we were expecting to receive some.) */ if (sanitise_stdout == FORCE_ON || (sanitise_stdout == AUTO && isatty(STDOUT_FILENO) && conf_get_bool(conf, CONF_nopty))) { stdout_scc = stripctrl_new(stdout_bs, true, L'\0'); stdout_bs = BinarySink_UPCAST(stdout_scc); } if (sanitise_stderr == FORCE_ON || (sanitise_stderr == AUTO && isatty(STDERR_FILENO) && conf_get_bool(conf, CONF_nopty))) { stderr_scc = stripctrl_new(stderr_bs, true, L'\0'); stderr_bs = BinarySink_UPCAST(stderr_scc); } sk_init(); uxsel_init(); /* * Plink doesn't provide any way to add forwardings after the * connection is set up, so if there are none now, we can safely set * the "simple" flag. */ if (conf_get_int(conf, CONF_protocol) == PROT_SSH && !conf_get_bool(conf, CONF_x11_forward) && !conf_get_bool(conf, CONF_agentfwd) && !conf_get_str_nthstrkey(conf, CONF_portfwd, 0)) conf_set_bool(conf, CONF_ssh_simple, true); if (just_test_share_exists) { if (!backvt->test_for_upstream) { fprintf(stderr, "Connection sharing not supported for connection " "type '%s'\n", backvt->name); return 1; } if (backvt->test_for_upstream(conf_get_str(conf, CONF_host), conf_get_int(conf, CONF_port), conf)) return 0; else return 1; } /* * Start up the connection. */ logctx = log_init(default_logpolicy, conf); { const char *error; char *realhost; /* nodelay is only useful if stdin is a terminal device */ bool nodelay = conf_get_bool(conf, CONF_tcp_nodelay) && isatty(0); /* This is a good place for a fuzzer to fork us. */ #ifdef __AFL_HAVE_MANUAL_CONTROL __AFL_INIT(); #endif error = backend_init(backvt, plink_seat, &backend, logctx, conf, conf_get_str(conf, CONF_host), conf_get_int(conf, CONF_port), &realhost, nodelay, conf_get_bool(conf, CONF_tcp_keepalives)); if (error) { fprintf(stderr, "Unable to open connection:\n%s\n", error); return 1; } ldisc_create(conf, NULL, backend, plink_seat); sfree(realhost); } /* * Set up the initial console mode. We don't care if this call * fails, because we know we aren't necessarily running in a * console. */ local_tty = (tcgetattr(STDIN_FILENO, &orig_termios) == 0); atexit(cleanup_termios); seat_echoedit_update(plink_seat, 1, 1); sending = false; now = GETTICKCOUNT(); pollwrapper *pw = pollwrap_new(); while (1) { int rwx; int ret; unsigned long next; pollwrap_clear(pw); pollwrap_add_fd_rwx(pw, signalpipe[0], SELECT_R); if (!sending && backend_connected(backend) && backend_sendok(backend) && backend_sendbuffer(backend) < MAX_STDIN_BACKLOG) { /* If we're OK to send, then try to read from stdin. */ pollwrap_add_fd_rwx(pw, STDIN_FILENO, SELECT_R); } if (bufchain_size(&stdout_data) > 0) { /* If we have data for stdout, try to write to stdout. */ pollwrap_add_fd_rwx(pw, STDOUT_FILENO, SELECT_W); } if (bufchain_size(&stderr_data) > 0) { /* If we have data for stderr, try to write to stderr. */ pollwrap_add_fd_rwx(pw, STDERR_FILENO, SELECT_W); } /* Count the currently active fds. */ i = 0; for (fd = first_fd(&fdstate, &rwx); fd >= 0; fd = next_fd(&fdstate, &rwx)) i++; /* Expand the fdlist buffer if necessary. */ sgrowarray(fdlist, fdsize, i); /* * Add all currently open fds to pw, and store them in fdlist * as well. */ int fdcount = 0; for (fd = first_fd(&fdstate, &rwx); fd >= 0; fd = next_fd(&fdstate, &rwx)) { fdlist[fdcount++] = fd; pollwrap_add_fd_rwx(pw, fd, rwx); } if (toplevel_callback_pending()) { ret = pollwrap_poll_instant(pw); } else if (run_timers(now, &next)) { do { unsigned long then; long ticks; then = now; now = GETTICKCOUNT(); if (now - then > next - then) ticks = 0; else ticks = next - now; bool overflow = false; if (ticks > INT_MAX) { ticks = INT_MAX; overflow = true; } ret = pollwrap_poll_timeout(pw, ticks); if (ret == 0 && !overflow) now = next; else now = GETTICKCOUNT(); } while (ret < 0 && errno == EINTR); } else { ret = pollwrap_poll_endless(pw); } if (ret < 0 && errno == EINTR) continue; if (ret < 0) { perror("poll"); exit(1); } for (i = 0; i < fdcount; i++) { fd = fdlist[i]; int rwx = pollwrap_get_fd_rwx(pw, fd); /* * We must process exceptional notifications before * ordinary readability ones, or we may go straight * past the urgent marker. */ if (rwx & SELECT_X) select_result(fd, SELECT_X); if (rwx & SELECT_R) select_result(fd, SELECT_R); if (rwx & SELECT_W) select_result(fd, SELECT_W); } if (pollwrap_check_fd_rwx(pw, signalpipe[0], SELECT_R)) { char c[1]; struct winsize size; if (read(signalpipe[0], c, 1) <= 0) /* ignore error */; /* ignore its value; it'll be `x' */ if (ioctl(STDIN_FILENO, TIOCGWINSZ, (void *)&size) >= 0) backend_size(backend, size.ws_col, size.ws_row); } if (pollwrap_check_fd_rwx(pw, STDIN_FILENO, SELECT_R)) { char buf[4096]; int ret; if (backend_connected(backend)) { ret = read(STDIN_FILENO, buf, sizeof(buf)); noise_ultralight(NOISE_SOURCE_IOLEN, ret); if (ret < 0) { perror("stdin: read"); exit(1); } else if (ret == 0) { backend_special(backend, SS_EOF, 0); sending = false; /* send nothing further after this */ } else { if (local_tty) from_tty(buf, ret); else backend_send(backend, buf, ret); } } } if (pollwrap_check_fd_rwx(pw, STDOUT_FILENO, SELECT_W)) { backend_unthrottle(backend, try_output(false)); } if (pollwrap_check_fd_rwx(pw, STDERR_FILENO, SELECT_W)) { backend_unthrottle(backend, try_output(true)); } run_toplevel_callbacks(); if (!backend_connected(backend) && bufchain_size(&stdout_data) == 0 && bufchain_size(&stderr_data) == 0) break; /* we closed the connection */ } exitcode = backend_exitcode(backend); if (exitcode < 0) { fprintf(stderr, "Remote process exit code unavailable\n"); exitcode = 1; /* this is an error condition */ } cleanup_exit(exitcode); return exitcode; /* shouldn't happen, but placates gcc */ }
/* * Pass around the message blocks so that they reach all processes * connected through the concentrator. */ STATIC int pass_message_blocks(void) { fd_set readfds, writefds; int nfds = 0; int i; int oi = -1; /* scatter/gather block's origin index */ int ofd = -1; /* ... origin fd direction */ bool ro = false; /* Whether the read block's origin should * be restored */ bool iswrite = false; if (noinput) { #ifdef TIME clock_gettime(CLOCK_MONOTONIC, &tstart); #endif construct_message_block("dgsh-conc", pid); chosen_mb->origin_fd_direction = STDOUT_FILENO; chosen_mb->is_origin_conc = true; chosen_mb->conc_pid = pid; pi[STDOUT_FILENO].to_write = chosen_mb; } for (;;) { // Create select(2) masks FD_ZERO(&readfds); FD_ZERO(&writefds); for (i = 0; i < nfd; i++) { if (noinput && i == STDIN_FILENO) continue; if (i == STDERR_FILENO) continue; if (!pi[i].seen) { FD_SET(i, &readfds); nfds = max(i + 1, nfds); } if (pi[i].to_write && !pi[i].written) { FD_SET(i, &writefds); nfds = max(i + 1, nfds); pi[i].to_write->is_origin_conc = true; pi[i].to_write->conc_pid = pid; DPRINTF(4, "Actual origin: conc with pid %d", pid); } } again: if (select(nfds, &readfds, &writefds, NULL, NULL) < 0) { if (errno == EINTR) goto again; /* All other cases are internal errors. */ err(1, "select"); } // Read/write what we can for (i = 0; i < nfd; i++) { if (FD_ISSET(i, &writefds)) { iswrite = true; assert(pi[i].to_write); chosen_mb = pi[i].to_write; DPRINTF(4, "**fd i: %d set for writing to tool with pid %d", i, pi[i].pid); write_message_block(i); // XXX check return if (pi[i].to_write->state == PS_RUN || pi[i].to_write->state == PS_DRAW_EXIT || (pi[i].to_write->state == PS_ERROR && pi[i].to_write->is_error_confirmed)) pi[i].written = true; // Write side exit if (is_ready(i, pi[i].to_write)) { pi[i].run_ready = true; DPRINTF(4, "**%s(): pi[%d] is run ready", __func__, i); } pi[i].to_write = NULL; } if (FD_ISSET(i, &readfds)) { struct dgsh_negotiation *rb; ro = false; int next = next_fd(i, &ro); assert(!pi[i].run_ready); assert(pi[next].to_write == NULL); if (read_message_block(i, &pi[next].to_write) == OP_ERROR) { chosen_mb->state = PS_ERROR; if (noinput) chosen_mb->is_error_confirmed = true; pi[next].to_write = chosen_mb; continue; } rb = pi[next].to_write; DPRINTF(4, "%s(): next write via fd %d to pid %d", __func__, next, pi[next].pid); if (oi == -1) { if ((multiple_inputs && i == 1) || (!multiple_inputs && i == 0)) { oi = rb->origin_index; ofd = rb->origin_fd_direction; DPRINTF(4, "**Store origin: %d, fd: %s", oi, ofd ? "stdout" : "stdin"); } } /* If conc talks to conc, set conc's pid * Required in order to allocate fds correctly * in the end */ if (rb->is_origin_conc) pi[i].pid = rb->conc_pid; else pi[i].pid = get_origin_pid(rb); /* If needed, re-set origin. * Don't move this block before get_origin_pid() */ if (ro) { DPRINTF(4, "**Restore origin: %d, fd: %s", oi, ofd ? "stdout" : "stdin"); pi[next].to_write->origin_index = oi; pi[next].to_write->origin_fd_direction = ofd; } else if (noinput) { pi[next].to_write->origin_index = -1; pi[next].to_write->origin_fd_direction = STDOUT_FILENO; } /* Set a conc's required/provided IO in mb */ if (!noinput) set_io_channels(pi[next].to_write); if (rb->state == PS_NEGOTIATION && noinput) { int j, seen = 0; pi[i].seen = true; for (j = 1; j < nfd; j++) if (pi[j].seen) seen++; if ((nfd > 2 && seen == nfd - 2) || seen == nfd - 1) { chosen_mb = rb; DPRINTF(1, "%s(): Gathered I/O requirements.", __func__); int state = solve_graph(); if (state == OP_ERROR) { pi[next].to_write->state = PS_ERROR; pi[next].to_write->is_error_confirmed = true; } else if (state == OP_DRAW_EXIT) pi[next].to_write->state = PS_DRAW_EXIT; else { DPRINTF(1, "%s(): Computed solution", __func__); pi[next].to_write->state = PS_RUN; } for (j = 1; j < nfd; j++) pi[j].seen = false; // Don't free chosen_mb = NULL; } } else if (rb->state == PS_RUN || rb->state == PS_DRAW_EXIT || (rb->state == PS_ERROR && rb->is_error_confirmed)) pi[i].seen = true; else if (rb->state == PS_ERROR) rb->is_error_confirmed = true; print_state(i, (int)rb->initiator_pid, 1); if (pi[i].seen && pi[i].written) { chosen_mb = pi[next].to_write; pi[i].run_ready = true; DPRINTF(4, "**%s(): pi[%d] is run ready", __func__, i); } } } // See if all processes are run-ready nfds = 0; for (i = 0; i < nfd; i++) { if (pi[i].run_ready) nfds++; print_state(i, nfds, 2); } if ((nfd > 2 && (nfds == nfd - 1 || (noinput && nfds == nfd - 2))) || (nfds == nfd || (noinput && nfds == nfd - 1))) { assert(chosen_mb != NULL); DPRINTF(4, "%s(): conc leaves negotiation", __func__); return chosen_mb->state; } else if (chosen_mb != NULL && iswrite) { // Free if we have written DPRINTF(4, "chosen_mb: %lx, i: %d, next: %d, pi[next].to_write: %lx\n", (long)chosen_mb, i, next_fd(i, &ro), (long)pi[next_fd(i, &ro)].to_write); free_mb(chosen_mb); chosen_mb = NULL; iswrite = false; } } }