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->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); 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 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_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; } }
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 (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; }
void log_proxy_stderr(Plug plug, bufchain *buf, const void *vdata, int len) { const char *data = (const char *)vdata; int pos = 0; int msglen; char *nlpos, *msg, *fullmsg; /* * This helper function allows us to collect the data written to a * local proxy command's standard error in whatever size chunks we * happen to get from its pipe, and whenever we have a complete * line, we pass it to plug_log. * * Prerequisites: a plug to log to, and a bufchain stored * somewhere to collect the data in. */ while (pos < len && (nlpos = memchr(data+pos, '\n', len-pos)) != NULL) { /* * Found a newline in the current input buffer. Append it to * the bufchain (which may contain a partial line from last * time). */ bufchain_add(buf, data + pos, nlpos - (data + pos)); /* * Collect the resulting line of data and pass it to plug_log. */ msglen = bufchain_size(buf); msg = snewn(msglen+1, char); bufchain_fetch(buf, msg, msglen); bufchain_consume(buf, msglen); msg[msglen] = '\0'; fullmsg = dupprintf("proxy: %s", msg); plug_log(plug, 2, NULL, 0, fullmsg, 0); sfree(fullmsg); sfree(msg); /* * Advance past the newline. */ pos += (int)(nlpos+1 - (data + pos)); } /* * Now any remaining data is a partial line, which we save for * next time. */ bufchain_add(buf, data + pos, len - pos); }
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 pty_uxsel_setup(Pty pty) { int rwx; rwx = 1; /* always want to read from pty */ if (bufchain_size(&pty->output_data)) rwx |= 2; /* might also want to write to it */ uxsel_set(pty->master_fd, rwx, pty_select_result); /* * In principle this only needs calling once for all pty * backend instances, but it's simplest just to call it every * time; uxsel won't mind. */ uxsel_set(pty_signal_pipe[0], 1, pty_select_result); }
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); } }
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 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_select_result(int fd, int event) { Local_Proxy_Socket s; char buf[20480]; int ret; if (!(s = find234(localproxy_by_fromfd, &fd, localproxy_fromfd_find)) && !(s = find234(localproxy_by_fromfd, &fd, localproxy_errfd_find)) && !(s = find234(localproxy_by_tofd, &fd, localproxy_tofd_find)) ) return 1; /* boggle */ if (event == 1) { if (fd == s->cmd_err) { ret = read(fd, buf, sizeof(buf)); if (ret > 0) log_proxy_stderr(s->plug, &s->pending_error_data, buf, ret); } else { assert(fd == s->from_cmd); ret = read(fd, buf, sizeof(buf)); if (ret < 0) { return plug_closing(s->plug, strerror(errno), errno, 0); } else if (ret == 0) { return plug_closing(s->plug, NULL, 0, 0); } else { return plug_receive(s->plug, 0, buf, ret); } } } else if (event == 2) { assert(fd == s->to_cmd); if (localproxy_try_send(s)) plug_sent(s->plug, bufchain_size(&s->pending_output_data)); return 1; } return 1; }
int handle_backlog(struct handle *h) { assert(h->type == HT_OUTPUT); return bufchain_size(&h->u.o.queued_data); }
void handle_got_event(HANDLE event) #endif { struct handle *h; assert(handles_by_evtomain); h = find234(handles_by_evtomain, &event, handle_find_evtomain); if (!h) { /* * This isn't an error condition. If two or more event * objects were signalled during the same select operation, * and processing of the first caused the second handle to * be closed, then it will sometimes happen that we receive * an event notification here for a handle which is already * deceased. In that situation we simply do nothing. */ #ifdef MPEXT return 0; #else return; #endif } if (h->u.g.moribund) { /* * A moribund handle is one which we have either already * signalled to die, or are waiting until its current I/O op * completes to do so. Either way, it's treated as already * dead from the external user's point of view, so we ignore * the actual I/O result. We just signal the thread to die if * we haven't yet done so, or destroy the handle if not. */ if (h->u.g.done) { handle_destroy(h); } else { h->u.g.done = TRUE; h->u.g.busy = TRUE; SetEvent(h->u.g.ev_from_main); } #ifdef MPEXT return 0; #else return; #endif } switch (h->type) { int backlog; case HT_INPUT: h->u.i.busy = FALSE; /* * A signal on an input handle means data has arrived. */ if (h->u.i.len == 0) { /* * EOF, or (nearly equivalently) read error. */ h->u.i.defunct = TRUE; h->u.i.gotdata(h, NULL, -h->u.i.readerr); } else { backlog = h->u.i.gotdata(h, h->u.i.buffer, h->u.i.len); handle_throttle(&h->u.i, backlog); } #ifdef MPEXT return 1; #else break; #endif case HT_OUTPUT: h->u.o.busy = FALSE; /* * A signal on an output handle means we have completed a * write. Call the callback to indicate that the output * buffer size has decreased, or to indicate an error. */ if (h->u.o.writeerr) { /* * Write error. Send a negative value to the callback, * and mark the thread as defunct (because the output * thread is terminating by now). */ h->u.o.defunct = TRUE; h->u.o.sentdata(h, -h->u.o.writeerr); } else { bufchain_consume(&h->u.o.queued_data, h->u.o.lenwritten); h->u.o.sentdata(h, bufchain_size(&h->u.o.queued_data)); handle_try_output(&h->u.o); } #ifdef MPEXT return 0; #else break; #endif case HT_FOREIGN: /* Just call the callback. */ h->u.f.callback(h->u.f.ctx); #ifdef MPEXT return 0; #else break; #endif } #ifdef MPEXT return 0; #endif }
int proxy_socks5_handlechap(Proxy_Socket p) { /* CHAP authentication reply format: * version number (1 bytes) = 1 * number of commands (1 byte) * * For each command: * command identifier (1 byte) * data length (1 byte) */ unsigned char data[260]; unsigned char outbuf[20]; while (p->chap_num_attributes == 0 || p->chap_num_attributes_processed < p->chap_num_attributes) { if (p->chap_num_attributes == 0 || p->chap_current_attribute == -1) { /* CHAP normally reads in two bytes, either at the * beginning or for each attribute/value pair. But if * we're waiting for the value's data, we might not want * to read 2 bytes. */ if (bufchain_size(&p->pending_input_data) < 2) return 1; /* not got anything yet */ /* get the response */ bufchain_fetch(&p->pending_input_data, data, 2); bufchain_consume(&p->pending_input_data, 2); } if (p->chap_num_attributes == 0) { /* If there are no attributes, this is our first msg * with the server, where we negotiate version and * number of attributes */ if (data[0] != 0x01) { plug_closing(p->plug, "Proxy error: SOCKS proxy wants" " a different CHAP version", PROXY_ERROR_GENERAL, 0); return 1; } if (data[1] == 0x00) { plug_closing(p->plug, "Proxy error: SOCKS proxy won't" " negotiate CHAP with us", PROXY_ERROR_GENERAL, 0); return 1; } p->chap_num_attributes = data[1]; } else { if (p->chap_current_attribute == -1) { /* We have to read in each attribute/value pair - * those we don't understand can be ignored, but * there are a few we'll need to handle. */ p->chap_current_attribute = data[0]; p->chap_current_datalen = data[1]; } if (bufchain_size(&p->pending_input_data) < p->chap_current_datalen) return 1; /* not got everything yet */ /* get the response */ bufchain_fetch(&p->pending_input_data, data, p->chap_current_datalen); bufchain_consume(&p->pending_input_data, p->chap_current_datalen); switch (p->chap_current_attribute) { case 0x00: /* Successful authentication */ if (data[0] == 0x00) p->state = 2; else { plug_closing(p->plug, "Proxy error: SOCKS proxy" " refused CHAP authentication", PROXY_ERROR_GENERAL, 0); return 1; } break; case 0x03: outbuf[0] = 0x01; /* Version */ outbuf[1] = 0x01; /* One attribute */ outbuf[2] = 0x04; /* Response */ outbuf[3] = 0x10; /* Length */ hmacmd5_chap(data, p->chap_current_datalen, conf_get_str(p->conf, CONF_proxy_password), &outbuf[4]); sk_write(p->sub_socket, (char *)outbuf, 20); break; case 0x11: /* Chose a protocol */ if (data[0] != 0x85) { plug_closing(p->plug, "Proxy error: Server chose " "CHAP of other than HMAC-MD5 but we " "didn't offer it!", PROXY_ERROR_GENERAL, 0); return 1; } break; } p->chap_current_attribute = -1; p->chap_num_attributes_processed++; } if (p->state == 8 && p->chap_num_attributes_processed >= p->chap_num_attributes) { p->chap_num_attributes = 0; p->chap_num_attributes_processed = 0; p->chap_current_datalen = 0; } } return 0; }
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)); }
/* * Called to query the current sendability status. */ static int serial_sendbuffer(void *handle) { Serial serial = (Serial) handle; return bufchain_size(&serial->output_data); }
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) { 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 */ }
int handle_backlog(struct handle *h) { assert(h->output); return bufchain_size(&h->u.o.queued_data); }
void handle_got_event(HANDLE event) { struct handle *h; assert(handles_by_evtomain); h = find234(handles_by_evtomain, &event, handle_find_evtomain); if (!h) { /* * This isn't an error condition. If two or more event * objects were signalled during the same select operation, * and processing of the first caused the second handle to * be closed, then it will sometimes happen that we receive * an event notification here for a handle which is already * deceased. In that situation we simply do nothing. */ return; } if (h->u.g.moribund) { /* * A moribund handle is already treated as dead from the * external user's point of view, so do nothing with the * actual event. Just signal the thread to die if * necessary, or destroy the handle if not. */ if (h->u.g.done) { handle_destroy(h); } else { h->u.g.done = TRUE; h->u.g.busy = TRUE; SetEvent(h->u.g.ev_from_main); } return; } if (!h->output) { int backlog; h->u.i.busy = FALSE; /* * A signal on an input handle means data has arrived. */ if (h->u.i.len == 0) { /* * EOF, or (nearly equivalently) read error. */ h->u.i.gotdata(h, NULL, -h->u.i.readerr); h->u.i.defunct = TRUE; } else { backlog = h->u.i.gotdata(h, h->u.i.buffer, h->u.i.len); handle_throttle(&h->u.i, backlog); } } else { h->u.o.busy = FALSE; /* * A signal on an output handle means we have completed a * write. Call the callback to indicate that the output * buffer size has decreased, or to indicate an error. */ if (h->u.o.writeerr) { /* * Write error. Send a negative value to the callback, * and mark the thread as defunct (because the output * thread is terminating by now). */ h->u.o.sentdata(h, -h->u.o.writeerr); h->u.o.defunct = TRUE; } else { bufchain_consume(&h->u.o.queued_data, h->u.o.lenwritten); h->u.o.sentdata(h, bufchain_size(&h->u.o.queued_data)); handle_try_output(&h->u.o); } } }