/* * Call this when proxy negotiation is complete, so that this * socket can begin working normally. */ void proxy_activate(Proxy_Socket p) { void *data; int len; long output_before, output_after; p->state = PROXY_STATE_ACTIVE; /* we want to ignore new receive events until we have sent * all of our buffered receive data. */ sk_set_frozen(p->sub_socket, 1); /* how many bytes of output have we buffered? */ output_before = bufchain_size(&p->pending_oob_output_data) + bufchain_size(&p->pending_output_data); /* and keep track of how many bytes do not get sent. */ output_after = 0; /* send buffered OOB writes */ while (bufchain_size(&p->pending_oob_output_data) > 0) { bufchain_prefix(&p->pending_oob_output_data, &data, &len); output_after += sk_write_oob(p->sub_socket, data, len); bufchain_consume(&p->pending_oob_output_data, len); } /* send buffered normal writes */ while (bufchain_size(&p->pending_output_data) > 0) { bufchain_prefix(&p->pending_output_data, &data, &len); output_after += sk_write(p->sub_socket, data, len); bufchain_consume(&p->pending_output_data, len); } /* if we managed to send any data, let the higher levels know. */ if (output_after < output_before) plug_sent(p->plug, output_after); /* if we were asked to flush the output during * the proxy negotiation process, do so now. */ if (p->pending_flush) sk_flush(p->sub_socket); /* if we have a pending EOF to send, send it */ if (p->pending_eof) sk_write_eof(p->sub_socket); /* if the backend wanted the socket unfrozen, try to unfreeze. * our set_frozen handler will flush buffered receive data before * unfreezing the actual underlying socket. */ if (!p->freeze) sk_set_frozen((Socket)p, 0); }
int try_output(int is_stderr) { bufchain *chain = (is_stderr ? &stderr_data : &stdout_data); int fd = (is_stderr ? STDERR_FILENO : STDOUT_FILENO); void *senddata; int sendlen, ret; if (bufchain_size(chain) > 0) { int prev_nonblock = nonblock(fd); do { bufchain_prefix(chain, &senddata, &sendlen); ret = write(fd, senddata, sendlen); if (ret > 0) bufchain_consume(chain, ret); } while (ret == sendlen && bufchain_size(chain) != 0); if (!prev_nonblock) no_nonblock(fd); if (ret < 0 && errno != EAGAIN) { perror(is_stderr ? "stderr: write" : "stdout: write"); exit(1); } } if (outgoingeof == EOF_PENDING && bufchain_size(&stdout_data) == 0) { close(STDOUT_FILENO); outgoingeof = EOF_SENT; } return bufchain_size(&stdout_data) + bufchain_size(&stderr_data); }
static int localproxy_try_send(Local_Proxy_Socket ps) { int sent = 0; while (bufchain_size(&ps->pending_output_data) > 0) { void *data; int len, ret; bufchain_prefix(&ps->pending_output_data, &data, &len); ret = write(ps->to_cmd, data, len); if (ret < 0 && errno != EWOULDBLOCK) { /* We're inside the Unix frontend here, so we know * that the frontend handle is unnecessary. */ logevent(NULL, strerror(errno)); fatalbox("%s", strerror(errno)); } else if (ret <= 0) { break; } else { bufchain_consume(&ps->pending_output_data, ret); sent += ret; } } if (bufchain_size(&ps->pending_output_data) == 0) uxsel_del(ps->to_cmd); else uxsel_set(ps->to_cmd, 2, localproxy_select_result); return sent; }
int try_output(int is_stderr) { bufchain *chain = (is_stderr ? &stderr_data : &stdout_data); int fd = (is_stderr ? STDERR_FILENO : STDOUT_FILENO); void *senddata; int sendlen, ret, fl; if (bufchain_size(chain) == 0) return bufchain_size(&stdout_data) + bufchain_size(&stderr_data); fl = fcntl(fd, F_GETFL); if (fl != -1 && !(fl & O_NONBLOCK)) fcntl(fd, F_SETFL, fl | O_NONBLOCK); do { bufchain_prefix(chain, &senddata, &sendlen); ret = write(fd, senddata, sendlen); if (ret > 0) bufchain_consume(chain, ret); } while (ret == sendlen && bufchain_size(chain) != 0); if (fl != -1 && !(fl & O_NONBLOCK)) fcntl(fd, F_SETFL, fl); if (ret < 0 && errno != EAGAIN) { perror(is_stderr ? "stderr: write" : "stdout: write"); exit(1); } return bufchain_size(&stdout_data) + bufchain_size(&stderr_data); }
static void serial_try_write(Serial serial) { void *data; int len, ret; assert(serial->fd >= 0); while (bufchain_size(&serial->output_data) > 0) { bufchain_prefix(&serial->output_data, &data, &len); ret = write(serial->fd, data, len); if (ret < 0 && (errno == EWOULDBLOCK)) { /* * We've sent all we can for the moment. */ break; } if (ret < 0) { perror("write serial port"); exit(1); } bufchain_consume(&serial->output_data, ret); } serial_uxsel_setup(serial); }
static void logfopen_callback(void *handle, int mode) { struct LogContext *ctx = (struct LogContext *)handle; char buf[256], *event; struct tm tm; const char *fmode; if (mode == 0) { ctx->state = L_ERROR; /* disable logging */ } else { fmode = (mode == 1 ? "ab" : "wb"); ctx->lgfp = f_open(&ctx->currlogfilename, fmode, FALSE); if (ctx->lgfp) ctx->state = L_OPEN; else ctx->state = L_ERROR; } if (ctx->state == L_OPEN) { /* Write header line into log file. */ tm = ltime(); strftime(buf, 24, "%Y.%m.%d %H:%M:%S", &tm); logprintf(ctx, "=~=~=~=~=~=~=~=~=~=~=~= PuTTY log %s" " =~=~=~=~=~=~=~=~=~=~=~=\r\n", buf); } event = dupprintf("%s session log (%s mode) to file: %s", ctx->state == L_ERROR ? (mode == 0 ? "Disabled writing" : "Error writing") : (mode == 1 ? "Appending" : "Writing new"), (ctx->cfg.logtype == LGTYP_ASCII ? "ASCII" : ctx->cfg.logtype == LGTYP_DEBUG ? "raw" : ctx->cfg.logtype == LGTYP_PACKETS ? "SSH packets" : ctx->cfg.logtype == LGTYP_SSHRAW ? "SSH raw data" : "unknown"), filename_to_str(&ctx->currlogfilename)); logevent(ctx->frontend, event); sfree(event); /* * Having either succeeded or failed in opening the log file, * we should write any queued data out. */ assert(ctx->state != L_OPENING); /* make _sure_ it won't be requeued */ while (bufchain_size(&ctx->queue)) { void *data; int len; bufchain_prefix(&ctx->queue, &data, &len); logwrite(ctx, data, len); bufchain_consume(&ctx->queue, len); } }
static void handle_try_output(struct handle_output *ctx) { void *senddata; int sendlen; if (!ctx->busy && bufchain_size(&ctx->queued_data)) { bufchain_prefix(&ctx->queued_data, &senddata, &sendlen); ctx->buffer = senddata; ctx->len = sendlen; SetEvent(ctx->ev_from_main); ctx->busy = TRUE; } }
void try_output(int is_stderr) { struct output_data *data = (is_stderr ? &edata : &odata); void *senddata; int sendlen; if (!data->busy) { bufchain_prefix(is_stderr ? &stderr_data : &stdout_data, &senddata, &sendlen); data->buffer = senddata; data->len = sendlen; SetEvent(data->eventback); data->busy = 1; } }
static void handle_socket_unfreeze(void *psv) { Handle_Socket ps = (Handle_Socket) psv; void *data; int len; /* * If we've been put into a state other than THAWING since the * last callback, then we're done. */ if (ps->frozen != THAWING) return; /* * Get some of the data we've buffered. */ bufchain_prefix(&ps->inputdata, &data, &len); assert(len > 0); /* * Hand it off to the plug. Be careful of re-entrance - that might * have the effect of trying to close this socket. */ ps->defer_close = TRUE; plug_receive(ps->plug, 0, data, len); bufchain_consume(&ps->inputdata, len); ps->defer_close = FALSE; if (ps->deferred_close) { sk_handle_close(ps); return; } if (bufchain_size(&ps->inputdata) > 0) { /* * If there's still data in our buffer, stay in THAWING state, * and reschedule ourself. */ queue_toplevel_callback(handle_socket_unfreeze, ps); } else { /* * Otherwise, we've successfully thawed! */ ps->frozen = UNFROZEN; handle_unthrottle(ps->recv_h, 0); } }
static void handle_try_output(struct handle_output *ctx) { void *senddata; int sendlen; if (!ctx->busy && bufchain_size(&ctx->queued_data)) { bufchain_prefix(&ctx->queued_data, &senddata, &sendlen); ctx->buffer = senddata; ctx->len = sendlen; SetEvent(ctx->ev_from_main); ctx->busy = TRUE; } else if (!ctx->busy && bufchain_size(&ctx->queued_data) == 0 && ctx->outgoingeof == EOF_PENDING) { CloseHandle(ctx->h); ctx->h = INVALID_HANDLE_VALUE; ctx->outgoingeof = EOF_SENT; } }
void try_output(int is_stderr) { bufchain *chain = (is_stderr ? &stderr_data : &stdout_data); int fd = (is_stderr ? 2 : 1); void *senddata; int sendlen, ret; if (bufchain_size(chain) == 0) return; bufchain_prefix(chain, &senddata, &sendlen); ret = write(fd, senddata, sendlen); if (ret > 0) bufchain_consume(chain, ret); else if (ret < 0) { perror(is_stderr ? "stderr: write" : "stdout: write"); exit(1); } }
static void sk_proxy_set_frozen(Socket s, int is_frozen) { Proxy_Socket ps = (Proxy_Socket)s; if (ps->state != PROXY_STATE_ACTIVE) { ps->freeze = is_frozen; return; } /* handle any remaining buffered recv data first */ if (bufchain_size(&ps->pending_input_data) > 0) { ps->freeze = is_frozen; /* loop while we still have buffered data, and while we are * unfrozen. the plug_receive call in the loop could result * in a call back into this function refreezing the socket, * so we have to check each time. */ while (!ps->freeze && bufchain_size(&ps->pending_input_data) > 0) { void *data; char databuf[512]; int len; bufchain_prefix(&ps->pending_input_data, &data, &len); if (len > lenof(databuf)) len = lenof(databuf); memcpy(databuf, data, len); bufchain_consume(&ps->pending_input_data, len); plug_receive(ps->plug, 0, databuf, len); } /* if we're still frozen, we'll have to wait for another * call from the backend to finish unbuffering the data. */ if (ps->freeze) return; } sk_set_frozen(ps->sub_socket, is_frozen); }
static void handle_socket_unfreeze(void *psv) { Handle_Socket ps = (Handle_Socket) psv; void *data; int len, new_backlog; /* * If we've been put into a state other than THAWING since the * last callback, then we're done. */ if (ps->frozen != THAWING) return; /* * Get some of the data we've buffered. */ bufchain_prefix(&ps->inputdata, &data, &len); assert(len > 0); /* * Hand it off to the plug. */ new_backlog = plug_receive(ps->plug, 0, data, len); if (bufchain_size(&ps->inputdata) > 0) { /* * If there's still data in our buffer, stay in THAWING state, * and reschedule ourself. */ queue_toplevel_callback(handle_socket_unfreeze, ps); } else { /* * Otherwise, we've successfully thawed! */ ps->frozen = UNFROZEN; handle_unthrottle(ps->recv_h, new_backlog); } }
static int localproxy_try_send(Local_Proxy_Socket ps) { int sent = 0; while (bufchain_size(&ps->pending_output_data) > 0) { void *data; int len, ret; bufchain_prefix(&ps->pending_output_data, &data, &len); ret = write(ps->to_cmd, data, len); if (ret < 0 && errno != EWOULDBLOCK) { plug_closing(ps->plug, strerror(errno), errno, 0); return 0; } else if (ret <= 0) { break; } else { bufchain_consume(&ps->pending_output_data, ret); sent += ret; } } if (ps->outgoingeof == EOF_PENDING) { del234(localproxy_by_tofd, ps); close(ps->to_cmd); uxsel_del(ps->to_cmd); ps->to_cmd = -1; ps->outgoingeof = EOF_SENT; } if (bufchain_size(&ps->pending_output_data) == 0) uxsel_del(ps->to_cmd); else uxsel_set(ps->to_cmd, 2, localproxy_select_result); return sent; }
static void logfopen_callback(void *handle, int mode) { struct LogContext *ctx = (struct LogContext *)handle; char buf[256], *event; struct tm tm; const char *fmode; int shout = FALSE; if (mode == 0) { ctx->state = L_ERROR; /* disable logging */ } else { fmode = (mode == 1 ? "ab" : "wb"); ctx->lgfp = f_open(ctx->currlogfilename, fmode, FALSE); if (ctx->lgfp) { ctx->state = L_OPEN; } else { ctx->state = L_ERROR; shout = TRUE; } } if (ctx->state == L_OPEN) { /* Write header line into log file. */ tm = ltime(); strftime(buf, 24, "%Y.%m.%d %H:%M:%S", &tm); logprintf(ctx, "=~=~=~=~=~=~=~=~=~=~=~= PuTTY log %s" " =~=~=~=~=~=~=~=~=~=~=~=\r\n", buf); } event = dupprintf(MPEXT_BOM "%s session log (%s mode) to file: %s", ctx->state == L_ERROR ? (mode == 0 ? "Disabled writing" : "Error writing") : (mode == 1 ? "Appending" : "Writing new"), (ctx->logtype == LGTYP_ASCII ? "ASCII" : ctx->logtype == LGTYP_DEBUG ? "raw" : ctx->logtype == LGTYP_PACKETS ? "SSH packets" : ctx->logtype == LGTYP_SSHRAW ? "SSH raw data" : "unknown"), filename_to_str(ctx->currlogfilename)); logevent(ctx->frontend, event); if (shout) { /* * If we failed to open the log file due to filesystem error * (as opposed to user action such as clicking Cancel in the * askappend box), we should log it more prominently. We do * this by sending it to the same place that stderr output * from the main session goes (so, either a console tool's * actual stderr, or a terminal window). * * Of course this is one case in which that policy won't cause * it to turn up embarrassingly in a log file of real server * output, because the whole point is that we haven't managed * to open any such log file :-) */ from_backend(ctx->frontend, 1, event, strlen(event)); from_backend(ctx->frontend, 1, "\r\n", 2); } sfree(event); /* * Having either succeeded or failed in opening the log file, * we should write any queued data out. */ assert(ctx->state != L_OPENING); /* make _sure_ it won't be requeued */ while (bufchain_size(&ctx->queue)) { void *data; int len; bufchain_prefix(&ctx->queue, &data, &len); logwrite(ctx, data, len); bufchain_consume(&ctx->queue, len); } }
int main(int argc, char **argv) { int masterr, masterw, slaver, slavew, pid; char ptyname[FILENAME_MAX]; bufchain tochild, tostdout; int tochild_active, tostdout_active, fromstdin_active, fromchild_active; int exitcode = -1; pid_t childpid = -1; --argc, ++argv; /* point at argument after "--" */ /* * Allocate the pipe for transmitting signals back to the * top-level select loop. */ if (pipe(signalpipe) < 0) { perror("pipe"); return 1; } /* * Now that pipe exists, we can set up the SIGCHLD handler to * write to one end of it. We needn't already know details like * which child pid we're waiting for, because we don't need that * until we respond to reading the far end of the pipe in the * main select loop. */ signal(SIGCHLD, sigchld); /* * Allocate the pty or pipes. */ masterr = pty_get(ptyname); masterw = dup(masterr); slaver = open(ptyname, O_RDWR); slavew = dup(slaver); if (slaver < 0) { perror("slave pty: open"); return 1; } bufchain_init(&tochild); bufchain_init(&tostdout); tochild_active = tostdout_active = TRUE; fromchild_active = fromstdin_active = TRUE; /* * Fork and execute the command. */ pid = fork(); if (pid < 0) { perror("fork"); return 1; } if (pid == 0) { int i; /* * We are the child. */ close(masterr); close(masterw); fcntl(slaver, F_SETFD, 0); /* don't close on exec */ fcntl(slavew, F_SETFD, 0); /* don't close on exec */ close(0); dup2(slaver, 0); close(1); dup2(slavew, 1); int fd; close(2); dup2(slavew, 2); setsid(); setpgid(0, 0); tcsetpgrp(0, getpgrp()); if ((fd = open("/dev/tty", O_RDWR)) >= 0) { ioctl(fd, TIOCNOTTY); close(fd); } ioctl(slavew, TIOCSCTTY); /* Close everything _else_, for tidiness. */ for (i = 3; i < 1024; i++) close(i); if (argc > 0) { execvp(argv[0], argv); /* assumes argv has trailing NULL */ } else { execl(getenv("SHELL"), getenv("SHELL"), NULL); } /* * If we're here, exec has gone badly foom. */ perror("exec"); exit(127); } /* * Now we're the parent. Close the slave fds and start copying * stuff back and forth. */ close(slaver); close(slavew); childpid = pid; tcgetattr(0, &oldattrs); newattrs = oldattrs; newattrs.c_iflag &= ~(IXON | IXOFF | ICRNL | INLCR); newattrs.c_oflag &= ~(ONLCR | OCRNL); newattrs.c_lflag &= ~(ISIG | ICANON | ECHO); atexit(attrsonexit); tcsetattr(0, TCSADRAIN, &newattrs); while (1) { fd_set rset, wset; char buf[65536]; int maxfd, ret; FD_ZERO(&rset); FD_ZERO(&wset); maxfd = 0; FD_SET(signalpipe[0], &rset); maxfd = max(signalpipe[0]+1, maxfd); if (tochild_active && bufchain_size(&tochild)) { FD_SET(masterw, &wset); maxfd = max(masterw+1, maxfd); } if (tostdout_active && bufchain_size(&tostdout)) { FD_SET(1, &wset); maxfd = max(1+1, maxfd); } if (fromstdin_active && bufchain_size(&tochild) < LOCALBUF_LIMIT) { FD_SET(0, &rset); maxfd = max(0+1, maxfd); } if (fromchild_active && bufchain_size(&tostdout) < LOCALBUF_LIMIT) { FD_SET(masterr, &rset); maxfd = max(masterr+1, maxfd); } do { ret = select(maxfd, &rset, &wset, NULL, NULL); } while (ret < 0 && (errno == EINTR || errno == EAGAIN)); if (ret < 0) { perror("select"); return 1; } if (FD_ISSET(masterr, &rset)) { if (FD_ISSET(masterr, &rset)) { ret = read(masterr, buf, sizeof(buf)); if (ret <= 0) { /* * EIO from a pty master just means end of * file, annoyingly. Why can't it report * ordinary EOF? */ if (errno == EIO) ret = 0; if (ret < 0) { perror("child process: read"); } close(masterr); fromchild_active = FALSE; ret = 0; } } else ret = 0; if (ret) { bufchain_add(&tostdout, buf, ret); } } if (FD_ISSET(0, &rset)) { if (FD_ISSET(0, &rset)) { ret = read(0, buf, sizeof(buf)); if (ret <= 0) { if (ret < 0) { perror("stdin: read"); } close(0); fromstdin_active = FALSE; ret = 0; } } else ret = 0; if (ret) { bufchain_add(&tochild, buf, ret); } } if (FD_ISSET(1, &wset)) { void *data; int len, ret; bufchain_prefix(&tostdout, &data, &len); if ((ret = write(1, data, len)) < 0) { perror("stdout: write"); close(1); close(masterr); tostdout_active = fromchild_active = FALSE; } else bufchain_consume(&tostdout, ret); } if (FD_ISSET(masterw, &wset)) { void *data; int len; bufchain_prefix(&tochild, &data, &len); if ((ret = write(masterw, data, len)) < 0) { perror("child process: write"); close(0); close(masterw); tochild_active = fromstdin_active = FALSE; } else bufchain_consume(&tochild, ret); } if (FD_ISSET(signalpipe[0], &rset)) { ret = read(signalpipe[0], buf, 1); if (ret == 1 && buf[0] == 'C') { int pid, code; pid = wait(&code); /* reap the exit code */ if (pid == childpid) exitcode = code; } } /* * If there can be no further data from a direction (the * input fd has been closed and the buffered data is used * up) but its output fd is still open, close it. */ if (!fromstdin_active && !bufchain_size(&tochild) && tochild_active) { tochild_active = FALSE; close(masterw); } if (!fromchild_active && !bufchain_size(&tostdout) && tostdout_active){ tostdout_active = FALSE; close(1); } /* * Termination condition with pipes is that there's still * data flowing in at least one direction. * * Termination condition for a pty-based run is that the * child process hasn't yet terminated and/or there is * still buffered data to send. */ if (exitcode < 0) /* process is still active */; else if (tochild_active && bufchain_size(&tochild)) /* data still to be sent to child's children */; else if (tostdout_active && bufchain_size(&tostdout)) /* data still to be sent to stdout */; else break; /* terminate */ } close(masterw); close(masterr); if (exitcode < 0) { int pid, code; pid = wait(&code); exitcode = code; } return (WIFEXITED(exitcode) ? WEXITSTATUS(exitcode) : 128 | WTERMSIG(exitcode)); }