/* This file is in the public domain. */ #include #include #include #include #include #include #include #include #include #include #include #include #include /* Fsck. , which is hauled in by , defines a struct session, even when _KERNEL isn't defined. :-þ */ #define session session_ #include #undef session #include /* * Length of the random part of our synthetic X cookies, used for * broken X forwarding. This value is in bytes *before* hex encoding. */ #define BXCOOKIE_RAND 16 extern const char *__progname; #include "x.h" #include "pp.h" #include "oq.h" #include "bpp.h" #include "str.h" #include "rnd.h" #include "dll.h" #include "msgs.h" #include "util.h" #include "errf.h" #include "agent.h" #include "panic.h" #include "config.h" #include "nested.h" #include "escaped.h" #include "channels.h" #include "pkt-util.h" #include "pollloop.h" #include "channels.h" #include "keepalive.h" #include "connshare.h" #include "transport.h" #include "stdio-util.h" #include "withscopeid.h" #include "agent-client.h" #include "userauth-conn.h" #include "client.h" #define BUFFERSPACE 65536 enum rtol_state { RFS_START = 1, /* requested, no response yet */ RFS_LIVE, /* normal operation */ RFS_STOP, /* cancellation requested, no response yet */ RFS_BROKEN, /* cancellation refused(!) */ RFS_DEAD, /* waiting for refs to vanish before freeing */ RFS_BLIP /* requested, no response yet, but canceled */ } ; enum cfa_state { CFAS_IDLE = 1, /* between packets */ CFAS_HEADER, /* awaiting packet header */ CFAS_NOTICE, /* copying body of FORWARDING_NOTICE packet */ CFAS_BODY, /* copying body of non-FORWARDING_NOTICE packet */ CFAS_DEAD /* being shut down */ } ; enum cfc_type { CFCT_UNSET = 1, /* not yet set */ CFCT_TCP, /* TCP forwarding */ CFCT_X /* X11 forwarding */ } ; enum cs_state { SS__NIL = 1, /* nascent, not yet set up */ SS_OPEN, /* awaiting setup response */ SS_UP, /* running normally */ SS_FLUSH, /* flushing output before dying */ SS_DEAD /* closed */ } ; enum cfx_method { CFXM_TCP = 1, /* via TCP */ CFXM_LOCAL, /* via /tmp/.X11-unix/ */ CFXM_OTHER /* via something else */ } ; enum localmode { LCL_NO = 1, /* normal communication */ LCL_ESC, /* esc char typed */ LCL_CMD /* local command prompt */ } ; enum rp_state { RP_NONE = 1, /* no reprompting */ RP_DRAIN, /* print prompt after output drain */ RP_PROMPT /* set PENDIN after output drain */ } ; typedef enum rtol_state RTOL_STATE; typedef enum cfa_state CFA_STATE; typedef enum cfc_type CFC_TYPE; typedef enum cs_state CS_STATE; typedef enum cfx_method CFX_METHOD; typedef enum localmode LOCALMODE; typedef enum rp_state RP_STATE; typedef struct dq DQ; typedef struct cmd CMD; typedef struct cfwdreq CFWDREQ; typedef struct cfwdlfd CFWDLFD; typedef struct cfwdconn CFWDCONN; typedef struct cipstate CIPSTATE; typedef struct csession CSESSION; typedef struct cfwdagent CFWDAGENT; typedef struct ops OPS; struct ops { int shared; __typeof__(&chan_open_hdr) open_hdr; __typeof__(&chan_open_send) open_send; __typeof__(&chan_open_ok) open_ok; __typeof__(&chan_open_fail) open_fail; __typeof__(&chan_set_ops) set_ops; __typeof__(&chan_get_rwin) get_rwin; __typeof__(&chan_add_rwin) add_rwin; __typeof__(&chan_get_wwin) get_wwin; __typeof__(&chan_req_hdr) req_hdr; __typeof__(&chan_send_req_reply) send_req_reply; __typeof__(&chan_send_req_blind) send_req_blind; __typeof__(&chan_send_data) send_data; __typeof__(&chan_send_eof) send_eof; __typeof__(&chan_close) close; __typeof__(&set_globalops) setgbl; __typeof__(&global_req_hdr) g_req_hdr; __typeof__(&global_send_req) g_send_req; } ; struct cfwdagent { CFWDAGENT *flink; CFWDAGENT *blink; void *ah; int id; int chan; int advwin; CFA_STATE state; OQ oq; unsigned char hdr[5]; int fill; int bodyleft; } ; struct cipstate { CFWDCONN *c; struct addrinfo *ai0; struct addrinfo *ai; int sock; int id; } ; struct cfwdlfd { CFWDLFD *flink; CFWDLFD *blink; CFWDREQ *req; int fd; int id; char *text; } ; struct cfwdreq { CFWDREQ *flink; CFWDREQ *blink; CSESSION *s; int refs; int dir; unsigned int serial; char *listenhost; unsigned int listenport; char *connhost; unsigned int connport; char *text; union { struct { CFWDLFD *lfds; } ltor; struct { RTOL_STATE state; unsigned int flags; #define RFF_BROKEN 0x00000001 #define RFF_MUST 0x00000002 } rtol; } ; } ; struct cfwdconn { CFWDCONN *flink; CFWDCONN *blink; CFC_TYPE type; union { struct { CFWDREQ *req; char *clientaddr; unsigned int clientport; } tcp; struct { CFX_METHOD method; union { struct { char *clientaddr; unsigned int clientport; } tcp; struct { char *method; } other; } ; unsigned char *chdr; int chlen; int chptr; CSESSION *session; } x; } ; unsigned int serial; int fd; int chan; int id; int advwin; int leof; int reof; OQ oq; } ; struct cmd { const char *cmd; void (*handler)(unsigned char *, int); } ; struct dq { int fd; const char *tag; OQ q; } ; /* * See the comment header on check_pend for more on the SF_xPND bits. */ struct csession { CSESSION *flink; CSESSION *blink; int refs; char *cmd; unsigned int serial; unsigned int xfwd_id; unsigned int afwd_id; CS_STATE state; unsigned int flags; #define SF_IEOF 0x00000001 /* read EOF local->remote */ #define SF_OEOF 0x00000002 /* read EOF remote->local */ #define SF_SUSP 0x00000004 /* suspended */ #define SF_AFWD 0x00000008 /* agent forwarding requested */ #define SF_BAF 0x00000010 /* broken agent forwarding requested */ #define SF_AFJO 0x00000020 /* Agent forwarding is just-once-style */ #define SF_AUSE 0x00000040 /* Agent forwarding has occurred */ #define SF_APND 0x00000080 /* Agent forwarding pending */ #define SF_XFWD 0x00000100 /* X forwarding requested */ #define SF_BXF 0x00000200 /* broken X forwarding requested */ #define SF_XFJO 0x00000400 /* X forwarding is just-once style */ #define SF_XUSE 0x00000800 /* X forwarding has occurred */ #define SF_XPND 0x00001000 /* X forwarding pending */ #define SF_CPND 0x00002000 /* command execution pending */ int autobg_id; int flush_id; DQ odq; DQ edq; int chan; int advwin; CFWDREQ *fwdreqs; void (*opendone)(CSESSION *); CFXREQ *xfreq; unsigned char *bxcookie; int bxclen; } ; static int using_tty; static int raw_tty; static unsigned int saved = 0; #define SAVE_TTY 0x00000001 #define SAVE_BLOCK 0x00000002 static struct termios tio_i; static struct termios tio_o; static struct termios tio_i_raw; static struct winsize wsz; static int nonblock_i; static int nonblock_o; static int nonblock_e; static DQ codq; static DQ cedq; static int ocount; static int ecount; static FILE *opf; static FILE *epf; static int iid; static int oid; static int eid; static int bfid; static int in_nl = 0; static int in_esc; static const char *esc_txt; static int in_suspc = -1; static char *suspc_txt = 0; static LOCALMODE localmode = LCL_NO; static RP_STATE reprompt_state = RP_NONE; static int reprompt_id; static unsigned char *cmdbuf = 0; static int cmdalloc = 0; static int cmdlen = 0; static unsigned int conn_serial = 0; static unsigned int session_serial = 0; static unsigned int fwdreq_serial = 0; static int winchpipe[2]; static CSESSION *csessions; static CSESSION *cursession; static BPP *bpp; static PEERID rem_id; static CFWDCONN *cfwdconns; static CFWDAGENT *cfwdagents; static const OPS *ops; static void (*when_session_up)(void) = 0; static const OPS ops_nonshared = { 0, &chan_open_hdr, &chan_open_send, &chan_open_ok, &chan_open_fail, &chan_set_ops, &chan_get_rwin, &chan_add_rwin, &chan_get_wwin, &chan_req_hdr, &chan_send_req_reply, &chan_send_req_blind, &chan_send_data, &chan_send_eof, &chan_close, &set_globalops, &global_req_hdr, &global_send_req }; static const OPS ops_shared = { 1, &share_chan_open_hdr, &share_chan_open_send, &share_chan_open_ok, &share_chan_open_fail, &share_chan_set_ops, &share_chan_get_rwin, &share_chan_add_rwin, &share_chan_get_wwin, &share_chan_req_hdr, &share_chan_send_req_reply, &share_chan_send_req_blind, &share_chan_send_data, &share_chan_send_eof, &share_chan_close, &share_set_globalops, &share_global_req_hdr, &share_global_send_req }; static void openconn(BPP *b) { struct addrinfo hints; struct addrinfo *ai0; struct addrinfo *ai; int e; int se; int fd; int px; const char *h; const char *p; h = config_str("connect-to"); if (! h) h = config_str("host"); if (! h) { fprintf(errf,"%s: need a host to connect to\n",__progname); exit(1); } if (nports < 1) { ports = malloc(sizeof(*ports)); ports[0] = 0; nports = 1; } se = 0; for (px=0;pxai_next) { char hnbuf[NI_MAXHOST]; char pnbuf[NI_MAXSERV]; int opt; if (getnameinfo(ai->ai_addr,ai->ai_addrlen,&hnbuf[0],NI_MAXHOST,&pnbuf[0],NI_MAXSERV,NI_NUMERICHOST|NI_NUMERICSERV|NI_WITHSCOPEID)) { fprintf(errf,"%s: %s%s%s: can't get numeric hostname info [%s]\n",__progname,h,ports[px]?"/":"",ports[px]?:"",strerror(errno)); continue; } fd = socket(ai->ai_family,ai->ai_socktype,ai->ai_protocol); if (fd < 0) { if (se == 0) se = errno; continue; } se = -1; if (connect(fd,ai->ai_addr,ai->ai_addrlen) < 0) { if (ai0->ai_next) { fprintf(errf,"%s: connect %s [%s/%s]: %s\n",__progname,h,&hnbuf[0],&pnbuf[0],strerror(errno)); } else { fprintf(errf,"%s: connect %s/%s: %s\n",__progname,&hnbuf[0],&pnbuf[0],strerror(errno)); } close(fd); continue; } fcntl(fd,F_SETFL,fcntl(fd,F_GETFL,0)|O_NONBLOCK); opt = 1; if (config_bool("net-keepalive")) setsockopt(fd,SOL_SOCKET,SO_KEEPALIVE,&opt,sizeof(opt)); b->fd = fd; asprintf(&b->peer_text,"%s/%s",&hnbuf[0],&pnbuf[0]); rem_id.name = strdup(h); rem_id.ip = strdup(&hnbuf[0]); rem_id.port = atoi(&pnbuf[0]); config_set_addr("remote-ip",ai->ai_addr); config_set_int("remote-port",rem_id.port); if (config_needs("local-ip") || config_needs("local-port")) { struct sockaddr_storage ss; socklen_t sslen; sslen = sizeof(ss); if (getsockname(fd,(struct sockaddr *)&ss,&sslen) < 0) { fprintf(errf,"%s: getsockname: %s\n",__progname,strerror(errno)); exit(1); } if (getnameinfo((const void *)&ss,sslen,&hnbuf[0],NI_MAXHOST,&pnbuf[0],NI_MAXSERV,NI_NUMERICHOST|NI_NUMERICSERV|NI_WITHSCOPEID)) { fprintf(errf,"%s: %s%s%s: can't get sockname numeric info [%s]\n",__progname,h,ports[px]?"/":"",ports[px]?:"",strerror(errno)); exit(1); } config_set_addr("local-ip",&ss); config_set_int("local-port",atoi(&pnbuf[0])); } freeaddrinfo(ai0); return; } freeaddrinfo(ai0); } if (se > 0) { fprintf(errf,"%s: socket: %s\n",__progname,strerror(se)); } else if (se == 0) { fprintf(errf,"%s: can't connect, can't tell why\n",__progname); } exit(1); } /* XXX this needs fixing! */ static int listenhost_match(const char *listenon, STR actual) { if (!listenon[0] || !strcmp(listenon,"::") || !strcmp(listenon,"0.0.0.0")) return(1); if (str_equalsC(actual,listenon)) return(1); return(0); } static CFWDCONN *nascent_cfwdconn(void) { CFWDCONN *c; c = malloc(sizeof(CFWDCONN)); c->serial = ++conn_serial; c->leof = 0; c->reof = 0; oq_init(&c->oq); /* The rest are actually our caller's responsibility */ c->type = CFCT_UNSET; c->fd = -1; c->chan = -1; c->flink = 0; c->blink = 0; return(c); } static void csession_ref(CSESSION *s) { s->refs ++; } static void csession_deref(CSESSION *s) { s->refs --; if (s->refs < 0) panic("negative refs"); if (s->refs == 0) { if (s->state != SS_DEAD) panic("csession not dead"); if (s->fwdreqs) panic("fwdreqs present"); free(s->cmd); if (s->xfreq) X_forward_done(s->xfreq); oq_flush(&s->odq.q); oq_flush(&s->edq.q); } } static void cfwdreq_ref(CFWDREQ *r) { r->refs ++; } static void cfwdreq_deref(CFWDREQ *r) { r->refs --; if (r->refs < 0) panic("negative refs"); if (r->refs == 0) { switch (r->dir) { case FF_DIR_LTOR: if (r->ltor.lfds) panic("lfds present"); break; case FF_DIR_RTOL: /* XXX should cancel it if START or LIVE */ break; default: panic("bad direction"); break; } csession_deref(r->s); free(r->listenhost); free(r->connhost); free(r->text); free(r); } } static void oprf(const char *, ...) __attribute__((__format__(__printf__,1,2))); static void oprf(const char *fmt, ...) { va_list ap; if (! using_tty) panic("no tty"); va_start(ap,fmt); vfprintf(opf,fmt,ap); va_end(ap); } static void eprf(const char *, ...) __attribute__((__format__(__printf__,1,2))); static void eprf(const char *fmt, ...) { va_list ap; va_start(ap,fmt); vfprintf(using_tty?epf:stderr,fmt,ap); va_end(ap); } static const char *cfwdconn_parentext(CFWDCONN *c) { switch (c->type) { case CFCT_UNSET: return("nascent"); break; case CFCT_TCP: return(c->tcp.req->text); break; case CFCT_X: return("X"); break; } panic("bad type"); } static void close_and_free_fwdconn_c(CFWDCONN *c) { if (VERB(PROGRESS)) verb(PROGRESS,"[close_and_free_fwdconn_c for %d (%s)]\r\n",c->fd,cfwdconn_parentext(c)); if (c->fd >= 0) close(c->fd); DLL_UNLINK(c,cfwdconns); oq_flush(&c->oq); switch (c->type) { case CFCT_UNSET: break; case CFCT_TCP: cfwdreq_deref(c->tcp.req); free(c->tcp.clientaddr); break; case CFCT_X: switch (c->x.method) { case CFXM_TCP: free(c->x.tcp.clientaddr); break; case CFXM_LOCAL: break; case CFXM_OTHER: free(c->x.other.method); break; default: panic("impossible X method in close_and_free_fwdconn_c"); break; } if (c->x.chdr) free(c->x.chdr); if (c->x.session) csession_deref(c->x.session); break; } free(c); } static void cfwd_maybe_close(CFWDCONN *c) { if (c->leof && c->reof && oq_empty(&c->oq)) { (*ops->close)(c->chan); if (VERB(PROGRESS)) verb(PROGRESS,"[cfwd_maybe_close closing %d (%s)]\r\n",c->fd,cfwdconn_parentext(c)); } } static void cfwd_force_teardown(CFWDCONN *c) { c->leof = 1; c->reof = 1; (*ops->close)(c->chan); if (VERB(PROGRESS)) verb(PROGRESS,"[cfwd_teardown %d (%s)]\r\n",c->fd,cfwdconn_parentext(c)); } static int rtest_cfwd(int id __attribute__((__unused__)), void *cv) { CFWDCONN *c; c = cv; return(!c->leof && ((*ops->get_wwin)(c->chan) > 0)); } static int wtest_cfwd(int id __attribute__((__unused__)), void *cv) { return(oq_nonempty(&((CFWDCONN *)cv)->oq)); } static void rd_cfwd(int id __attribute__((__unused__)), void *cv) { CFWDCONN *c; int n; int r; char buf[8192]; c = cv; if (c->leof) return; n = (*ops->get_wwin)(c->chan); if (n < 1) return; if (n > sizeof(buf)) n = sizeof(buf); r = read(c->fd,&buf[0],n); { int e; e = errno; if (VERB(FWDDATA)) { verb(FWDDATA,"[rd_cfwd %d (%s): read %d",c->fd,cfwdconn_parentext(c),r); if (r < 0) verb(FWDDATA," (errno=%d)",e); verb(FWDDATA,"]\r\n"); } errno = e; } if (r < 0) { switch (errno) { case EINTR: case EWOULDBLOCK: return; break; } eprf("%s: local forwarding %s: read error: %s\r\n",__progname,cfwdconn_parentext(c),strerror(errno)); } if (r <= 0) { c->leof = 1; shutdown(c->fd,SHUT_RD); (*ops->send_eof)(c->chan); cfwd_maybe_close(c); return; } (*ops->send_data)(c->chan,0,0,&buf[0],r); } static void wr_cfwd(int id __attribute__((__unused__)), void *cv) { CFWDCONN *c; int w; int goteof; int n; NESTED int ateof(void *vp __attribute__((__unused__)), int i __attribute__((__unused__))) { goteof = 1; return(0); } c = cv; goteof = 0; w = oq_writev(&c->oq,c->fd,&ateof); if (goteof) { if (VERB(FWDDATA)) verb(FWDDATA,"[wr_cfwd %d (%s): goteof]\r\n",c->fd,cfwdconn_parentext(c)); shutdown(c->fd,SHUT_WR); cfwd_maybe_close(c); return; } if (w < 0) { switch (errno) { case EINTR: case EWOULDBLOCK: return; break; } eprf("%s: local forwarding %s: write error: %s\r\n",__progname,cfwdconn_parentext(c),strerror(errno)); cfwd_force_teardown(c); } if (VERB(FWDDATA)) verb(FWDDATA,"[wr_cfwd %d (%s): wrote %d]\r\n",c->fd,cfwdconn_parentext(c),w); oq_dropdata(&c->oq,w); if (! c->reof) { n = c->advwin + oq_qlen(&c->oq); if (n < BUFFERSPACE/2) { (*ops->add_rwin)(c->chan,BUFFERSPACE-n); c->advwin += BUFFERSPACE-n; } } } static void cfwd_up(CFWDCONN *c) { c->id = add_poll_fd(c->fd,&rtest_cfwd,&wtest_cfwd,&rd_cfwd,&wr_cfwd,c); (*ops->add_rwin)(c->chan,BUFFERSPACE); c->advwin = BUFFERSPACE; } static void restore_tty(void) { if (saved & SAVE_TTY) { /* could restore stdout, but we never explicitly changed it, so if it was changed it's 'cause it's the same as stdin, in which case restoring stdin will restore it too. */ tcsetattr(0,TCSADRAIN|TCSASOFT,&tio_i); raw_tty = 0; } } static void restore_blocking(void) { if (saved & SAVE_BLOCK) { if (! nonblock_i) fcntl(0,F_SETFL,fcntl(0,F_GETFL,0)&~O_NONBLOCK); if (! nonblock_o) fcntl(1,F_SETFL,fcntl(1,F_GETFL,0)&~O_NONBLOCK); if (! nonblock_e) fcntl(2,F_SETFL,fcntl(2,F_GETFL,0)&~O_NONBLOCK); } } static void die(int ecode) { restore_tty(); restore_blocking(); exit(ecode); } static void cfwd_opensucc(void *cv, int ch, ROSTR rest) { CFWDCONN *c; c = cv; if (ch != c->chan) panic("channel wrong"); if (rest.len) { fprintf(errf,"%s: protocol error: data (len=%d) in open confirmation\r\n",__progname,rest.len); die(1); } cfwd_up(c); } static void cfwd_openfail(void *cv, int ch, unsigned int rcode, ROSTR reason, ROSTR lang __attribute__((__unused__))) { CFWDCONN *c; c = cv; if (ch != c->chan) panic("channel wrong"); eprf("%s: local forwarding %s: channel open refused (",__progname,cfwdconn_parentext(c)); print_openfail_rcode(epf,rcode); eprf(")"); if (reason.len) { eprf(": "); print_escaped(epf,reason.data,reason.len,1024); } eprf("\r\n"); close_and_free_fwdconn_c(c); } static void cfwd_gotdata(void *cv, int cno, int ext __attribute__((__unused__)), unsigned int code __attribute__((__unused__)), const void *buf, int len) { CFWDCONN *c; c = cv; if (cno != c->chan) panic("channel wrong"); if (VERB(FWDDATA)) verb(FWDDATA,"[cfwd_gotdata %d (%s): gotdata %d]\r\n",c->fd,cfwdconn_parentext(c),len); if (len == 0) { oq_queue_special(&c->oq,0,0); c->reof = 1; return; } oq_queue_copy(&c->oq,buf,len); c->advwin -= len; } static void cfwd_closed(void *cv, int cno, int final) { CFWDCONN *c; c = cv; if (cno != c->chan) panic("channel wrong"); if (VERB(PROGRESS)) verb(PROGRESS,"[cfwd_closed %d (%s): final=%d]\r\n",c->fd,cfwdconn_parentext(c),final); if (!final && !(*ops->close)(cno)) panic("close failed"); if (c->id != PL_NOID) remove_poll_id(c->id); close_and_free_fwdconn_c(c); } static const CHANOPS cfwd_ops = { &cfwd_opensucc, &cfwd_openfail, &cfwd_gotdata, 0, 0, &cfwd_closed }; static void cfwd_connect_fail(CFWDCONN *c) { (*ops->open_fail)(c->chan,SSH_OPEN_CONNECT_FAILED,cstr_to_rostr(""),cstr_to_rostr("")); c->fd = -1; close_and_free_fwdconn_c(c); } static void cfwd_connect_try(CIPSTATE *); /* forward */ static void cfwd_connect_ok(CFWDCONN *c, int sock) { c->fd = sock; (*ops->open_ok)(c->chan,cstr_to_rostr(""),&cfwd_ops,c); cfwd_up(c); } static void cfwd_connect_next(CIPSTATE *cs) { cs->ai = cs->ai->ai_next; cfwd_connect_try(cs); } static void cfwd_connect_trial(int id, void *csv) { CIPSTATE *cs; int err; socklen_t errlen; cs = csv; remove_poll_id(id); errlen = sizeof(err); if ((getsockopt(cs->sock,SOL_SOCKET,SO_ERROR,&err,&errlen) < 0) || err) { close(cs->sock); cfwd_connect_next(cs); } else { freeaddrinfo(cs->ai0); cfwd_connect_ok(cs->c,cs->sock); free(cs); } } static void cfwd_connect_try(CIPSTATE *cs) { int sock; for (;cs->ai;cs->ai=cs->ai->ai_next) { sock = socket(cs->ai->ai_family,cs->ai->ai_socktype,cs->ai->ai_protocol); if (sock < 0) continue; fcntl(sock,F_SETFL,fcntl(sock,F_GETFL,0)|O_NONBLOCK); cs->sock = sock; if (connect(sock,cs->ai->ai_addr,cs->ai->ai_addrlen) < 0) { if (errno == EINPROGRESS) { cs->id = add_poll_fd(sock,&rwtest_never,&rwtest_always,0,&cfwd_connect_trial,cs); return; } close(sock); continue; } freeaddrinfo(cs->ai0); cfwd_connect_ok(cs->c,sock); free(cs); return; } freeaddrinfo(cs->ai0); cfwd_connect_fail(cs->c); free(cs); } static void cfwd_start_tcp(CFWDCONN *c) { struct addrinfo hints; char *portstr; CIPSTATE *cs; int err; if (c->type != CFCT_TCP) panic("type wrong"); cs = malloc(sizeof(CIPSTATE)); cs->c = c; hints.ai_flags = 0; hints.ai_family = 0; hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = 0; hints.ai_addrlen = 0; hints.ai_canonname = 0; hints.ai_addr = 0; hints.ai_next = 0; /* annoying to have to convert the port to a string */ asprintf(&portstr,"%u",c->tcp.req->connport); err = getaddrinfo(c->tcp.req->connhost,portstr,&hints,&cs->ai0); free(portstr); if (err) { free(cs); if (VERB(PROGRESS)) verb(PROGRESS,"[cfwd_start_connect: getaddrinfo(%s/%u): %s\r\n",c->tcp.req->connhost,c->tcp.req->connport,gai_strerror(err)); cfwd_connect_fail(c); } else { cs->ai = cs->ai0; cfwd_connect_try(cs); } } static void chanopen_forwarded_tcpip(int chan, const void *rest, int restlen, int broken) { CSESSION *s; CFWDREQ *r; STR connhost; unsigned int connport; STR clienthost; unsigned int clientport; unsigned int id; NESTED void failure(const void *errat __attribute__((__unused__)), const char *fmt, ...) { va_list ap; fprintf(errf,"%s: protocol error: bad %s request: ",__progname,broken?"forwarded-tcpip":"fixed-forwarded-tcpip@rodents.montreal.qc.ca"); va_start(ap,fmt); vfprintf(errf,fmt,ap); va_end(ap); fprintf(errf,"\r\n"); die(1); } if (broken) { parse_data(rest,restlen,&failure, PP_STRING(&connhost), PP_UINT32(&connport), PP_STRING(&clienthost), PP_UINT32(&clientport), PP_ENDSHERE ); } else { parse_data(rest,restlen,&failure, PP_STRING(&connhost), PP_UINT32(&connport), PP_STRING(&clienthost), PP_UINT32(&clientport), PP_UINT32(&id), PP_ENDSHERE ); } if (VERB(PROGRESS)) { verb(PROGRESS,"[chanopen: forwarded-tcpip %.*s/%u -> %.*s/%u",clienthost.len,clienthost.data,clientport,connhost.len,connhost.data,connport); if (broken) verb(PROGRESS," compat"); else printf(" id %u",id); verb(PROGRESS,"]\r\n"); } if ( !broken && config_bool("share-server") && share_fwded_tcpip(chan,str_to_rostr(connhost),connport,str_to_rostr(clienthost),clientport,id) ) return; for (s=csessions;s;s=s->flink) { for (r=s->fwdreqs;r;r=r->flink) { if (r->dir != FF_DIR_RTOL) continue; if (r->rtol.state != RFS_LIVE) continue; if ( (broken ? (r->rtol.flags & RFF_BROKEN) : (!(r->rtol.flags & RFF_BROKEN) && (r->serial == id))) && (r->listenport == connport) && ( !r->listenhost[0] || listenhost_match(r->listenhost,connhost) ) ) { CFWDCONN *c; c = nascent_cfwdconn(); c->type = CFCT_TCP; c->tcp.req = r; cfwdreq_ref(r); c->tcp.clientaddr = blk_to_cstr(clienthost.data,clienthost.len); c->tcp.clientport = clientport; c->chan = chan; DLL_LINK_HEAD(c,cfwdconns); cfwd_start_tcp(c); free_str(clienthost); free_str(connhost); return; } } } free_str(clienthost); free_str(connhost); (*ops->open_fail)(chan,SSH_OPEN_ADMINISTRATIVELY_PROHIBITED,cstr_to_rostr("No matching forwarding found"),cstr_to_rostr("en")); } static int rtest_cfa(int id __attribute__((__unused__)), void *cv) { CFWDAGENT *c; c = cv; return((c->state != CFAS_DEAD) && ((*ops->get_wwin)(c->chan) > 0)); } static int wtest_cfa(int id __attribute__((__unused__)), void *cv) { CFWDAGENT *c; c = cv; return((c->state != CFAS_DEAD) && oq_nonempty(&c->oq)); } static void rd_cfa(int id __attribute__((__unused__)), void *cv) { CFWDAGENT *c; int n; int r; char buf[8192]; c = cv; if (c->state == CFAS_DEAD) return; n = (*ops->get_wwin)(c->chan); if (n < 1) return; if (n > sizeof(buf)) n = sizeof(buf); r = read(agent_client_fd(c->ah),&buf[0],n); if (r < 0) { switch (errno) { case EINTR: case EWOULDBLOCK: return; break; } eprf("%s: agent forwarding %d: read error: %s\r\n",__progname,agent_client_fd(c->ah),strerror(errno)); } if (r <= 0) { c->state = CFAS_DEAD; (*ops->close)(c->chan); return; } (*ops->send_data)(c->chan,0,0,&buf[0],r); } static void wr_cfa(int id __attribute__((__unused__)), void *cv) { CFWDAGENT *c; int w; int n; c = cv; if (c->state == CFAS_DEAD) return; w = oq_writev(&c->oq,agent_client_fd(c->ah),0); if (w < 0) { switch (errno) { case EINTR: case EWOULDBLOCK: return; break; } eprf("%s: agent forwarding %d: write error: %s\r\n",__progname,agent_client_fd(c->ah),strerror(errno)); c->state = CFAS_DEAD; (*ops->close)(c->chan); return; } oq_dropdata(&c->oq,w); n = c->advwin + oq_qlen(&c->oq); if (n < BUFFERSPACE/2) { (*ops->add_rwin)(c->chan,BUFFERSPACE-n); c->advwin += BUFFERSPACE-n; } } static void generate_forwarding_notice(CFWDAGENT *c) { char *msg; int msglen; FILE *f; unsigned char lenbuf[4]; f = fopen_alloc(&msg,&msglen); putc(SSH_AGENT_FORWARDING_NOTICE,f); fput_string(f,rem_id.name,-1); fput_string(f,rem_id.ip,-1); fput_uint32(f,rem_id.port); fclose(f); put_uint32(&lenbuf[0],msglen); oq_queue_copy(&c->oq,&lenbuf[0],4); oq_queue_free(&c->oq,msg,msglen); } static void cfa_gotdata(void *cv, int cno, int ext __attribute__((__unused__)), unsigned int code __attribute__((__unused__)), const void *buf, int len) { CFWDAGENT *c; const unsigned char *bp; c = cv; if (cno != c->chan) panic("channel wrong"); if (c->state == CFAS_DEAD) return; if (len == 0) { c->state = CFAS_DEAD; (*ops->close)(cno); return; } c->advwin -= len; bp = buf; while (len > 0) { switch (c->state) { case CFAS_IDLE: generate_forwarding_notice(c); c->fill = 0; c->state = CFAS_HEADER; continue; break; case CFAS_HEADER: if (len >= 5-c->fill) { bcopy(bp,&c->hdr[c->fill],5-c->fill); len -= 5 - c->fill; bp += 5 - c->fill; c->bodyleft = get_uint32(&c->hdr[0]) - 1; if (c->bodyleft < 0) { c->state = CFAS_DEAD; eprf("%s: forwarded agent connection: bodyleft = %d\r\n",__progname,c->bodyleft); continue; } c->state = (c->hdr[4] == SSH_AGENT_FORWARDING_NOTICE) ? CFAS_NOTICE : CFAS_BODY; oq_queue_copy(&c->oq,&c->hdr[0],5); c->fill = 0; } else { bcopy(bp,&c->hdr[c->fill],len); c->fill += len; len = 0; } break; case CFAS_NOTICE: { int newstate; newstate = CFAS_HEADER; if (0) { case CFAS_BODY: newstate = CFAS_IDLE; } if (c->bodyleft < 1) { c->state = newstate; } else if (len >= c->bodyleft) { oq_queue_copy(&c->oq,bp,c->bodyleft); len -= c->bodyleft; bp += c->bodyleft; c->state = newstate; } else { oq_queue_copy(&c->oq,bp,len); c->bodyleft -= len; len = 0; } } break; case CFAS_DEAD: len = 0; break; } } } static void cfa_closed(void *cv, int cno, int final) { CFWDAGENT *c; c = cv; if (cno != c->chan) panic("channel wrong"); if (!final && !(*ops->close)(cno)) panic("close failed"); remove_poll_id(c->id); agent_client_close(c->ah); oq_flush(&c->oq); DLL_UNLINK(c,cfwdagents); free(c); } static const CHANOPS cfa_ops = { 0, 0, &cfa_gotdata, 0, 0, &cfa_closed }; static void chanopen_auth_agent(int chan, const void *rest, int restlen, int broken) { unsigned int serial; CSESSION *s; NESTED void failure(const void *errat __attribute__((__unused__)), const char *fmt, ...) { va_list ap; fprintf(errf,"%s: protocol error: bad authentication agent channel open: ",__progname); va_start(ap,fmt); vfprintf(errf,fmt,ap); va_end(ap); fprintf(errf,"\r\n"); die(1); } if (broken) { parse_data(rest,restlen,&failure,PP_ENDSHERE); } else { parse_data(rest,restlen,&failure, PP_UINT32(&serial), PP_ENDSHERE ); if ( config_bool("share-server") && share_auth_agent(chan,serial) ) return; } for (s=csessions;s;s=s->flink) { if ( broken ? (s->flags & SF_BAF) : ((s->flags & SF_AFWD) && (s->afwd_id == serial)) ) { CFWDAGENT *fa; char *errmsg; if ((s->flags & (SF_AFJO|SF_AUSE)) == (SF_AFJO|SF_AUSE)) { (*ops->open_fail)(chan,SSH_OPEN_ADMINISTRATIVELY_PROHIBITED,cstr_to_rostr("No agent forwarding request found"),cstr_to_rostr("en")); return; } s->flags |= SF_AUSE; fa = malloc(sizeof(CFWDAGENT)); fa->ah = open_agent_connection(&errmsg,AGENT_PROTO_NORMAL); if (! fa->ah) { eprf("%s: can't open forwarded agent connection: %s\r\n",__progname,errmsg); (*ops->open_fail)(chan,SSH_OPEN_CONNECT_FAILED,cstr_to_rostr(errmsg),cstr_to_rostr("en")); free(errmsg); free(fa); } else { DLL_LINK_HEAD(fa,cfwdagents); fa->id = add_poll_fd(agent_client_fd(fa->ah),&rtest_cfa,&wtest_cfa,&rd_cfa,&wr_cfa,fa); fa->chan = chan; fa->advwin = BUFFERSPACE; fa->state = CFAS_IDLE; oq_init(&fa->oq); (*ops->open_ok)(chan,ROSTRZERO,&cfa_ops,fa); (*ops->add_rwin)(chan,BUFFERSPACE); } return; } } (*ops->open_fail)(chan,SSH_OPEN_ADMINISTRATIVELY_PROHIBITED,cstr_to_rostr("No agent forwarding request found"),cstr_to_rostr("en")); } static void x_fail(void *cv) { CFWDCONN *c; c = cv; (*ops->close)(c->chan); } static void x_ok(int fd, void *cv) { CFWDCONN *c; c = cv; c->fd = fd; cfwd_up(c); } static void cxhdr_done(CFWDCONN *c) { if (c->advwin || (*ops->get_rwin)(c->chan)) panic("window present"); (*ops->set_ops)(c->chan,&cfwd_ops,c); c->id = PL_NOID; X_start_connect(c->x.session->xfreq,c->x.chdr,&x_ok,&x_fail,c); } static void cxskip_gotdata(void *cv, int cno, int ext __attribute__((__unused__)), unsigned int code __attribute__((__unused__)), const void *buf __attribute__((__unused__)), int len) { CFWDCONN *c; c = cv; if (cno != c->chan) panic("channel wrong"); if (VERB(FWDDATA)) verb(FWDDATA,"[cxskip_gotdata %d (%s): gotdata %d]\r\n",c->fd,cfwdconn_parentext(c),len); if (len == 0) { (*ops->close)(cno); return; } if (len > c->x.chlen) { fprintf(errf,"%s: X data overrun (%d > %d)\n",__progname,len,c->x.chlen); (*ops->close)(cno); return; } c->x.chlen -= len; c->advwin -= len; if (c->x.chlen > 0) return; cxhdr_done(c); } static const CHANOPS cxskip_ops = { 0, 0, &cxskip_gotdata, 0, 0, &cfwd_closed }; static void cxhdr_gotdata(void *cv, int cno, int ext __attribute__((__unused__)), unsigned int code __attribute__((__unused__)), const void *buf, int len) { CFWDCONN *c; int skiplen; c = cv; if (cno != c->chan) panic("channel wrong"); if (VERB(FWDDATA)) verb(FWDDATA,"[cxhdr_gotdata %d (%s): gotdata %d]\r\n",c->fd,cfwdconn_parentext(c),len); if (len == 0) { (*ops->close)(cno); return; } if (c->x.chptr+len > 12) { fprintf(errf,"%s: X data overrun (%d+%d > 12)\n",__progname,c->x.chptr,len); (*ops->close)(cno); return; } bcopy(buf,c->x.chdr+c->x.chptr,len); c->x.chptr += len; c->advwin -= len; if (c->x.chptr < 12) return; skiplen = X_auth_len(c->x.chdr); if (skiplen > 0) { (*ops->set_ops)(c->chan,&cxskip_ops,c); c->x.chlen = skiplen; (*ops->add_rwin)(c->chan,skiplen); c->advwin += skiplen; return; } cxhdr_done(c); } static const CHANOPS cxhdr_ops = { 0, 0, &cxhdr_gotdata, 0, 0, &cfwd_closed }; static void cbxhdr_gotdata(void *cv, int cno, int ext __attribute__((__unused__)), unsigned int code __attribute__((__unused__)), const void *buf, int len) { CFWDCONN *c; int skiplen; CSESSION *s; c = cv; if (cno != c->chan) panic("channel wrong"); if (VERB(FWDDATA)) verb(FWDDATA,"[cbxhdr_gotdata %d (%s): gotdata %d]\r\n",c->fd,cfwdconn_parentext(c),len); if (len == 0) { (*ops->close)(cno); return; } if (c->x.chptr+len > c->x.chlen) { fprintf(errf,"%s: X data overrun (%d+%d > %d)\n",__progname,c->x.chptr,len,c->x.chlen); (*ops->close)(cno); return; } bcopy(buf,c->x.chdr+c->x.chptr,len); c->x.chptr += len; c->advwin -= len; if (c->x.chptr < c->x.chlen) return; if (c->x.chlen == 12) { skiplen = X_auth_len(c->x.chdr); if (skiplen < 1) { fprintf(errf,"%s: unauthenticated broken X forward\n",__progname); (*ops->close)(cno); return; } (*ops->add_rwin)(c->chan,skiplen); c->advwin += skiplen; c->x.chlen += skiplen; c->x.chdr = realloc(c->x.chdr,c->x.chlen); return; } for (s=csessions;s;s=s->flink) { if ( (s->flags & SF_BXF) && X_auth_match(c->x.chdr,"MIT-MAGIC-COOKIE-1",18,s->bxcookie,s->bxclen) ) { c->x.session = s; csession_ref(s); cxhdr_done(c); return; } } fprintf(errf,"%s: unrequested broken X forward\n",__progname); (*ops->close)(cno); } static const CHANOPS cbxhdr_ops = { 0, 0, &cbxhdr_gotdata, 0, 0, &cfwd_closed }; /* * The only available place to stash any identifier for which channel a * forwarded X connection goes with is in the X cookie. This means we * have to accept the connection and let it come up far enough to * receive the start of the X data before we even tell whether we want * to accept it - an interesting chicken-and-egg dilemma. This means * that unwanted X forwardings don't have their channels refused; * instead, the channels are accepted and then closed ungracefully. * This is one of the prices of using stock X forwarding. */ static void chanopen_x(int chan, const void *rest, int restlen, int broken) { unsigned int serial; CSESSION *s; STR remhow; const void *rest2; int rest2len; CFX_METHOD remtype; STR remhost; unsigned int remport; NESTED void failure(const void *errat __attribute__((__unused__)), const char *fmt, ...) { va_list ap; fprintf(errf,"%s: protocol error: bad X channel open: ",__progname); va_start(ap,fmt); vfprintf(errf,fmt,ap); va_end(ap); fprintf(errf,"\r\n"); die(1); } if (broken) { parse_data(rest,restlen,&failure, PP_STRING(&remhost), PP_UINT32(&remport), PP_ENDSHERE ); remhow = blk_to_str(strdup("tcp"),3); for (s=csessions;s;s=s->flink) { if (s->flags & SF_BXF) { CFWDCONN *c; c = nascent_cfwdconn(); c->type = CFCT_X; c->x.method = CFXM_TCP; c->x.tcp.clientaddr = blk_to_cstr(remhost.data,remhost.len); c->x.tcp.clientport = remport; free_str(remhost); c->chan = chan; c->id = PL_NOID; DLL_LINK_HEAD(c,cfwdconns); (*ops->open_ok)(c->chan,cstr_to_rostr(""),&cbxhdr_ops,c); (*ops->add_rwin)(c->chan,12); c->advwin = 12; c->x.chdr = malloc(12); c->x.chlen = 12; c->x.chptr = 0; c->x.session = 0; return; } } } else { parse_data(rest,restlen,&failure, PP_UINT32(&serial), PP_STRING(&remhow), PP_REST(&rest2,&rest2len) ); if (str_equalsC(remhow,"tcp")) { remtype = CFXM_TCP; parse_data(rest2,rest2len,&failure, PP_STRING(&remhost), PP_UINT32(&remport), PP_ENDSHERE ); if (VERB(FWD)) verb(FWD,"forwarded TCP X connection (%.*s/%u)\n",remhost.len,remhost.data,remport); } else if (str_equalsC(remhow,"local")) { remtype = CFXM_LOCAL; parse_data(rest2,rest2len,&failure,PP_ENDSHERE); } else { remtype = CFXM_OTHER; if (VERB(FWD)) verb(FWD,"forwarded X connection, method %.*s\n",remhow.len,remhow.data); } if ( config_bool("share-server") && share_x(chan,str_to_rostr(remhow),rest2,rest2len,serial) ) return; for (s=csessions;s;s=s->flink) { if ((s->flags & SF_XFWD) && (s->xfwd_id == serial)) { CFWDCONN *c; if ((s->flags & (SF_XFJO|SF_XUSE)) == (SF_XFJO|SF_XUSE)) { (*ops->open_fail)(c->chan,SSH_OPEN_ADMINISTRATIVELY_PROHIBITED,cstr_to_rostr("No X forwarding request found"),cstr_to_rostr("en")); free_str(remhow); if (remtype == CFXM_TCP) free_str(remhost); return; } s->flags |= SF_XUSE; c = nascent_cfwdconn(); c->type = CFCT_X; c->x.method = remtype; switch (remtype) { case CFXM_TCP: c->x.tcp.clientaddr = blk_to_cstr(remhost.data,remhost.len); c->x.tcp.clientport = remport; free_str(remhost); break; case CFXM_LOCAL: break; case CFXM_OTHER: c->x.other.method = blk_to_cstr(remhow.data,remhow.len); break; default: panic("impossible remtype in chanopen_x"); break; } c->chan = chan; c->id = PL_NOID; DLL_LINK_HEAD(c,cfwdconns); (*ops->open_ok)(c->chan,cstr_to_rostr(""),&cxhdr_ops,c); (*ops->add_rwin)(c->chan,12); c->advwin = 12; c->x.chdr = malloc(12); c->x.chptr = 0; c->x.session = s; csession_ref(s); free_str(remhow); return; } } } free_str(remhow); switch (remtype) { case CFXM_TCP: free_str(remhost); break; default: break; } (*ops->open_fail)(chan,SSH_OPEN_ADMINISTRATIVELY_PROHIBITED,cstr_to_rostr("No X forwarding request found"),cstr_to_rostr("en")); } static void chanopen_c(ROSTR type, int chan, const void *rest, int restlen) { if (str_equalcC(type,"forwarded-tcpip")) { chanopen_forwarded_tcpip(chan,rest,restlen,1); } else if (str_equalcC(type,"fixed-forwarded-tcpip@rodents.montreal.qc.ca")) { chanopen_forwarded_tcpip(chan,rest,restlen,0); } else if (str_equalcC(type,"auth-agent")) { chanopen_auth_agent(chan,rest,restlen,1); } else if (str_equalcC(type,"fixed-auth-agent@rodents.montreal.qc.ca")) { chanopen_auth_agent(chan,rest,restlen,0); } else if (str_equalcC(type,"x11")) { chanopen_x(chan,rest,restlen,1); } else if (str_equalcC(type,"fixed-x11@rodents.montreal.qc.ca")) { chanopen_x(chan,rest,restlen,0); } else { printf("[chanopen: type %.*s restlen %d]\r\n",type.len,type.data,restlen); (*ops->open_fail)(chan,SSH_OPEN_UNKNOWN_CHANNEL_TYPE,cstr_to_rostr(""),cstr_to_rostr("")); } } static int globalreq_c(ROSTR name, int wantrepl, const void *rest, int restlen) { printf("[globalreq: name %.*s wantreply %d restlen %d]\n",name.len,name.data,wantrepl,restlen); rest=rest; return(GLOBALREQ_UNK); } static NONCHANOPS gbl_ops_c = { &chanopen_c, &globalreq_c }; static char *text_char(unsigned char ch) { char *t; if (ch < 32) { asprintf(&t,"^%c",ch+'@'); } else if (ch < 127) { asprintf(&t,"%c",ch); } else if (ch == 127) { asprintf(&t,"^?"); } else if (ch < 160) { asprintf(&t,"^%c",ch+'À'-128); } else { asprintf(&t,"%c",ch); } return(t); } static int save_tty(void) { if (tcgetattr(0,&tio_i) < 0) return(0); if (tcgetattr(1,&tio_o) < 0) return(0); if (ioctl(1,TIOCGWINSZ,&wsz) < 0) return(0); in_suspc = tio_i.c_cc[VSUSP]; free(suspc_txt); suspc_txt = text_char(in_suspc); tio_i_raw = tio_i; cfmakeraw(&tio_i_raw); tio_i_raw.c_cc[VMIN] = 1; tio_i_raw.c_cc[VTIME] = 0; raw_tty = 0; saved |= SAVE_TTY; return(1); } static void save_blocking(void) { nonblock_i = fcntl(0,F_GETFL,0) & O_NONBLOCK; nonblock_o = fcntl(0,F_GETFL,1) & O_NONBLOCK; nonblock_e = fcntl(0,F_GETFL,2) & O_NONBLOCK; saved |= SAVE_BLOCK; } static void set_nonblocking(void) { if (using_tty) { fcntl(0,F_SETFL,fcntl(0,F_GETFL,0)|O_NONBLOCK); fcntl(1,F_SETFL,fcntl(1,F_GETFL,0)|O_NONBLOCK); fcntl(2,F_SETFL,fcntl(2,F_GETFL,0)|O_NONBLOCK); } } static void modecheck(void) { int wantraw; if (! using_tty) return; wantraw = (localmode != LCL_CMD) && cursession && (cursession->state == SS_UP) && !(cursession->flags & SF_SUSP); if (wantraw == raw_tty) return; tcsetattr(0,TCSADRAIN|TCSASOFT,wantraw?&tio_i_raw:&tio_i); raw_tty = wantraw; } /* * escchar points to unsigned to shut up gcc about tolower(), * isxdigit(), etc being appliex to escchar[]. Contrary to POLS, * these functions are sometimes actually macros (this is permitted) * which don't work correctly for negative signed chars (IMO this is a * bug if plain char is signed). */ static void setup_esc(void) { const unsigned char *escchar; escchar = config_str("escape"); if (!escchar || !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 ( (escchar[0] == '0') && (tolower(escchar[1]) == 'x') && escchar[2] && escchar[3] && !escchar[4] && isxdigit(escchar[2]) && isxdigit(escchar[3]) && (sscanf(&escchar[2],"%x",&in_esc) == 1) ) { } else { int ctl; int meta; const char *ep; ctl = 0; meta = 0; ep = escchar; while (1) { if ( (tolower((unsigned char)ep[0]) == 'c') && (ep[1] == '-') ) { ctl = 1; ep += 2; } else if ( (tolower((unsigned char)ep[0]) == 'm') && (ep[1] == '-') ) { meta = 1; ep += 2; } else if (ep[0] || !ep[1]) { in_esc = ep[0]; if (ctl) in_esc = (in_esc == '?') ? '\177' : (in_esc&31); if (meta) in_esc |= 0x80; break; } else { fprintf(errf,"%s: can't parse -esc option `%s'\n",__progname,escchar); exit(1); } } } esc_txt = text_char(in_esc); } static void dq_init(DQ *q, int fd, const char *tag) { q->fd = fd; q->tag = tag; oq_init(&q->q); } static void dq_append(DQ *q, const void *buf, int len) { if (q->fd < 0) panic("no fd"); oq_queue_copy(&q->q,buf,len); } static void dq_weof(DQ *q) { oq_queue_special(&q->q,0,0); } static void send_size(CSESSION *s) { unsigned char *opp; if (s->state == SS_UP) { opp = (*ops->req_hdr)(s->chan,cstr_to_rostr("window-change")); opp = put_uint32(opp,wsz.ws_col); opp = put_uint32(opp,wsz.ws_row); opp = put_uint32(opp,wsz.ws_xpixel); opp = put_uint32(opp,wsz.ws_ypixel); (*ops->send_req_blind)(s->chan,opp); } } static void check_size(void) { struct winsize sz; if (ioctl(1,TIOCGWINSZ,&sz) < 0) return; if ( (sz.ws_row != wsz.ws_row) || (sz.ws_col != wsz.ws_col) || (sz.ws_xpixel != wsz.ws_xpixel) || (sz.ws_ypixel != wsz.ws_ypixel) ) { wsz = sz; if (cursession) send_size(cursession); } } static void handle_winch(int sig __attribute__((__unused__))) { write(winchpipe[1],"",1); } static void rd_winch(int id __attribute__((__unused__)), void *arg __attribute__((__unused__))) { char buf[64]; read(winchpipe[0],&buf[0],sizeof(buf)); check_size(); } static int input_for_session(CSESSION *s) { return( s && (s->state == SS_UP) && !(s->flags & (SF_IEOF|SF_SUSP)) && ((*ops->get_wwin)(s->chan) > 0) ); } static void suspend_it(void) { sigset_t m; sigset_t om; struct sigaction act; struct sigaction oact; struct winsize owsz; owsz = wsz; restore_tty(); restore_blocking(); sigemptyset(&m); sigemptyset(&om); sigaddset(&m,SIGTSTP); sigprocmask(SIG_UNBLOCK,&m,&om); act.sa_handler = SIG_DFL; sigemptyset(&act.sa_mask); act.sa_flags = 0; sigaction(SIGTSTP,&act,&oact); kill(0,SIGTSTP); sigprocmask(SIG_SETMASK,&om,0); sigaction(SIGTSTP,&oact,0); save_tty(); save_blocking(); set_nonblocking(); modecheck(); wsz = owsz; handle_winch(0); } static void quit_cur(void) { if (cursession) (*ops->close)(cursession->chan); else oprf("\a"); } static void lclfwd_acc(int id __attribute__((__unused__)), void *lv) { CFWDLFD *l; int new; CFWDCONN *c; unsigned char *opp; struct sockaddr_storage ss; socklen_t sslen; char hnbuf[NI_MAXHOST]; char pnbuf[NI_MAXSERV]; l = lv; sslen = sizeof(ss); new = accept(l->fd,(void *)&ss,&sslen); if (new < 0) { switch (errno) { case EINTR: case EWOULDBLOCK: return; break; } eprf("%s: local forwarding %s: accept on %s: %s\r\n",__progname,l->req->text,l->text,strerror(errno)); return; } if (getnameinfo((void *)&ss,sslen,&hnbuf[0],NI_MAXHOST,&pnbuf[0],NI_MAXSERV,NI_NUMERICHOST|NI_NUMERICSERV|NI_WITHSCOPEID)) { eprf("%s: local forwarding %s: getnameinfo: %s\r\n",__progname,l->req->text,strerror(errno)); close(new); return; } fcntl(new,F_SETFL,fcntl(new,F_GETFL,0)|O_NONBLOCK); c = nascent_cfwdconn(); c->type = CFCT_TCP; c->tcp.req = l->req; cfwdreq_ref(l->req); c->tcp.clientaddr = strdup(&hnbuf[0]); c->tcp.clientport = atoi(&pnbuf[0]); c->fd = new; c->id = PL_NOID; opp = (*ops->open_hdr)(cstr_to_rostr("direct-tcpip")); opp = put_string(opp,l->req->connhost,-1); opp = put_uint32(opp,l->req->connport); opp = put_string(opp,c->tcp.clientaddr,-1); opp = put_uint32(opp,c->tcp.clientport); c->chan = (*ops->open_send)(opp,&cfwd_ops,c); DLL_LINK_HEAD(c,cfwdconns); } static void rfwd_setdead(CFWDREQ *r) { r->rtol.state = RFS_DEAD; DLL_UNLINK(r,r->s->fwdreqs); cfwdreq_deref(r); } static void rfwd_stop_reply(int status, void *arg, const void *rest, int restlen) { CFWDREQ *r; NESTED void failure(const void *errat __attribute__((__unused__)), const char *fmt, ...) { va_list ap; fprintf(errf,"%s: protocol error: bad tcpip-forward reply: ",__progname); va_start(ap,fmt); vfprintf(errf,fmt,ap); va_end(ap); fprintf(errf,"\n"); die(1); } r = arg; switch (status) { case GREQ_OK: parse_data(rest,restlen,&failure,PP_ENDSHERE); if (r->rtol.state != RFS_STOP) panic("bad OK state"); rfwd_setdead(r); break; case GREQ_FAIL: oprf("-R %s: forwarding cancellation rejected by peer!\n",r->text); r->rtol.state = RFS_BROKEN; break; default: panic("bad status"); break; } cfwdreq_deref(r); } static void cfwd_send_cancel(CFWDREQ *r) { unsigned char *opp; if (r->dir != FF_DIR_RTOL) panic("wrong direction"); if (r->rtol.flags & RFF_BROKEN) { opp = (*ops->g_req_hdr)(cstr_to_rostr("cancel-tcpip-forward")); opp = put_string(opp,r->listenhost,-1); opp = put_uint32(opp,r->listenport); } else { if (config_bool("no-private")) abort(); opp = (*ops->g_req_hdr)(cstr_to_rostr("fixed-cancel-tcpip@rodents.montreal.qc.ca")); opp = put_string(opp,r->listenhost,-1); opp = put_uint32(opp,r->listenport); opp = put_uint32(opp,r->serial); } (*ops->g_send_req)(opp,&rfwd_stop_reply,r); } static int flush_check(void *sv) { CSESSION *s; s = sv; if (oq_nonempty(&s->odq.q) || oq_nonempty(&s->edq.q)) return(BLOCK_NIL); remove_block_id(s->flush_id); s->state = SS_DEAD; return(BLOCK_LOOP); } static void die_on_flush(CSESSION *s) { s->state = SS_FLUSH; s->flush_id = add_block_fn(&flush_check,s); s->flags |= SF_IEOF | SF_OEOF; } static void rfwd_broken_start_reply(int status, void *arg, const void *rest, int restlen) { CFWDREQ *r; NESTED void failure(const void *errat __attribute__((__unused__)), const char *fmt, ...) { va_list ap; fprintf(errf,"%s: protocol error: bad tcpip-forward reply: ",__progname); va_start(ap,fmt); vfprintf(errf,fmt,ap); va_end(ap); fprintf(errf,"\r\n"); die(1); } r = arg; switch (status) { case GREQ_OK: parse_data(rest,restlen,&failure,PP_ENDSHERE); switch (r->rtol.state) { default: panic("bad state"); break; case RFS_START: r->rtol.state = RFS_LIVE; r->rtol.flags |= RFF_BROKEN; break; case RFS_BLIP: cfwd_send_cancel(r); cfwdreq_ref(r); r->rtol.state = RFS_STOP; break; } break; case GREQ_FAIL: parse_data(rest,restlen,&failure,PP_ENDSHERE); switch (r->rtol.state) { default: panic("bad state"); break; case RFS_START: eprf("%s: -R %s: forwarding rejected by peer\r\n",__progname,r->text); if (r->rtol.flags & RFF_MUST) die_on_flush(r->s); break; case RFS_BLIP: break; } rfwd_setdead(r); break; default: panic("bad status"); break; } cfwdreq_deref(r); } static void rfwd_request_broken(CFWDREQ *r) { unsigned char *opp; opp = (*ops->g_req_hdr)(cstr_to_rostr("tcpip-forward")); opp = put_string(opp,r->listenhost,-1); opp = put_uint32(opp,r->listenport); (*ops->g_send_req)(opp,&rfwd_broken_start_reply,r); cfwdreq_ref(r); } static void rfwd_start_reply(int status, void *arg, const void *rest, int restlen) { CFWDREQ *r; NESTED void failure(const void *errat __attribute__((__unused__)), const char *fmt, ...) { va_list ap; fprintf(errf,"%s: protocol error: bad fixed-tcpip-forward@rodents.montreal.qc.ca reply: ",__progname); va_start(ap,fmt); vfprintf(errf,fmt,ap); va_end(ap); fprintf(errf,"\r\n"); die(1); } r = arg; switch (status) { case GREQ_OK: parse_data(rest,restlen,&failure,PP_ENDSHERE); switch (r->rtol.state) { default: panic("bad state"); break; case RFS_START: r->rtol.state = RFS_LIVE; break; case RFS_BLIP: cfwd_send_cancel(r); cfwdreq_ref(r); r->rtol.state = RFS_STOP; break; } break; case GREQ_FAIL: parse_data(rest,restlen,&failure,PP_ENDSHERE); switch (r->rtol.state) { default: panic("bad state"); break; case RFS_START: rfwd_request_broken(r); break; case RFS_BLIP: rfwd_setdead(r); break; } break; default: panic("bad status"); break; } cfwdreq_deref(r); } unsigned int nextfwdreq_serial(void) { return(++fwdreq_serial); } static void setup_forwarding(CSESSION *s, const TCPFWD *f) { switch (f->flags & FF_DIR) { case FF_DIR_LTOR: { CFWDREQ *r; CFWDLFD *l; int sock; int err; char *etxt; struct addrinfo *ai0; struct addrinfo *ai; struct addrinfo hints; char *portstr; char hn[NI_MAXHOST]; char sn[NI_MAXSERV]; r = malloc(sizeof(CFWDREQ)); r->s = s; csession_ref(s); r->ltor.lfds = 0; r->refs = 0; r->dir = FF_DIR_LTOR; r->serial = nextfwdreq_serial(); r->listenhost = strdup(f->listenhost); r->listenport = f->listenport; r->connhost = strdup(f->connhost); r->connport = f->connport; r->text = strdup(f->text); hints.ai_flags = AI_PASSIVE; hints.ai_family = 0; hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = 0; hints.ai_addrlen = 0; hints.ai_canonname = 0; hints.ai_addr = 0; hints.ai_next = 0; /* annoying to have to convert the port to a string */ asprintf(&portstr,"%u",r->listenport); err = getaddrinfo(r->listenhost[0]?r->listenhost:0,portstr,&hints,&ai0); free(portstr); if (err) { oprf("%s: can't look up ",__progname); if (r->listenhost[0]) { oprf("%s/%u",r->listenhost,r->listenport); } else { oprf("port %u",r->listenport); } oprf(": %s\n",gai_strerror(err)); } else { NESTED char *errtxt(void) { if (! etxt) { int e; e = getnameinfo(ai->ai_addr,ai->ai_addrlen,&hn[0],sizeof(hn),&sn[0],sizeof(sn),NI_NUMERICHOST|NI_NUMERICSERV|NI_WITHSCOPEID); if (e) { if (r->listenhost[0]) { asprintf(&etxt,"%s/%u [getnameinfo failed: %s]",r->listenhost,r->listenport,strerror(e)); } else { asprintf(&etxt,"port %u [getnameinfo failed: %s]",r->listenport,strerror(e)); } } else { asprintf(&etxt,"%s/%s",&hn[0],&sn[0]); } } return(etxt); } etxt = 0; for (ai=ai0;ai;ai=ai->ai_next) { free(etxt); etxt = 0; sock = socket(ai->ai_family,ai->ai_socktype,ai->ai_protocol); if (sock < 0) { oprf("%s: %s: socket: %s\n",__progname,errtxt(),strerror(errno)); continue; } if (bind(sock,ai->ai_addr,ai->ai_addrlen) < 0) { oprf("%s: %s: bind: %s\n",__progname,errtxt(),strerror(errno)); close(sock); continue; } listen(sock,10); fcntl(sock,F_SETFL,fcntl(sock,F_GETFL,0)|O_NONBLOCK); l = malloc(sizeof(CFWDLFD)); l->req = r; cfwdreq_ref(r); l->fd = sock; DLL_LINK_HEAD(l,r->ltor.lfds); l->id = add_poll_fd(sock,&rwtest_always,&rwtest_never,&lclfwd_acc,0,l); l->text = errtxt(); etxt = 0; } free(etxt); freeaddrinfo(ai0); if (r->ltor.lfds == 0) { oprf("%s: can't forward ",__progname); if (r->listenhost[0]) { oprf("%s/%u\n",r->listenhost,r->listenport); } else { oprf("port %u\n",r->listenport); } } } if (r->ltor.lfds == 0) { free(r); } else { DLL_LINK_HEAD(r,s->fwdreqs); cfwdreq_ref(r); } } break; case FF_DIR_RTOL: { unsigned char *opp; CFWDREQ *r; r = malloc(sizeof(CFWDREQ)); r->s = s; csession_ref(s); r->refs = 0; r->dir = FF_DIR_RTOL; r->serial = nextfwdreq_serial(); r->listenhost = strdup(f->listenhost); r->listenport = f->listenport; r->connhost = strdup(f->connhost); r->connport = f->connport; r->text = strdup(f->text); r->rtol.state = RFS_START; r->rtol.flags = 0; if (f->flags & FF_MUST) r->rtol.flags |= RFF_MUST; DLL_LINK_HEAD(r,s->fwdreqs); cfwdreq_ref(r); if (config_bool("no-private")) { rfwd_request_broken(r); } else { opp = (*ops->g_req_hdr)(cstr_to_rostr("fixed-tcpip-forward@rodents.montreal.qc.ca")); opp = put_string(opp,r->listenhost,-1); opp = put_uint32(opp,r->listenport); opp = put_uint32(opp,r->serial); (*ops->g_send_req)(opp,&rfwd_start_reply,r); } cfwdreq_ref(r); } break; default: panic("bad direction"); break; } } static void initial_forwardings(CSESSION *s) { int i; for (i=ntcpfwds-1;i>=0;i--) setup_forwarding(s,tcpfwds[i]); } static int matchlen(const char *s, const unsigned char *b, int l) { int i; for (i=0;s[i]&&(i= len) return(CAA_EMPTY); if (! isspace(cmd[i])) break; i ++; } cbeg = i; while ((i < len) && !isspace(cmd[i])) i ++; cend = i; while ((i < len) && isspace(cmd[i])) i ++; argbeg = i; match = -1; ambiguous = 0; for (i=0;cmds[i].cmd;i++) { n = matchlen(cmds[i].cmd,cmd+cbeg,cend-cbeg); if (n == cend-cbeg) { if (match >= 0) ambiguous = 1; else match = i; } } if (match < 0) return(CAA_NOTFOUND); if (ambiguous) return(CAA_AMBIGUOUS); (*cmds[match].handler)(cmd+argbeg,len-argbeg); return(CAA_OK); } static void lcl_cmd_help(unsigned char *args __attribute__((__unused__)), int arglen __attribute__((__unused__))) { oprf("\ ?, help - print this list\n\ continue - resume the current session\n\ continue N - resume session number N\n\ quit - drop the connection and exit\n\ forward - connection-forwarding operations (`forward help' for more)\n\ suspend - suspend\n\ bg - background\n\ status - print status information\n\ open - open a new shell connection\n\ open cmd arg arg arg...\n\ - execute a remote command\n\ rekey - force the underlying session to rekey\n\ alg - algorithm negotiation options (`alg help' for more)\n\ debug [arg] - debugging ops (see the source)\n\ \n\ Unambiguous abbreviations are accepted.\n"); } static void resume_session(void) { if (cursession) { localmode = LCL_NO; remove_block_id(bfid); cursession->flags &= ~SF_SUSP; modecheck(); } } static void lcl_cmd_continue(unsigned char *args, int arglen) { long int v; char *t; char *ep; CSESSION *s; if (arglen < 1) { resume_session(); return; } t = blk_to_cstr(args,arglen); v = strtol(t,&ep,0); if (*ep || (t == ep)) { oprf("Invalid session number `%s'\n",t); free(t); return; } free(t); for (s=csessions;s;s=s->flink) { if (s->serial == v) { DLL_UNLINK(s,csessions); DLL_LINK_HEAD(s,csessions); cursession = s; resume_session(); return; } } oprf("No session %ld\n",v); } static void lcl_cmd_quit(unsigned char *args __attribute__((__unused__)), int arglen) { if (arglen > 0) { oprf("`quit' takes no arguments\r\n"); return; } /* Arguably should tear down stuff cleanly.... */ exit(0); } static void lcl_cmd_forward_help(unsigned char *args __attribute__((__unused__)), int arglen __attribute__((__unused__))) { oprf("\ forward ? - print this list\r\n\ forward help - print this list\r\n\ forward list - list forwardings\r\n\ forward add -[L|R] [addr/]port/addr/port\r\n\ - add a new forwarding\r\n\ forward delete -[L|R] [addr/]port/addr/port\r\n\ - delete an existing forwarding request\r\n\ forward delete #\r\n\ - delete a forwarding request by number\r\n\ forward drop #\r\n\ - drop a forwarded connection\r\n\ forward +agent\r\n\ forward 1agent\r\n\ forward -agent\r\n\ forward +x\r\n\ forward 1x\r\n\ forward -x\r\n\ - do/don't do agent, or X, forwarding for new opens\r\n\ (1 = do for one connection)\r\n\ \r\n\ Unambiguous abbreviations are accepted. Numbers for `forward drop' and\r\n\ the numeric form of `forward delete' are printed by `forward list'.\r\n\ The current agent and X forwarding settings are shown by `status'.\r\n"); } static void lcl_cmd_forward_list(unsigned char *args __attribute__((__unused__)), int arglen) { CSESSION *s; CFWDREQ *r; CFWDCONN *c; if (arglen > 0) { oprf("`forward list' takes no arguments\r\n"); return; } oprf("Forwarding requests:\r\n"); for (s=csessions;s;s=s->flink) { oprf("Session %u%s\r\n",s->serial,(s==cursession)?" (current)":""); for (r=s->fwdreqs;r;r=r->flink) { oprf(" #%u: ",r->serial); switch (r->dir) { case FF_DIR_LTOR: oprf("-L "); if (r->listenhost[0]) oprf("%s/",r->listenhost); oprf("%u/%s/%u\r\n",r->listenport,r->connhost,r->connport); break; case FF_DIR_RTOL: oprf("-R "); if (r->listenhost[0]) oprf("%s/",r->listenhost); oprf("%u/%s/%u",r->listenport,r->connhost,r->connport); switch (r->rtol.state) { case RFS_START: oprf(" (startup)"); break; case RFS_LIVE: break; case RFS_STOP: oprf(" (stopping)"); break; case RFS_BROKEN: oprf(" (stop refused!)"); break; case RFS_DEAD: oprf(" (dead?""?)"); break; case RFS_BLIP: oprf(" (startup/stopped)"); break; default: panic("bad state"); break; } if (r->rtol.flags & RFF_BROKEN) oprf(" (compat)"); else oprf(" (id %u)",r->serial); oprf("\r\n"); break; default: panic("bad direction"); break; } } } oprf("Active connections:\r\n"); for (c=cfwdconns;c;c=c->flink) { char nbuf[64]; sprintf(&nbuf[0],"#%u",c->serial); oprf("%9s: ",&nbuf[0]); switch (c->type) { case CFCT_UNSET: oprf("?unset\r\n"); break; case CFCT_TCP: switch (c->tcp.req->dir) { case FF_DIR_LTOR: oprf("-L "); break; case FF_DIR_RTOL: oprf("-R "); break; default: panic("bad direction"); break; } oprf("%s/%u -> ",c->tcp.clientaddr,c->tcp.clientport); if (c->tcp.req->listenhost[0]) oprf("%s/",c->tcp.req->listenhost); oprf("%u -> %s/%u [%d]\r\n",c->tcp.req->listenport,c->tcp.req->connhost,c->tcp.req->connport,c->fd); break; case CFCT_X: oprf("X\r\n"); break; default: panic("bad type"); break; } } } static void lcl_cmd_forward_add(unsigned char *args, int arglen) { __label__ ret; TCPFWD f; NESTED void usage(void) { oprf("Usage: forward add -[L|R] [addr/]port/addr/port\n"); goto ret; } NESTEDFWD void invalid(const char *, ...) __attribute__((__format__(__printf__,1,2))); NESTEDDEF void invalid(const char *fmt, ...) { va_list ap; oprf("Error: "); va_start(ap,fmt); vfprintf(opf,fmt,ap); va_end(ap); oprf("\n"); usage(); } if (arglen < 2) usage(); if (! cursession) { oprf("No current session\n"); return; } if (!bcmp(args,"-L",2)) { f.flags = FF_DIR_LTOR; } else if (!bcmp(args,"-R",2)) { f.flags = FF_DIR_RTOL; } else { usage(); } parse_fwd(args+2,arglen-2,&f,&invalid); setup_forwarding(cursession,&f); free(f.listenhost); free(f.connhost); free(f.text); ret:; } static void request_stop_forwarding(CFWDREQ *r) { if (r->dir != FF_DIR_RTOL) panic("wrong direction"); switch (r->rtol.state) { default: panic("bad state"); break; case RFS_START: r->rtol.state = RFS_BLIP; break; case RFS_LIVE: cfwd_send_cancel(r); cfwdreq_ref(r); r->rtol.state = RFS_STOP; break; case RFS_STOP: case RFS_BROKEN: case RFS_BLIP: break; case RFS_DEAD: panic("stopping dead forwarding"); break; } } static void delete_forwarding(CFWDREQ *r) { switch (r->dir) { case FF_DIR_LTOR: while (r->ltor.lfds) { CFWDLFD *l; l = r->ltor.lfds; if (l->req != r) panic("backpointer wrong"); DLL_UNLINK(l,r->ltor.lfds); cfwdreq_deref(r); remove_poll_id(l->id); close(l->fd); free(l->text); free(l); } DLL_UNLINK(r,r->s->fwdreqs); cfwdreq_deref(r); break; case FF_DIR_RTOL: request_stop_forwarding(r); break; default: panic("bad direction"); break; } } static int fwdreq_matches_tcpfwd(const CFWDREQ *r, const TCPFWD *f) { return( (r->listenport == f->listenport) && (r->connport == f->connport) && (r->dir == (f->flags & FF_DIR)) && !strcmp(r->listenhost,f->listenhost) && !strcmp(r->connhost,f->connhost) ); } static void lcl_cmd_forward_delete(unsigned char *args, int arglen) { __label__ ret; CSESSION *s; CFWDREQ *r; CFWDREQ *r2; TCPFWD f; int any; unsigned long int ser; char *ep; int (*matchfn)(CFWDREQ *); NESTED void usage(void) { oprf("Usage: forward delete -[L|R] [addr/]port/addr/port\n"); oprf(" forward delete number\n"); goto ret; } NESTEDFWD void invalid(const char *, ...) __attribute__((__format__(__printf__,1,2))); NESTEDDEF void invalid(const char *fmt, ...) { va_list ap; oprf("Error: "); va_start(ap,fmt); vfprintf(opf,fmt,ap); va_end(ap); oprf("\n"); usage(); } NESTED int match_serial(CFWDREQ *r) { return(r->serial==ser); } NESTED int match_args(CFWDREQ *r) { return(fwdreq_matches_tcpfwd(r,&f)); } if (arglen < 1) usage(); ser = strtoul((char *)args,&ep,0); if (! *ep) { matchfn = &match_serial; } else { if (arglen < 2) usage(); if (!bcmp(args,"-L",2)) { f.flags = FF_DIR_LTOR; } else if (!bcmp(args,"-R",2)) { f.flags = FF_DIR_RTOL; } else { usage(); } parse_fwd(args+2,arglen-2,&f,&invalid); matchfn = &match_args; } any = 0; for (s=csessions;s;s=s->flink) { for (r=s->fwdreqs;r;r=r2) { r2 = r->flink; if ((*matchfn)(r)) { delete_forwarding(r); any = 1; } } } if (! any) oprf("No matching forwarding found\n"); ret:; } static void lcl_cmd_forward_drop(unsigned char *args, int arglen) { args=args;arglen=arglen; oprf("`forward drop' not yet implemented\n"); } #define FOO(name,bit) \ static void lcl_cmd_forward_##name##_y(unsigned char *args \ __attribute__((__unused__)), int arglen __attribute__(( \ __unused__))) { fwd |= (bit); fwdonce &= ~(bit); oprf("Set.\r\n"); }\ static void lcl_cmd_forward_##name##_1(unsigned char *args \ __attribute__((__unused__)), int arglen __attribute__(( \ __unused__))) { fwd |= (bit); fwdonce |= (bit); oprf("Set.\r\n"); }\ static void lcl_cmd_forward_##name##_n(unsigned char *args \ __attribute__((__unused__)), int arglen __attribute__(( \ __unused__))) { fwd &= ~(bit); oprf("Set.\r\n"); } FOO(agent,FWD_AGENT) FOO(x,FWD_X) #undef FOO static void lcl_cmd_forward(unsigned char *args, int arglen) { static CMD cmds[] = { { "?", &lcl_cmd_forward_help }, { "help", &lcl_cmd_forward_help }, { "list", &lcl_cmd_forward_list }, { "add", &lcl_cmd_forward_add }, { "delete", &lcl_cmd_forward_delete }, { "drop", &lcl_cmd_forward_drop }, { "+agent", &lcl_cmd_forward_agent_y }, { "1agent", &lcl_cmd_forward_agent_1 }, { "-agent", &lcl_cmd_forward_agent_n }, { "+x", &lcl_cmd_forward_x_y }, { "1x", &lcl_cmd_forward_x_1 }, { "-x", &lcl_cmd_forward_x_n }, { 0 } }; switch (cmd_and_args(args,arglen,&cmds[0])) { case CAA_OK: break; case CAA_EMPTY: oprf("\"forward help\" for help\n"); break; case CAA_NOTFOUND: oprf("Not found - \"forward help\" for help\n"); break; case CAA_AMBIGUOUS: oprf("Multiple matches - \"forward help\" for help\n"); break; } } static void lcl_cmd_suspend(unsigned char *args __attribute__((__unused__)), int arglen) { if (arglen > 0) { oprf("`suspend' takes no arguments\n"); return; } suspend_it(); } static void null_fd(int fd) { int dnfd; dnfd = open("/dev/null",O_RDWR,0); if (dnfd < 0) { fprintf(errf,"%s: /dev/null: %s\n",__progname,strerror(errno)); die(1); } if (dnfd != fd) { dup2(dnfd,fd); close(dnfd); } } static void background_self(void) { pid_t kid; restore_tty(); restore_blocking(); localmode = LCL_NO; null_fd(0); null_fd(1); null_fd(2); save_blocking(); set_nonblocking(); using_tty = 0; remove_poll_id(iid); fflush(0); kid = moussh_fork(); if (kid < 0) { fprintf(errf,"%s: fork: %s\n",__progname,strerror(errno)); exit(1); } if (kid == 0) return; exit(0); } static void lcl_cmd_bg(unsigned char *args __attribute__((__unused__)), int arglen) { if (arglen > 0) { oprf("`bg' takes no arguments\n"); return; } background_self(); } static void lcl_cmd_status(unsigned char *args __attribute__((__unused__)), int arglen) { CSESSION *s; int i; if (arglen > 0) { oprf("`status' takes no arguments\n"); return; } oprf("Escape character: "); if (in_esc < 0) { oprf("none"); } else { oprf("0x%02x (%s)",in_esc,esc_txt); } oprf("\nSuspend character: "); if (in_suspc < 0) { oprf("none"); } else { oprf("0x%02x (%s)",in_suspc,suspc_txt); } oprf("\nForwarding enables for new connections:"); for (i=0;fwdwhats[i].str;i++) { oprf(" %s=%s",fwdwhats[i].str,(fwd&fwdwhats[i].bit)?(fwdonce&fwdwhats[i].bit)?"once":"yes":"no"); } oprf("\nSessions:"); for (s=csessions;s;s=s->flink) oprf(" %u",s->serial); oprf("\n"); if (cursession) { oprf("Current session: %u\n",cursession->serial); oprf(" Advertised window: %d\n",cursession->advwin); oprf(" Channel: %d\n",cursession->chan); oprf(" Window sizes: write %d, read %d\n",(*ops->get_wwin)(cursession->chan),(*ops->get_rwin)(cursession->chan)); } } static CSESSION *nascent_csession(void) { CSESSION *s; s = malloc(sizeof(CSESSION)); s->flink = 0; s->blink = 0; s->refs = 1; s->cmd = 0; s->serial = ++session_serial; s->afwd_id = 0; s->xfwd_id = 0; s->state = SS__NIL; s->flags = 0; /* autobg_id needs no init */ /* flush_id needs no init */ dq_init(&s->odq,-1,0); dq_init(&s->edq,-1,0); s->chan = CHAN_NO_CHANNEL; s->advwin = -1; s->fwdreqs = 0; s->opendone = 0; s->xfreq = 0; return(s); } static int autobg_check(void *sv) { CSESSION *s; CFWDREQ *r; s = sv; for (r=s->fwdreqs;r;r=r->flink) { if (r->dir != FF_DIR_RTOL) continue; if (r->rtol.state == RFS_START) return(BLOCK_NIL); } remove_block_id(s->autobg_id); background_self(); return(BLOCK_LOOP); } static void session_up(CSESSION *s) { s->state = SS_UP; if (s->opendone) (*s->opendone)(s); (*ops->add_rwin)(s->chan,BUFFERSPACE); s->advwin = BUFFERSPACE; if (config_bool("auto-bg")) s->autobg_id = add_block_fn(&autobg_check,s); modecheck(); if (when_session_up) (*when_session_up)(); if (ops == &ops_nonshared) start_ssh_keepalive(); } static void shell_req_done(int ok, void *sv) { switch (ok) { default: panic("bad status"); break; case CREQ_OK: if (VERB(PROGRESS)) verb(PROGRESS,"Shell request accepted\n"); session_up(sv); break; case CREQ_FAIL: fprintf(errf,"%s: shell request refused\n",__progname); die(1); break; case CREQ_CLOSE: fprintf(errf,"%s: unexpected closedown\n",__progname); die(1); break; } } static void request_shell(CSESSION *s) { (*ops->send_req_reply)(s->chan,(*ops->req_hdr)(s->chan,cstr_to_rostr("shell")),&shell_req_done,s); } static void exec_req_done(int ok, void *sv) { switch (ok) { default: panic("bad status"); break; case CREQ_OK: if (VERB(PROGRESS)) verb(PROGRESS,"Exec request accepted\n"); session_up(sv); break; case CREQ_FAIL: fprintf(errf,"%s: exec request refused\n",__progname); die(1); break; case CREQ_CLOSE: fprintf(errf,"%s: unexpected closedown\n",__progname); die(1); break; } } static void request_exec(CSESSION *s) { unsigned char *opp; opp = (*ops->req_hdr)(s->chan,cstr_to_rostr("exec")); opp = put_string(opp,s->cmd,-1); (*ops->send_req_reply)(s->chan,opp,&exec_req_done,s); } static void request_run(CSESSION *s) { if (s->cmd) request_exec(s); else request_shell(s); } static void pty_req_done(int ok, void *sv) { switch (ok) { default: panic("bad status"); break; case CREQ_OK: if (VERB(PROGRESS)) verb(PROGRESS,"Pty request accepted\n"); request_run(sv); break; case CREQ_FAIL: fprintf(errf,"%s: pty request refused\n",__progname); die(1); break; case CREQ_CLOSE: fprintf(errf,"%s: unexpected closedown\n",__progname); die(1); break; } } static void request_pty(CSESSION *s) { unsigned char *opp; char *term; FILE *f; char *tm; int tml; opp = (*ops->req_hdr)(s->chan,cstr_to_rostr("pty-req")); term = getenv("TERM"); opp = put_string(opp,term?:"unknown",-1); if (using_tty) { opp = put_uint32(opp,wsz.ws_col); opp = put_uint32(opp,wsz.ws_row); opp = put_uint32(opp,wsz.ws_xpixel); opp = put_uint32(opp,wsz.ws_ypixel); f = fopen_alloc(&tm,&tml); #define FOO(n) do { putc(TTY_OP_##n,f); fput_uint32(f,(tio_i.c_cc[n]==_POSIX_VDISABLE)?255: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); fput_uint32(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); fput_uint32(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: case CS6: break; case CS7: putc(TTY_OP_CS7,f); fput_uint32(f,1); break; case CS8: putc(TTY_OP_CS8,f); fput_uint32(f,1); break; } #ifdef PARENB FOO(c_cflag,PARENB); #endif #ifdef PARODD FOO(c_cflag,PARODD); #endif /* No protocol field for IGNBRK BRKINT OXTABS ONOEOT CSTOPB CREAD HUPCL CLOCAL CRTSCTS CDTRCTS MDMBUF ECHOPRT ALTWERASE EXTPROC FLUSHO NOKERNINFO. Most of these shouldn't be passed anyway; for those that should, see below. */ /* Don't have a 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); fput_uint32(f,cfgetispeed(&tio_i)); putc(TTY_OP_OSPEED,f); fput_uint32(f,cfgetospeed(&tio_o)); putc(TTY_OP_END,f); fclose(f); opp = put_string(opp,tm,tml); (*ops->send_req_reply)(s->chan,opp,&pty_req_done,s); if (! config_bool("no-private")) { /* Pass ECHOPRT ALTWERASE NOKERNINFO CSIZE=CS5/CS6 */ opp = (*ops->req_hdr)(s->chan,cstr_to_rostr("missing-pty-modes@rodents.montreal.qc.ca")); *opp++ = MOUSETTY_OP_ECHOPRT; *opp++ = (tio_o.c_lflag & ECHOPRT) ? 1 : 0; *opp++ = MOUSETTY_OP_ALTWERASE; *opp++ = (tio_o.c_lflag & ALTWERASE) ? 1 : 0; *opp++ = MOUSETTY_OP_NOKERNINFO; *opp++ = (tio_o.c_lflag & NOKERNINFO) ? 1 : 0; *opp++ = MOUSETTY_OP_CS; switch (tio_i.c_cflag & CSIZE) { case CS5: *opp++ = 5; break; case CS6: *opp++ = 6; break; case CS7: *opp++ = 7; break; case CS8: *opp++ = 8; break; default: opp --; break; } (*ops->send_req_blind)(s->chan,opp); } } else { opp = put_uint32(opp,0); opp = put_uint32(opp,0); opp = put_uint32(opp,0); opp = put_uint32(opp,0); putc(TTY_OP_END,f); fclose(f); opp = put_string(opp,tm,tml); (*ops->send_req_reply)(s->chan,opp,&pty_req_done,s); } } static int want_pty(int def) { return( config_isset("request-pty") ? config_bool("request-pty") : def ); } /* * check_pend exists because we don't want to fire off the exec/shell * request right on the heels of all our other requests, because if we * have to fall back to other requests for agent or X forwarding, this * causes the exec/shell request to happen before all the forwarding * is set up. So instead we have flag bits indicating what's pending: * SF_APND Agent forwarding is pending * SF_XPND X forwarding is pending * SF_CPND exec/shell request is pending * This is perhaps a little confusing, in that "pending" has a * different meaning for CPND and APND/XPND - for APND/XPND, it means * that we've started the process but it hasn't yet run to completion; * for CPND, it means we want to do it but may need to wait for the * outcome of agent or X forwarding requests. */ static void check_pend(CSESSION *s) { if ((s->flags & (SF_APND|SF_XPND|SF_CPND)) == SF_CPND) { s->flags &= ~SF_CPND; if (want_pty(using_tty&&!s->cmd)) request_pty(s); else request_run(s); } } static void broken_agent_fwd_reply(int ok, void *sv) { CSESSION *s; s = sv; switch (ok) { default: panic("bad status"); break; case CREQ_OK: if (VERB(PROGRESS)) verb(PROGRESS,"Broken agent forwarding request accepted\n"); s->flags |= SF_BAF; break; case CREQ_FAIL: fprintf(errf,"%s: agent forwarding request refused\n",__progname); break; case CREQ_CLOSE: fprintf(errf,"%s: agent forwarding: unexpected closedown\n",__progname); break; } s->flags &= ~SF_APND; check_pend(s); } static void agent_fwd_reply(int ok, void *sv) { CSESSION *s; s = sv; switch (ok) { default: panic("bad status"); break; case CREQ_OK: if (VERB(PROGRESS)) verb(PROGRESS,"Agent forwarding request accepted\n"); s->flags |= SF_AFWD; s->flags &= ~SF_APND; break; case CREQ_FAIL: if (VERB(PROGRESS)) verb(PROGRESS,"Agent forwarding request failed\n"); if (! (s->flags & SF_AFJO)) { (*ops->send_req_reply)(s->chan,(*ops->req_hdr)(s->chan,cstr_to_rostr("auth-agent-req")),&broken_agent_fwd_reply,s); } else { broken_agent_fwd_reply(CREQ_FAIL,sv); } break; case CREQ_CLOSE: fprintf(errf,"%s: agent forwarding: unexpected closedown\n",__progname); s->flags &= ~SF_APND; break; } check_pend(s); } static void agent_fwd_reply_2(int ok, void *sv) { CSESSION *s; unsigned char *opp; s = sv; switch (ok) { default: panic("bad status"); break; case CREQ_OK: if (VERB(PROGRESS)) verb(PROGRESS,"Agent forwarding request accepted\n"); s->flags |= SF_AFWD; s->flags &= ~SF_APND; break; case CREQ_FAIL: if (! (s->flags & SF_AFJO)) { opp = (*ops->req_hdr)(s->chan,cstr_to_rostr("fixed-auth-agent-req@rodents.montreal.qc.ca")); opp = put_uint32(opp,s->afwd_id); (*ops->send_req_reply)(s->chan,opp,&agent_fwd_reply,s); } else { agent_fwd_reply(CREQ_FAIL,sv); } break; case CREQ_CLOSE: fprintf(errf,"%s: agent forwarding: unexpected closedown\n",__progname); s->flags &= ~SF_APND; break; } check_pend(s); } static void request_agent_fwd(CSESSION *s) { unsigned char *opp; if (fwdonce & FWD_AGENT) s->flags |= SF_AFJO; if (config_bool("no-private")) { if (s->flags & SF_AFJO) { fprintf(errf,"%s: just-once agent forwading incompatible with no-private\n",__progname); } else { s->flags |= SF_APND; agent_fwd_reply(CREQ_FAIL,s); } return; } opp = (*ops->req_hdr)(s->chan,cstr_to_rostr("fixed-auth-agent-req-2@rodents.montreal.qc.ca")); if (! s->afwd_id) s->afwd_id = nextfwdreq_serial(); opp = put_bool(opp,s->flags&SF_AFJO); opp = put_uint32(opp,s->afwd_id); (*ops->send_req_reply)(s->chan,opp,&agent_fwd_reply_2,s); s->flags |= SF_APND; } static void broken_x_fwd_reply(int ok, void *sv) { CSESSION *s; s = sv; switch (ok) { default: panic("bad status"); break; case CREQ_OK: if (VERB(PROGRESS)) verb(PROGRESS,"Broken X forwarding request accepted\n"); s->flags |= SF_BXF; break; case CREQ_FAIL: fprintf(errf,"%s: X forwarding request refused\n",__progname); break; case CREQ_CLOSE: fprintf(errf,"%s: X forwarding: unexpected closedown\n",__progname); break; } s->flags &= ~SF_XPND; check_pend(s); } static void x_fwd_reply(int ok, void *sv) { CSESSION *s; unsigned char *opp; int i; s = sv; switch (ok) { default: panic("bad status"); break; case CREQ_OK: if (VERB(PROGRESS)) verb(PROGRESS,"X forwarding request accepted\n"); s->flags |= SF_XFWD; s->flags &= ~SF_XPND; break; case CREQ_FAIL: #define R BXCOOKIE_RAND { char hexenc[((R+4)*2)+1]; if (VERB(PROGRESS)) verb(PROGRESS,"X forwarding request failed\n"); s->bxclen = R + 4; s->bxcookie = malloc(s->bxclen); random_data(s->bxcookie,R); put_uint32(s->bxcookie+R,s->xfwd_id); for (i=0;ibxcookie[i]); opp = (*ops->req_hdr)(s->chan,cstr_to_rostr("x11-req")); opp = put_bool(opp,s->flags&SF_XFJO); opp = put_string(opp,"MIT-MAGIC-COOKIE-1",-1); opp = put_string(opp,&hexenc[0],s->bxclen*2); opp = X_fwdreq_gen(opp,s->xfreq); (*ops->send_req_reply)(s->chan,opp,&broken_x_fwd_reply,s); } #undef R break; case CREQ_CLOSE: fprintf(errf,"%s: X forwarding: unexpected closedown\n",__progname); s->flags &= ~SF_XPND; break; } check_pend(s); } static void x_fwd_reply_2(int ok, void *sv) { CSESSION *s; unsigned char *opp; s = sv; switch (ok) { default: panic("bad status"); break; case CREQ_OK: if (VERB(PROGRESS)) verb(PROGRESS,"X forwarding request accepted\n"); s->flags |= SF_XFWD; s->flags &= ~SF_XPND; break; case CREQ_FAIL: if (! (s->flags & SF_XFJO)) { opp = (*ops->req_hdr)(s->chan,cstr_to_rostr("fixed-x11-req@rodents.montreal.qc.ca")); opp = put_uint32(opp,s->xfwd_id); opp = X_fwdreq_gen(opp,s->xfreq); (*ops->send_req_reply)(s->chan,opp,&x_fwd_reply,s); } else { x_fwd_reply(CREQ_FAIL,sv); } break; case CREQ_CLOSE: fprintf(errf,"%s: X forwarding: unexpected closedown\n",__progname); s->flags &= ~SF_XPND; break; } check_pend(s); } static void request_X_fwd(CSESSION *s) { unsigned char *opp; if (fwdonce & FWD_X) s->flags |= SF_XFJO; s->xfreq = X_forward_req(); if (s->xfreq == 0) return; s->flags |= SF_XPND; if (config_bool("no-private")) { x_fwd_reply(CREQ_FAIL,s); } else { opp = (*ops->req_hdr)(s->chan,cstr_to_rostr("fixed-x11-req-2@rodents.montreal.qc.ca")); if (! s->xfwd_id) s->xfwd_id = nextfwdreq_serial(); opp = put_bool(opp,s->flags&SF_XFJO); opp = put_uint32(opp,s->xfwd_id); opp = X_fwdreq_gen(opp,s->xfreq); (*ops->send_req_reply)(s->chan,opp,&x_fwd_reply_2,s); } } static void rem_opensucc(void *sv, int ch, ROSTR rest) { CSESSION *s; s = sv; if (ch != s->chan) panic("channel wrong"); if (rest.len) { fprintf(errf,"%s: protocol error: data (len=%d) in open confirmation\r\n",__progname,rest.len); die(1); } if (VERB(PROGRESS)) verb(PROGRESS,"Channel opened\n"); if ((fwd & FWD_AGENT) && agent_available()) request_agent_fwd(s); if (fwd & FWD_X) request_X_fwd(s); if (config_bool("just-die")) { session_up(s); die(0); } else if (config_bool("do-nothing")) { session_up(s); } else { s->flags |= SF_CPND; check_pend(s); } } static void rem_openfail(void *sv, int ch, unsigned int rcode, ROSTR reason, ROSTR lang __attribute__((__unused__))) { CSESSION *s; s = sv; if (ch != s->chan) panic("channel wrong"); fprintf(errf,"%s: channel open failed (",__progname); print_openfail_rcode(epf,rcode); eprf(")"); if (reason.len) { eprf(": "); print_escaped(epf,reason.data,reason.len,1024); } eprf("\n"); die(1); } static void rem_gotdata(void *sv, int cno, int ext, unsigned int code, const void *buf, int len) { CSESSION *s; DQ *q; s = sv; if (cno != s->chan) panic("channel wrong"); if (len == 0) { dq_weof(&s->odq); dq_weof(&s->edq); return; } if (ext) { switch (code) { case SSH_EXTENDED_DATA_STDERR: q = &s->edq; break; default: fprintf(errf,"%s: protocol error: extended data code %u\n",__progname,code); die(1); break; } } else { q = &s->odq; } dq_append(q,buf,len); s->advwin -= len; } static int rem_chanreq(void *sv, int cno, ROSTR req, int wantrep __attribute__((__unused__)), const void *rest, int restlen) { __label__ throw; CSESSION *s; int rv; NESTED void failure(const void *at __attribute__((__unused__)), const char *fmt, ...) { va_list ap; int i; eprf("%s: protocol error: unparseable `%.*s': ",__progname,req.len,req.data); va_start(ap,fmt); vfprintf(epf,fmt,ap); va_end(ap); eprf("\r\n"); for (i=0;ichan) panic("channel wrong"); rv = CHANREQRET_OK; 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); } else if (str_equalcC(req,"keepalive@openssh.com")) { return(CHANREQRET_FAIL); } return(CHANREQRET_UNK); } static void rem_closed(void *sv, int cno, int final) { CSESSION *s; s = sv; if (cno != s->chan) panic("channel wrong"); if (VERB(PROGRESS)) verb(PROGRESS,"Session channel closed\r\n"); if (!final && !(*ops->close)(cno)) panic("close failed"); die_on_flush(s); } static const CHANOPS rl_ops = { &rem_opensucc, &rem_openfail, &rem_gotdata, 0, &rem_chanreq, &rem_closed }; static void lcl_cmd_open(unsigned char *args, int arglen) { CSESSION *s; s = nascent_csession(); if (arglen > 0) s->cmd = blk_to_cstr(args,arglen); s->state = SS_OPEN; dq_init(&s->odq,1,"stdout"); ocount ++; dq_init(&s->edq,2,"stderr"); ecount ++; s->chan = (*ops->open_send)((*ops->open_hdr)(cstr_to_rostr("session")),&rl_ops,s); DLL_LINK_HEAD(s,csessions); cursession = s; resume_session(); } static void debug_reply(int status, void *arg __attribute__((__unused__)), const void *rest, int restlen) { STR reply; NESTED void failure(const void *errat __attribute__((__unused__)), const char *fmt, ...) { va_list ap; fprintf(errf,"%s: protocol error: bad debug reply: ",__progname); va_start(ap,fmt); vfprintf(errf,fmt,ap); va_end(ap); fprintf(errf,"\r\n"); die(1); } switch (status) { case GREQ_OK: parse_data(rest,restlen,&failure,PP_STRING(&reply),PP_ENDSHERE); if (localmode == LCL_CMD) { oprf("\r\n"); fwrite(reply.data,1,reply.len,opf); reprompt_state = RP_DRAIN; } else { dq_append(&codq,reply.data,reply.len); } free_str(reply); break; case GREQ_FAIL: oprf("debug request rejected by server\r\n"); break; default: panic("bad status"); break; } } static void lcl_cmd_rekey(unsigned char *args __attribute__((__unused__)), int arglen __attribute__((__unused__))) { set_rekey(bpp); } static void lcl_cmd_debug(unsigned char *args, int arglen) { unsigned char *opp; opp = (*ops->g_req_hdr)(cstr_to_rostr("server-debug@rodents.montreal.qc.ca")); opp = put_string(opp,args,arglen); (*ops->g_send_req)(opp,&debug_reply,0); } static void lcl_cmd_alg_help(unsigned char *args __attribute__((__unused__)), int arglen __attribute__((__unused__))) { oprf("\ alg ? - print this help text\r\n\ alg help - print this help text\r\n\ alg show - show current algorithm lists\r\n\ alg kex ... - manipulate key-exchange algorithm list\r\n\ alg enc ... - manipulate encryption algorithm lists\r\n\ alg mac ... - manipulate MAC algorithm lists\r\n\ alg comp ... - manipulate compression algorithm lists\r\n\ alg c2s-enc ... - manipulate client->server encryption algorithm list\r\n\ alg s2c-enc ... - manipulate server->client encryption algorithm list\r\n\ alg c2s-mac ... - manipulate client->server MAC algorithm list\r\n\ alg s2c-mac ... - manipulate server->client MAC algorithm list\r\n\ alg c2s-comp ... - manipulate client->server comprsesion algorithm list\r\n\ alg s2c-comp ... - manipulate server->client compression algorithm list\r\n\ \r\n\ Unambiguous abbreviations are accepted.\r\n"); } static void lcl_cmd_alg_show(unsigned char *args __attribute__((__unused__)), int arglen) { if (arglen > 0) { oprf("`alg show' takes no arguments\n"); return; } if (! bpp) { oprf("Can't control algorithms when using connection sharing\n"); } else { oprf("Last-negotiated algorithms:\n"); oprf(" kex: %s\n",bpp->kex->name); oprf(" hk: %s\n",bpp->hk->name); if (bpp->w_enc == bpp->r_enc) { oprf(" enc: %s\n",bpp->w_enc->name); } else { oprf(" enc c->s: %s\n",bpp->w_enc->name); oprf(" enc s->c: %s\n",bpp->r_enc->name); } if (bpp->w_mac == bpp->r_mac) { oprf(" mac: %s\n",bpp->w_mac->name); } else { oprf(" mac c->s: %s\n",bpp->w_mac->name); oprf(" mac s->c: %s\n",bpp->r_mac->name); } if (bpp->w_comp == bpp->r_comp) { oprf(" comp: %s\n",bpp->w_comp->name); } else { oprf(" comp c->s: %s\n",bpp->w_comp->name); oprf(" comp s->c: %s\n",bpp->r_comp->name); } oprf("Current offer lists:\n"); oprf(" kex:"); alglist_dump(&algs_kex,opf); oprf("\n"); oprf(" hk:"); alglist_dump(&algs_hk,opf); oprf("\n"); if (alglists_identical(&algs_enc_c2s,&algs_enc_s2c)) { oprf(" enc:"); alglist_dump(&algs_enc_c2s,opf); oprf("\n"); } else { oprf(" enc c->s:"); alglist_dump(&algs_enc_c2s,opf); oprf("\n"); oprf(" enc s->c:"); alglist_dump(&algs_enc_s2c,opf); oprf("\n"); } if (alglists_identical(&algs_mac_c2s,&algs_mac_s2c)) { oprf(" mac:"); alglist_dump(&algs_mac_c2s,opf); oprf("\n"); } else { oprf(" mac c->s:"); alglist_dump(&algs_mac_c2s,opf); oprf("\n"); oprf(" mac s->c:"); alglist_dump(&algs_mac_s2c,opf); oprf("\n"); } if (alglists_identical(&algs_comp_c2s,&algs_comp_s2c)) { oprf(" comp:"); alglist_dump(&algs_comp_c2s,opf); oprf("\n"); } else { oprf(" comp c->s:"); alglist_dump(&algs_comp_c2s,opf); oprf("\n"); oprf(" comp s->c:"); alglist_dump(&algs_comp_s2c,opf); oprf("\n"); } } } static void lcl_cmd_alg_kex(unsigned char *args, int arglen __attribute__((__unused__))) { algset(&algs_kex,args,1,opf); alglist_expand_default(&algs_kex); } static void lcl_cmd_alg_hk(unsigned char *args, int arglen __attribute__((__unused__))) { algset(&algs_hk,args,1,opf); alglist_expand_default(&algs_hk); } static void lcl_cmd_alg_enc(unsigned char *args, int arglen __attribute__((__unused__))) { algset(&algs_enc_c2s,args,1,opf); alglist_expand_default(&algs_enc_c2s); algset(&algs_enc_s2c,args,0,opf); alglist_expand_default(&algs_enc_s2c); } static void lcl_cmd_alg_mac(unsigned char *args, int arglen __attribute__((__unused__))) { algset(&algs_mac_c2s,args,1,opf); alglist_expand_default(&algs_mac_c2s); algset(&algs_mac_s2c,args,0,opf); alglist_expand_default(&algs_mac_s2c); } static void lcl_cmd_alg_comp(unsigned char *args, int arglen __attribute__((__unused__))) { algset(&algs_comp_c2s,args,1,opf); alglist_expand_default(&algs_comp_c2s); algset(&algs_comp_s2c,args,0,opf); alglist_expand_default(&algs_comp_s2c); } static void lcl_cmd_alg_enc_c2s(unsigned char *args, int arglen __attribute__((__unused__))) { algset(&algs_enc_c2s,args,1,opf); alglist_expand_default(&algs_enc_c2s); } static void lcl_cmd_alg_enc_s2c(unsigned char *args, int arglen __attribute__((__unused__))) { algset(&algs_enc_s2c,args,1,opf); alglist_expand_default(&algs_enc_s2c); } static void lcl_cmd_alg_mac_c2s(unsigned char *args, int arglen __attribute__((__unused__))) { algset(&algs_mac_c2s,args,1,opf); alglist_expand_default(&algs_mac_c2s); } static void lcl_cmd_alg_mac_s2c(unsigned char *args, int arglen __attribute__((__unused__))) { algset(&algs_mac_s2c,args,1,opf); alglist_expand_default(&algs_mac_s2c); } static void lcl_cmd_alg_comp_c2s(unsigned char *args, int arglen __attribute__((__unused__))) { algset(&algs_comp_c2s,args,1,opf); alglist_expand_default(&algs_comp_c2s); } static void lcl_cmd_alg_comp_s2c(unsigned char *args, int arglen __attribute__((__unused__))) { algset(&algs_comp_s2c,args,1,opf); alglist_expand_default(&algs_comp_s2c); } static void lcl_cmd_alg(unsigned char *args, int arglen) { static CMD cmds[] = { { "?", &lcl_cmd_alg_help }, { "help", &lcl_cmd_alg_help }, { "show", &lcl_cmd_alg_show }, { "kex", &lcl_cmd_alg_kex }, { "hk", &lcl_cmd_alg_hk }, { "enc", &lcl_cmd_alg_enc }, { "mac", &lcl_cmd_alg_mac }, { "comp", &lcl_cmd_alg_comp }, { "c2s-enc", &lcl_cmd_alg_enc_c2s }, { "s2c-enc", &lcl_cmd_alg_enc_s2c }, { "c2s-mac", &lcl_cmd_alg_mac_c2s }, { "s2c-mac", &lcl_cmd_alg_mac_s2c }, { "c2s-comp", &lcl_cmd_alg_comp_c2s }, { "s2c-comp", &lcl_cmd_alg_comp_s2c }, { 0 } }; switch (cmd_and_args(args,arglen,&cmds[0])) { case CAA_OK: break; case CAA_EMPTY: oprf("\"alg help\" for help\n"); break; case CAA_NOTFOUND: oprf("Not found - \"alg help\" for help\n"); break; case CAA_AMBIGUOUS: oprf("Multiple matches - \"alg help\" for help\n"); break; } } static void local_command(void) { static CMD cmds[] = { { "?", &lcl_cmd_help }, { "help", &lcl_cmd_help }, { "continue", &lcl_cmd_continue }, { "quit", &lcl_cmd_quit }, { "forward", &lcl_cmd_forward }, { "suspend", &lcl_cmd_suspend }, { "bg", &lcl_cmd_bg }, { "status", &lcl_cmd_status }, { "open", &lcl_cmd_open }, { "rekey", &lcl_cmd_rekey }, { "alg", &lcl_cmd_alg }, { "debug", &lcl_cmd_debug }, { 0 } }; switch (cmd_and_args(cmdbuf,cmdlen,&cmds[0])) { case CAA_OK: case CAA_EMPTY: break; case CAA_NOTFOUND: oprf("Not found - ? for help\r\n"); break; case CAA_AMBIGUOUS: oprf("Multiple matches - ? for help\r\n"); break; } if (localmode == LCL_CMD) oprf("%s> ",__progname); cmdlen = 0; } static void cmd_input(int c) { switch (c) { case '\r': case '\n': cmd_input('\0'); cmdlen --; local_command(); break; default: if (cmdlen >= cmdalloc) cmdbuf = realloc(cmdbuf,cmdalloc=cmdlen+16); cmdbuf[cmdlen++] = c; break; } } static int flush_out(void *arg __attribute__((__unused__))) { int oolen; int oelen; oolen = oq_qlen(&codq.q); oelen = oq_qlen(&cedq.q); fflush(0); return( ((oolen != oq_qlen(&codq.q)) || (oelen != oq_qlen(&cedq.q))) ? BLOCK_LOOP : BLOCK_NIL ); } static int rtest_i(int id __attribute__((__unused__)), void *arg __attribute__((__unused__))) { return(input_for_session(cursession)); } static void rd_i_pipe(int id __attribute__((__unused__)), void *arg __attribute__((__unused__))) { char buf[8192]; int n; int r; if (! input_for_session(cursession)) return; n = (*ops->get_wwin)(cursession->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(errf,"%s: stdin read error: %s\n",__progname,strerror(errno)); die(1); } if (r == 0) { (*ops->send_eof)(cursession->chan); cursession->flags |= SF_IEOF; } else { (*ops->send_data)(cursession->chan,0,0,&buf[0],r); } } static void command_mode(void) { localmode = LCL_CMD; bfid = add_block_fn(&flush_out,0); modecheck(); cmdlen = 0; if (cursession) cursession->flags |= SF_SUSP; oprf("\n%s> ",__progname); } static void rd_i_tty(int id __attribute__((__unused__)), void *arg __attribute__((__unused__))) { unsigned char buf[8192]; int r; int i; int i0; int beeped; NESTED void beep(void) { if (beeped) return; oprf("\a"); beeped = 1; } NESTED void data_for_session(const void *buf, int len) { int win; if (! input_for_session(cursession)) { beep(); return; } win = (*ops->get_wwin)(cursession->chan); if (win >= len) { (*ops->send_data)(cursession->chan,0,0,buf,len); } else { (*ops->send_data)(cursession->chan,0,0,buf,win); beep(); } } r = read(0,&buf[0],sizeof(buf)); if (r < 0) { switch (errno) { case EINTR: case EWOULDBLOCK: return; break; } eprf("%s: stdin read error: %s\r\n",__progname,strerror(errno)); die(1); } if (r == 0) { oprf("\a"); return; } beeped = 0; i0 = -1; for <"inputloop"> (i=0;i i0) data_for_session(&buf[i0],i-i0); i0 = -1; localmode = LCL_ESC; cmdlen = 0; } in_nl = 0; break; } break; case LCL_ESC: if (buf[i] == in_esc) { localmode = LCL_NO; i0 = i; break; } else if (buf[i] == in_suspc) { localmode = LCL_NO; suspend_it(); break; } switch (buf[i]) { case '.': quit_cur(); break <"inputloop">; case '?': oprf("%s?\r\n",esc_txt); oprf("%s%-4s- send %s\r\n",esc_txt,esc_txt,esc_txt); if ((in_esc != '.') && (in_suspc != '.')) oprf("%s. - end connection\r\n",esc_txt); if ((in_esc != '?') && (in_suspc != '?')) oprf("%s? - print this message\r\n",esc_txt); if ((in_esc != '%') && (in_suspc != '%')) oprf("%s%% - local command line\r\n",esc_txt); if ((in_esc != '&') && (in_suspc != '&')) oprf("%s& - background\r\n",esc_txt); if ((in_esc != 'z') && (in_suspc != 'z')) oprf("%sz - suspend\r\n",esc_txt); if ((in_esc != in_suspc) && (in_suspc != -1)) oprf("%s%-4s- suspend\r\n",esc_txt,suspc_txt); fflush(opf); localmode = LCL_NO; break; case '%': command_mode(); break; case '&': background_self(); break; case 'z': localmode = LCL_NO; suspend_it(); break; default: localmode = LCL_NO; i0 = i; if ((i0 > 0) && (buf[i0-1] == in_esc)) { i0 --; } else { unsigned char c; c = in_esc; data_for_session(&c,1); } #if 0 oprf("[%s",esc_txt); if ((in_esc != '.') && (in_suspc != '.')) oprf(" ."); if ((in_esc != '?') && (in_suspc != '?')) oprf(" ?"); if ((in_esc != '%') && (in_suspc != '%')) oprf(" %%"); if ((in_esc != 'z') && (in_suspc != 'z')) oprf(" z"); if ((in_esc != in_suspc) && (in_suspc != -1)) oprf(" %s",suspc_txt); oprf("]"); #endif break; } break; case LCL_CMD: cmd_input(buf[i]); break; } } if (i0 >= 0) { if (i == i0) panic("zero-length block"); data_for_session(&buf[i0],i-i0); } } static void wr_oe(DQ *cq, DQ *sq, int *countp) { DQ *q; int w; int goteof; int n; NESTED int ateof(void *vp __attribute__((__unused__)), int i __attribute__((__unused__))) { goteof = 1; return(0); } if (using_tty && oq_nonempty(&cq->q)) { q = cq; } else if ((localmode != LCL_CMD) && cursession && oq_nonempty(&sq->q)) { q = sq; } else { return; } if (! q) return; if (oq_empty(&q->q)) return; goteof = 0; w = oq_writev(&q->q,q->fd,&ateof); if (goteof) { if (! --*countp) close(q->fd); q->fd = -1; return; } if (w < 0) { switch (errno) { case EINTR: case EWOULDBLOCK: return; break; } fprintf(errf,"%s: output write error: %s\n",__progname,strerror(errno)); die(1); } oq_dropdata(&q->q,w); if ( (q == sq) && (cursession->state == SS_UP) && !(cursession->flags & SF_OEOF) ) { n = cursession->advwin + oq_qlen(&cursession->odq.q) + oq_qlen(&cursession->edq.q); if (n < BUFFERSPACE/2) { (*ops->add_rwin)(cursession->chan,BUFFERSPACE-n); cursession->advwin += BUFFERSPACE-n; } } } static void wr_o(int id __attribute__((__unused__)), void *arg __attribute__((__unused__))) { wr_oe(&codq,cursession?&cursession->odq:0,&ocount); } static void wr_e(int id __attribute__((__unused__)), void *arg __attribute__((__unused__))) { wr_oe(&cedq,cursession?&cursession->edq:0,&ecount); } static int wtest_o(int id __attribute__((__unused__)), void *arg __attribute__((__unused__))) { return( (using_tty && oq_nonempty(&codq.q)) || ( (localmode != LCL_CMD) && cursession && oq_nonempty(&cursession->odq.q) ) ); } static int wtest_e(int id __attribute__((__unused__)), void *arg __attribute__((__unused__))) { return( (using_tty && oq_nonempty(&cedq.q)) || ( (localmode != LCL_CMD) && cursession && oq_nonempty(&cursession->edq.q) ) ); } static int pf_write(void *dqv, const char *data, int len) { dq_append(dqv,data,len); return(len); } static CSESSION *start_session(void) { CSESSION *s; s = nascent_csession(); if (argsbegin) { FILE *f; int i; f = fopen_alloc(&s->cmd,0); for (i=0;istate = SS_OPEN; dq_init(&s->odq,1,"stdout"); ocount ++; dq_init(&s->edq,2,"stderr"); ecount ++; s->chan = (*ops->open_send)((*ops->open_hdr)(cstr_to_rostr("session")),&rl_ops,s); DLL_LINK_HEAD(s,csessions); return(s); } static int maybe_exit(void *bv) { CSESSION *s; CSESSION *s2; BPP *b; int rv; rv = BLOCK_NIL; b = bv; for (s=csessions;s;s=s2) { s2 = s->flink; if (s->state == SS_DEAD) { rv = BLOCK_LOOP; DLL_UNLINK(s,csessions); if (csessions) oprf("Session %u closed.\n",s->serial); if (cursession == s) cursession = 0; while (s->fwdreqs) { CFWDREQ *r; r = s->fwdreqs; DLL_UNLINK(r,s->fwdreqs); cfwdreq_deref(r); } csession_deref(s); } } if ( !csessions && !cfwdconns && !cfwdagents && (localmode != LCL_CMD) && oq_empty(&codq.q) && oq_empty(&cedq.q) && (b ? oq_empty(&b->output) : share_oq_empty()) ) { restore_tty(); restore_blocking(); if (using_tty) printf("Connection to %s closed.\n",rem_id.name); if (config_bool("share-server")) share_unlisten(); exit(0); } if ((localmode != LCL_CMD) && !cursession) { cursession = csessions; if (using_tty) command_mode(); if (cursession) rv = BLOCK_LOOP; } return(rv); } static int reprompt_block(void *arg __attribute__((__unused__))) { struct termios t; if (reprompt_state == RP_NONE) return(BLOCK_NIL); if (!oq_empty(&codq.q) || !oq_empty(&cedq.q)) return(BLOCK_NIL); switch (reprompt_state) { case RP_NONE: break; case RP_DRAIN: oprf("%s> ",__progname); reprompt_state = RP_PROMPT; return(BLOCK_LOOP); break; case RP_PROMPT: t = tio_i; t.c_lflag |= PENDIN; tcsetattr(0,TCSANOW|TCSASOFT,&t); reprompt_state = RP_NONE; break; } return(BLOCK_NIL); } void client_setup(void) { init_polling(); if (! config_isset("user")) config_set_str("user",getenv("USER")); if (! config_isset("home")) config_set_str("home",getenv("HOME")); if (! config_isset("shell")) config_set_str("shell",getenv("SHELL")); if (config_bool("auto-share")) { const char *lp; const char *p; const char *h; lp = config_str("auto-share-path"); p = config_str("share-path"); h = config_str("connect-to"); if (! h) h = config_str("host"); if (!h || !*h) { fprintf(errf,"%s: auto-share mode requires a host to connect to\n",__progname); exit(1); } if (!p || !*p) { fprintf(errf,"%s: auto-share mode requires a rendezvous path\n",__progname); exit(1); } if (!lp || !*lp) { fprintf(errf,"%s: auto-share mode requires a lock path\n",__progname); exit(1); } share_auto(p,lp); } if (config_bool("share-client")) { const char *p; ops = &ops_shared; bpp = 0; p = config_str("share-path"); if (! p) { fprintf(errf,"%s: no shared-client path specified\n",__progname); exit(1); } share_open(p,&rem_id); } else { ops = &ops_nonshared; bpp = malloc(sizeof(BPP)); bpp_setup(bpp,0); openconn(bpp); sendversion(bpp); push_layer(bpp,&layer_base); push_layer(bpp,&layer_d_i_d); push_layer(bpp,&layer_transport_c); push_layer(bpp,&layer_userauth_conn_c); push_layer(bpp,&layer_channels); push_layer(bpp,&layer_catchall); bpp_add_poll(bpp); if (config_bool("share-server")) { const char *p; p = config_str("share-path"); if (! p) p = config_str("host"); if (! p) { fprintf(errf,"%s: no shared-client path specified\n",__progname); exit(1); } share_listen(p,&rem_id); } } (*ops->setgbl)(&gbl_ops_c); csessions = 0; cfwdconns = 0; cfwdagents = 0; if (config_bool("no-input")) null_fd(0); using_tty = want_pty(!argsbegin) && save_tty(); ocount = 0; ecount = 0; if (using_tty) { setup_esc(); dq_init(&codq,1,"stdout"); ocount ++; dq_init(&cedq,2,"stderr"); ecount ++; opf = fwopen(&codq,&pf_write); epf = fwopen(&cedq,&pf_write); reprompt_id = add_block_fn(&reprompt_block,0); setlinebuf(opf); setbuf(epf,0); iid = add_poll_fd(0,&rwtest_always,&rwtest_never,&rd_i_tty,0,0); if (pipe(&winchpipe[0]) < 0) { fprintf(errf,"%s: pipe: %s\n",__progname,strerror(errno)); exit(1); } fcntl(winchpipe[0],F_SETFL,fcntl(winchpipe[0],F_GETFL,0)|O_NONBLOCK); add_poll_fd(winchpipe[0],&rwtest_always,&rwtest_never,&rd_winch,0,0); signal(SIGWINCH,&handle_winch); } else { iid = add_poll_fd(0,&rtest_i,&rwtest_never,&rd_i_pipe,0,0); } save_blocking(); set_nonblocking(); oid = add_poll_fd(1,&rwtest_never,&wtest_o,0,&wr_o,0); eid = add_poll_fd(2,&rwtest_never,&wtest_e,0,&wr_e,0); cursession = start_session(); cursession->opendone = &initial_forwardings; add_block_fn(&maybe_exit,bpp); } const PEERID *client_peer_id(void) { return(&rem_id); } void client_session_up(void (*fn)(void)) { when_session_up = fn; }