#include #include #include #include #include #include #include #include #include #include #include #include extern const char *__progname; #include "pp.h" #include "str.h" #include "msgs.h" #include "remote.h" #include "cmdline.h" #include "pkt-util.h" #include "pollloop.h" #include "channels.h" #include "stdio-util.h" #define BUFFERSPACE 65536 typedef struct dqe DQE; typedef struct dq DQ; struct dqe { DQE *link; char *data; int left; } ; struct dq { int fd; const char *tag; int len; DQE *head; DQE **tail; } ; static int input_ok; static DQ odq; static int oid; static DQ edq; static int eid; static int chan; static struct termios tio_i; static struct termios tio_o; static struct termios tio_n; static int exit_on_drain; static int in_nl = 0; static int in_esc = '~'; static const char *esc_txt = "~"; #define LCL_NO 1 #define LCL_ESC 2 #define LCL_CMD 3 static int localmode = LCL_NO; static unsigned char *cmdbuf = 0; static int cmdalloc = 0; static int cmdlen = 0; static void dq_init(DQ *q, int fd, const char *tag) { q->fd = fd; q->tag = tag; q->len = 0; q->head = 0; q->tail = &q->head; } static void dq_append(DQ *q, const void *buf, int len) { DQE *e; if (q->fd < 0) abort(); e = malloc(sizeof(DQE)+len); bcopy(buf,e+1,len); e->link = 0; e->data = (void *) (e+1); e->left = len; *q->tail = e; q->tail = &e->link; q->len += len; } static void dq_weof(DQ *q) { DQE *e; e = malloc(sizeof(DQE)); e->link = 0; e->data = 0; e->left = 0; *q->tail = e; q->tail = &e->link; } static void raw_tty(void) { tio_n = tio_i; tio_n.c_cc[VMIN] = 1; tio_n.c_cc[VTIME] = 0; tio_n.c_iflag &= IGNBRK | IGNPAR; tio_n.c_oflag &= ONOEOT; tio_n.c_lflag &= ~(ECHOKE|ECHOE|ECHOK|ECHO|ECHONL|ECHOPRT|ECHOCTL|ISIG|ICANON|IEXTEN); tio_n.c_lflag |= NOKERNINFO; tcsetattr(0,TCSADRAIN|TCSASOFT,&tio_n); } static void start_input(void) { fcntl(0,F_SETFL,fcntl(0,F_GETFL,0)|O_NONBLOCK); fcntl(odq.fd,F_SETFL,fcntl(odq.fd,F_GETFL,0)|O_NONBLOCK); fcntl(edq.fd,F_SETFL,fcntl(edq.fd,F_GETFL,0)|O_NONBLOCK); input_ok = 1; } static void shell_req_done(int ok) { switch (ok) { case CREQ_OK: printf("Shell request accepted\n"); raw_tty(); start_input(); break; case CREQ_FAIL: fprintf(stderr,"%s: shell request refused\n",__progname); exit(1); break; case CREQ_CLOSE: fprintf(stderr,"%s: unexpected closedown\n",__progname); exit(1); break; } } static void request_shell(void) { unsigned char *opp; opp = chan_rq_hdr(chan); opp = put_string(opp,"shell",-1); *opp++ = 1; chan_send_req(chan,opp,&shell_req_done); } static void exec_req_done(int ok) { switch (ok) { case CREQ_OK: printf("Exec request accepted\n"); start_input(); break; case CREQ_FAIL: fprintf(stderr,"%s: exec request refused\n",__progname); exit(1); break; case CREQ_CLOSE: fprintf(stderr,"%s: unexpected closedown\n",__progname); exit(1); break; } } static void request_exec(void) { unsigned char *opp; char *s; int l; FILE *f; int i; opp = chan_rq_hdr(chan); opp = put_string(opp,"exec",-1); *opp++ = 1; f = fopen_alloc(&s,&l); for (i=0;i>24)&0xff,f); putc((v>>16)&0xff,f); putc((v>> 8)&0xff,f); putc( v &0xff,f); } static void request_pty(void) { unsigned char *opp; char *term; struct winsize sz; FILE *f; char *tm; int tml; if (tcgetattr(0,&tio_i) < 0) return; if (tcgetattr(1,&tio_o) < 0) return; if (ioctl(1,TIOCGWINSZ,&sz) < 0) return; opp = chan_rq_hdr(chan); opp = put_string(opp,"pty-req",-1); *opp++ = 1; term = getenv("TERM"); opp = put_string(opp,term?:"unknown",-1); opp = put_uint32(opp,sz.ws_col); opp = put_uint32(opp,sz.ws_row); opp = put_uint32(opp,sz.ws_xpixel); opp = put_uint32(opp,sz.ws_ypixel); f = fopen_alloc(&tm,&tml); #define FOO(n) do { putc(TTY_OP_##n,f); put_32(f,tio_i.c_cc[n]); } while (0) #ifdef VINTR FOO(VINTR); #endif #ifdef VQUIT FOO(VQUIT); #endif #ifdef VERASE FOO(VERASE); #endif #ifdef VKILL FOO(VKILL); #endif #ifdef VEOF FOO(VEOF); #endif #ifdef VEOL FOO(VEOL); #endif #ifdef VEOL2 FOO(VEOL2); #endif #ifdef VSTART FOO(VSTART); #endif #ifdef VSTOP FOO(VSTOP); #endif #ifdef VSUSP FOO(VSUSP); #endif #ifdef VDSUSP FOO(VDSUSP); #endif #ifdef VREPRINT FOO(VREPRINT); #endif #ifdef VWERASE FOO(VWERASE); #endif #ifdef VLNEXT FOO(VLNEXT); #endif #ifdef VFLUSH FOO(VFLUSH); #endif #ifdef VSWTCH FOO(VSWTCH); #endif #ifdef VSTATUS FOO(VSTATUS); #endif #ifdef VDISCARD FOO(VDISCARD); #endif #undef FOO #define FOO(field,n) do { putc(TTY_OP_##n,f); put_32(f,(tio_i.field&n)?1:0); } while (0) #ifdef IGNPAR FOO(c_iflag,IGNPAR); #endif #ifdef PARMRK FOO(c_iflag,PARMRK); #endif #ifdef INPCK FOO(c_iflag,INPCK); #endif #ifdef ISTRIP FOO(c_iflag,ISTRIP); #endif #ifdef INLCR FOO(c_iflag,INLCR); #endif #ifdef IGNCR FOO(c_iflag,IGNCR); #endif #ifdef ICRNL FOO(c_iflag,ICRNL); #endif #ifdef IXON FOO(c_iflag,IXON); #endif #ifdef IXANY FOO(c_iflag,IXANY); #endif #ifdef IXOFF FOO(c_iflag,IXOFF); #endif #ifdef IMAXBEL FOO(c_iflag,IMAXBEL); #endif #ifdef ISIG FOO(c_lflag,ISIG); #endif #ifdef ICANON FOO(c_lflag,ICANON); #endif #ifdef ECHO FOO(c_lflag,ECHO); #endif #ifdef ECHOE FOO(c_lflag,ECHOE); #endif #ifdef ECHONL FOO(c_lflag,ECHONL); #endif #ifdef NOFLSH FOO(c_lflag,NOFLSH); #endif #ifdef IEXTEN FOO(c_lflag,IEXTEN); #endif #ifdef ECHOCTL FOO(c_lflag,ECHOCTL); #endif #ifdef ECHOKE FOO(c_lflag,ECHOKE); #endif #ifdef ECHOK FOO(c_lflag,ECHOK); #endif #undef FOO #define FOO(field,n) do { putc(TTY_OP_##n,f); put_32(f,(tio_o.field&n)?1:0); } while (0) #ifdef TOSTOP FOO(c_lflag,TOSTOP); #endif #ifdef OPOST FOO(c_oflag,OPOST); #endif #ifdef ONLCR FOO(c_oflag,ONLCR); #endif #ifdef OCRNL FOO(c_oflag,OCRNL); #endif #ifdef ONOCR FOO(c_oflag,ONOCR); #endif #ifdef ONLRET FOO(c_oflag,ONLRET); #endif /* Using tio_i here is arguable. Maybe warn if tio_i and tio_o differ? */ switch (tio_i.c_cflag & CSIZE) { case CS5: fprintf(stderr,"%s: warning: CS5 not supported by the protocol\n",__progname); break; case CS6: fprintf(stderr,"%s: warning: CS6 not supported by the protocol\n",__progname); break; case CS7: putc(TTY_OP_CS7,f); put_32(f,1); break; case CS8: putc(TTY_OP_CS8,f); put_32(f,1); break; } #ifdef PARENB FOO(c_cflag,PARENB); #endif #ifdef PARODD FOO(c_cflag,PARODD); #endif /* No protocol field for VLNEXT IGNBRK BRKINT OXTABS ONOEOT CSTOPB CREAD HUPCL CLOCAL CRTSCTS CDTRCTS MDMBUF ECHOPRT ALTWERASE EXTPROC FLUSHO NOKERNINFO PENDIN. Most of these shouldn't be passed anyway. */ /* Don't know the termios field for protocol values IUCLC XCASE OLCUC. */ /* PENDIN isn't passed because it has no business being passed (despite having a protocol value for some inexplicable reason). */ #undef FOO putc(TTY_OP_ISPEED,f); put_32(f,cfgetispeed(&tio_i)); putc(TTY_OP_OSPEED,f); put_32(f,cfgetospeed(&tio_o)); putc(TTY_OP_END,f); fclose(f); opp = put_string(opp,tm,tml); chan_send_req(chan,opp,&pty_req_done); } static void rem_opendone(int ch, int status) { if (ch != chan) abort(); if (status) { fprintf(stderr,"%s: channel open failed %d\n",__progname,status); exit(1); } printf("Channel opened\n"); /* XXX request X11 forwarding */ /* XXX pass environment */ if (cmdbegin) { request_exec(); } else { request_pty(); /* We want a shell whether the above succeeded or not */ request_shell(); } chan_add_rwin(chan,BUFFERSPACE); } static void rem_gotdata(int cno, int ext, unsigned int code, const void *buf, int len) { DQ *q; if (cno != chan) abort(); if (len == 0) { dq_weof(&odq); dq_weof(&edq); return; } if (ext) { switch (code) { case SSH_EXTENDED_DATA_STDERR: q = &edq; break; default: fprintf(stderr,"%s: protocol error: extended data code %u\n",__progname,code); exit(1); break; } } else { q = &odq; } dq_append(q,buf,len); } static int rtest_i(int id __attribute__((__unused__)), void *arg __attribute__((__unused__))) { return(input_ok&&(chan_get_wwin(chan)>0)); } static int wtest_o(int id __attribute__((__unused__)), void *arg) { DQ *q; q = arg; return((q->fd >= 0) && (q->head != 0)); } static void rd_in_pipe(int id __attribute__((__unused__)), void *arg __attribute__((__unused__))) { char buf[512]; int n; int r; n = chan_get_wwin(chan); if (n > sizeof(buf)) n = sizeof(buf); r = read(0,&buf[0],n); if (r < 0) { switch (errno) { case EINTR: case EWOULDBLOCK: return; break; } fprintf(stderr,"%s: stdin read error: %s\n",__progname,strerror(errno)); exit(1); } if (r == 0) { chan_send_eof(chan); input_ok = 0; } else { chan_send_data(chan,0,0,&buf[0],r); } } static void local_command(void) { unsigned char *cp; unsigned char *ep; cp = cmdbuf; ep = cmdbuf + cmdlen; while ((cp < ep) && isspace(*cp)) cp ++; if (cp < ep) { printf("local commands not yet implemented\r\n"); } printf("%s> ",__progname); } static int print_len(int c) { if (c < 32) return(2); if (c < 127) return(1); if (c < 160) return(2); return(1); } static void print_out(FILE *f, int c) { if (c < 32) { fprintf(f,"^%c",c+'@'); } else if (c < 127) { putc(c,f); } else if (c == 127) { fprintf(f,"^?"); } else if (c < 160) { fprintf(f,"^%c",in_esc+'À'-128); } else { putc(c,f); } } static void cmd_input(int c) { int i; switch (c) { case '\r': case '\n': printf("\r\n"); local_command(); break; case '\b': case '\177': if (cmdlen > 0) { cmdlen --; for (i=print_len(cmdbuf[cmdlen]);i>0;i--) printf("\b \b"); } break; case 21: /* ^U */ case 24: /* ^X */ while (cmdlen > 0) { cmdlen --; for (i=print_len(cmdbuf[cmdlen]);i>0;i--) printf("\b \b"); } break; default: if ((c < 32) || ((c >= 128) && (c < 160))) { printf("\a"); break; } if (cmdlen >= cmdalloc) cmdbuf = realloc(cmdbuf,cmdalloc=cmdlen+16); cmdbuf[cmdlen++] = c; print_out(stdout,c); break; } } static void rd_in_tty(int id __attribute__((__unused__)), void *arg __attribute__((__unused__))) { unsigned char buf[512]; int n; int r; n = chan_get_wwin(chan); if (n > sizeof(buf)) n = sizeof(buf); r = read(0,&buf[0],n); if (r < 0) { switch (errno) { case EINTR: case EWOULDBLOCK: return; break; } fprintf(stderr,"%s: stdin read error: %s\n",__progname,strerror(errno)); exit(1); } if (r == 0) { fprintf(stderr,"%s: stdin read EOF?""?\n",__progname); } else { int i; int i0; i0 = -1; for <"inputloop"> (i=0;i i0) chan_send_data(chan,0,0,&buf[i0],i-i0); i0 = -1; localmode = LCL_ESC; cmdlen = 0; } break; } break; case LCL_ESC: if (buf[i] == in_esc) { localmode = LCL_NO; i0 = i; break; } switch (buf[i]) { case '.': chan_send_eof(chan); input_ok = 0; break <"inputloop">; case '?': printf("%s?\r\n",esc_txt); printf("%s%-4s- send %s\r\n",esc_txt,esc_txt,esc_txt); if (in_esc != '.') printf("%s. - end connection\r\n",esc_txt); if (in_esc != '?') printf("%s? - print this message\r\n",esc_txt); if (in_esc != '%') printf("%s%% - local command line\r\n",esc_txt); localmode = LCL_NO; break; case '%': localmode = LCL_CMD; cmdlen = 0; printf("\r\n%s> ",__progname); break; } break; case LCL_CMD: cmd_input(buf[i]); break; } } if (i0 >= 0) { if (i == i0) abort(); chan_send_data(chan,0,0,&buf[i0],i-i0); } } } static void exit_if_drained(void) { if (!odq.head && !edq.head) exit(0); } static void wr_out(int id __attribute__((__unused__)), void *arg) { static int maxiov = -1; static struct iovec *iov; static int iovn; int iovl; DQ *q; DQE *e; int w; q = arg; if (maxiov < 0) { int mib[2]; size_t oldlen; mib[0] = CTL_KERN; mib[1] = KERN_IOV_MAX; oldlen = sizeof(maxiov); if (sysctl(&mib[0],2,&maxiov,&oldlen,0,0) < 0) { fprintf(stderr,"%s: can't get kern.iov_max: %s\n",__progname,strerror(errno)); exit(1); } if (maxiov > 64) maxiov = 64; if (maxiov < 1) maxiov = 1; iov = 0; iovn = 0; } iovl = 0; for (e=q->head;e;e=e->link) { if (iovl >= maxiov) break; if (e->data == 0) { if (iovl > 0) break; close(q->fd); q->fd = -1; if (e->link) abort(); free(e); q->head = 0; q->tail = &q->head; if (exit_on_drain) exit_if_drained(); return; } if (iovl >= iovn) iov = realloc(iov,(iovn=iovl+4)*sizeof(*iov)); iov[iovl++] = (struct iovec){ .iov_base=e->data, .iov_len=e->left }; } w = writev(q->fd,iov,iovl); if (w < 0) { switch (errno) { case EINTR: case EWOULDBLOCK: return; break; } fprintf(stderr,"%s: %s write: %s\n",__progname,q->tag,strerror(errno)); exit(1); } if (w == 0) { fprintf(stderr,"%s: zero %s write\n",__progname,q->tag); exit(1); } while ((e=q->head) && (w >= e->left)) { q->head = e->link; w -= e->left; q->len -= e->left; free(e); } if (e) { if (w) { e->data += w; e->left -= w; q->len -= w; } } else { q->tail = &q->head; if (q->len != 0) abort(); if (exit_on_drain) exit_if_drained(); } } static void rem_morewin(int cno, unsigned int addl, unsigned int window) { if (cno != chan) abort(); printf("window: +%u -> %u\n",addl,window); } static int rem_chanreq(int cno, ROSTR req, int wantrep __attribute__((__unused__)), const void *rest, int restlen) { static void failure(const void *at __attribute__((__unused__)), const char *fmt, ...) { va_list ap; fprintf(stderr,"%s: protocol error: unparseable `%.*s': ",__progname,req.len,req.data); va_start(ap,fmt); vfprintf(stderr,fmt,ap); va_end(ap); fprintf(stderr,"\n"); exit(1); } if (cno != chan) abort(); if (str_equalcC(req,"exit-status")) { parse_data(rest,restlen,&failure, PP_SKIP(PP_UINT32(0)), PP_ENDSHERE ); return(CHANREQRET_OK); } else if (str_equalcC(req,"exit-signal")) { parse_data(rest,restlen,&failure, PP_SKIP(PP_STRING(0)), PP_SKIP(PP_BOOL(0)), PP_SKIP(PP_STRING(0)), PP_SKIP(PP_STRING(0)), PP_ENDSHERE ); return(CHANREQRET_OK); } return(CHANREQRET_UNK); } static void rem_closed(int cno, int final) { if (cno != chan) abort(); if (!final && !chan_close(chan)) { fprintf(stderr,"%s: can't do final channel teardown?\n",__progname); exit(1); } input_ok = 0; exit_on_drain = 1; exit_if_drained(); } static const CHANOPS rl_ops = { &rem_opendone, &rem_gotdata, &rem_morewin, &rem_chanreq, &rem_closed }; static void setup_esc(void) { char *t; if (! escchar) return; if (!strcmp(escchar,"none")) { in_esc = -1; return; } if (escchar[0] && !escchar[1]) { in_esc = ((const unsigned char *)escchar)[0]; } else if ((escchar[0] == '^') && escchar[1] && !escchar[2]) { in_esc = (escchar[1] == '?') ? '\177' : (escchar[1]&31); } else if ( (tolower(escchar[0]) == 'c') && (escchar[1] == '-') && escchar[2] && !escchar[3] ) { in_esc = (escchar[2] == '?') ? '\177' : (escchar[2]&31); } else if ( (tolower(escchar[0]) == 'm') && (escchar[1] == '-') && escchar[2] && !escchar[3] ) { in_esc = escchar[2] | 0x80; } else if ( ( ( (tolower(escchar[0]) == 'm') && (escchar[1] == '-') && (tolower(escchar[2]) == 'c') ) || ( (tolower(escchar[0]) == 'c') && (escchar[1] == '-') && (tolower(escchar[2]) == 'm') ) ) && (escchar[3] == '-') && escchar[4] && !escchar[5] ) { in_esc = (escchar[4] == '?') ? '\377' : ((escchar[2]&31)|0x80); } else { fprintf(stderr,"%s: can't parse -esc option `%s'\n",__progname,escchar); exit(1); } if (in_esc < 32) { asprintf(&t,"^%c",in_esc+'@'); } else if (in_esc < 127) { asprintf(&t,"%c",in_esc); } else if (in_esc == 127) { asprintf(&t,"^?"); } else if (in_esc < 160) { asprintf(&t,"^%c",in_esc+'À'-128); } else { asprintf(&t,"%c",in_esc); } esc_txt = t; } void start_remote_session(void) { input_ok = 0; if (cmdbegin) { dq_init(&odq,1,"stdout"); dq_init(&edq,2,"stderr"); add_poll_fd(0,rtest_i,rwtest_never,rd_in_pipe,0,0); } else { int ofd; int efd; ofd = dup(1); efd = dup(2); dq_init(&odq,ofd,"stdout"); dq_init(&edq,efd,"stderr"); setup_esc(); add_poll_fd(0,rtest_i,rwtest_never,rd_in_tty,0,0); } oid = add_poll_fd(odq.fd,rwtest_never,wtest_o,0,wr_out,&odq); eid = add_poll_fd(edq.fd,rwtest_never,wtest_o,0,wr_out,&edq); chan = chan_open(CHANTYPE_SESSION,&rl_ops); exit_on_drain = 0; }