/* This file is in the public domain. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include extern const char *__progname; #include "x.h" #include "pp.h" #include "oq.h" #include "dll.h" #include "util.h" #include "msgs.h" #include "errf.h" #include "panic.h" #include "agent.h" #include "config.h" #include "nested.h" #include "escaped.h" #include "pkt-util.h" #include "channels.h" #include "pollloop.h" #include "keepalive.h" #include "transport.h" #include "stdio-util.h" #include "withscopeid.h" #include "agent-server.h" #include "userauth-conn.h" #include "server.h" #define BUFFERSPACE 65536 typedef enum { FAS_OPEN = 1, FAS_RUN, FAS_DEAD, } SFWASTATE; typedef enum { SFCT_UNSET = 1, SFCT_TCP, SFCT_X, } SFWCTYPE; typedef enum { XK_NONE = 1, XK_TCP, XK_LOCAL } XKIND; typedef struct acc ACC; typedef struct ssession SSESSION; typedef struct ss_fd SS_FD; typedef struct session_setup SESSION_SETUP; typedef struct pty_req PTY_REQ; typedef struct env_req ENV_REQ; typedef struct sfwdreq SFWDREQ; typedef struct sfwdlfd SFWDLFD; typedef struct sfwdconn SFWDCONN; typedef struct sfwdagent SFWDAGENT; typedef struct sipstate SIPSTATE; struct sipstate { SFWDCONN *c; struct addrinfo *ai0; struct addrinfo *ai; int sock; int id; } ; struct sfwdagent { SFWDAGENT *flink; SFWDAGENT *blink; int fd; int chan; int id; int advwin; OQ oq; SFWASTATE state; } ; struct sfwdconn { SFWDCONN *flink; SFWDCONN *blink; SFWCTYPE type; union { struct { SFWDREQ *req; char *clientaddr; unsigned int clientport; char *toaddr; unsigned int toport; } tcp; struct { XKIND kind; union { struct { char *clientaddr; unsigned int clientport; } tcp; } ; unsigned char *chdr; int cptr; int clen; SSESSION *s; } x; } ; int fd; int chan; int id; int advwin; int leof; int reof; OQ oq; } ; struct sfwdlfd { SFWDLFD *flink; SFWDLFD *blink; SFWDREQ *req; int fd; int id; char *text; void *addr; int addrlen; } ; struct sfwdreq { SFWDREQ *flink; SFWDREQ *blink; int refs; SFWDLFD *lfds; char *text; int broken; int id; } ; struct ss_fd { int fd; int eof; int id; } ; struct ssession { SSESSION *flink; SSESSION *blink; int chan; unsigned int flags; #define SSF_STARTED 0x00000001 #define SSF_PTY 0x00000002 #define SSF_ONEAUTH 0x00000004 union { struct { int mfd; char *dev; int id; int leof; int reof; } pty; struct { SS_FD i; SS_FD o; SS_FD e; } pipe; } ; void *auth_agent; char *auth_env; int broken_auth; unsigned int auth_id; SFXREQ *x_fwd; unsigned int x_id; int x_broken; STR x_authproto; STR x_authcookie; SESSION_SETUP *setup; pid_t kid; int advwin; OQ oq; int abortid; } ; struct env_req { ENV_REQ *flink; ENV_REQ *blink; STR var; STR val; } ; struct pty_req { unsigned int x_c; unsigned int y_c; unsigned int x_p; unsigned int y_p; struct termios val; struct termios set; } ; struct session_setup { PTY_REQ *ptyreq; /*X11_REQ *x11req;*/ ENV_REQ *envreq; } ; struct acc { int n; struct sockaddr *sa; char *accstr; int fd; int id; } ; static ACC **accs; static int nacc; static struct sockaddr_storage lcl; static socklen_t lcllen; static struct sockaddr_storage rem; static socklen_t remlen; static SSESSION *sessions; static void (*chanreq_parse_fail)(const void *, const char *, ...); static LAYER *ualayer; static int deathwatcher = 0; static int deathpipe[2]; static SFWDREQ *sfwdreqs; static SFWDCONN *sfwdconns; static SFWDAGENT *sfwdagents; static int daemon_idle_id; static void set_iflag(struct termios *t, unsigned int s, unsigned int c) { t->c_iflag = (t->c_iflag & ~c) | s; } static void set_lflag(struct termios *t, unsigned int s, unsigned int c) { t->c_lflag = (t->c_lflag & ~c) | s; } static void set_oflag(struct termios *t, unsigned int s, unsigned int c) { t->c_oflag = (t->c_oflag & ~c) | s; } static void set_cflag(struct termios *t, unsigned int s, unsigned int c) { t->c_cflag = (t->c_cflag & ~c) | s; } static void set_tty_modes_std(PTY_REQ *r, const void *rest, int restlen) { const void *rest2; int restlen2; unsigned int v; while (restlen) { rest2 = 0; switch (*(const unsigned char *)rest) { case TTY_OP_END: restlen2 = 0; break; { int ccx; { #ifdef VINTR case TTY_OP_VINTR: ccx = VINTR; } if (0) { #endif #ifdef VQUIT case TTY_OP_VQUIT: ccx = VQUIT; } if (0) { #endif #ifdef VERASE case TTY_OP_VERASE: ccx = VERASE; } if (0) { #endif #ifdef VKILL case TTY_OP_VKILL: ccx = VKILL; } if (0) { #endif #ifdef VEOF case TTY_OP_VEOF: ccx = VEOF; } if (0) { #endif #ifdef VEOL case TTY_OP_VEOL: ccx = VEOL; } if (0) { #endif #ifdef VEOL2 case TTY_OP_VEOL2: ccx = VEOL2; } if (0) { #endif #ifdef VSTART case TTY_OP_VSTART: ccx = VSTART; } if (0) { #endif #ifdef VSTOP case TTY_OP_VSTOP: ccx = VSTOP; } if (0) { #endif #ifdef VSUSP case TTY_OP_VSUSP: ccx = VSUSP; } if (0) { #endif #ifdef VDSUSP case TTY_OP_VDSUSP: ccx = VDSUSP; } if (0) { #endif #ifdef VREPRINT case TTY_OP_VREPRINT: ccx = VREPRINT; } if (0) { #endif #ifdef VWERASE case TTY_OP_VWERASE: ccx = VWERASE; } if (0) { #endif #ifdef VLNEXT case TTY_OP_VLNEXT: ccx = VLNEXT; } if (0) { #endif #ifdef VFLUSH case TTY_OP_VFLUSH: ccx = VFLUSH; } if (0) { #endif #ifdef VSWTCH case TTY_OP_VSWTCH: ccx = VSWTCH; } if (0) { #endif #ifdef VSTATUS case TTY_OP_VSTATUS: ccx = VSTATUS; } if (0) { #endif #ifdef VDISCARD case TTY_OP_VDISCARD: ccx = VDISCARD; } if (0) { #endif } parse_data(rest,restlen,chanreq_parse_fail, PP_IGNORE(1), PP_UINT32(&v), PP_REST(&rest2,&restlen2) ); r->set.c_cc[ccx] = 1; r->val.c_cc[ccx] = (v == 255) ? _POSIX_VDISABLE : v; } break; { unsigned int bit; void (*setbit)(struct termios *, unsigned int, unsigned int); { #ifdef ICRNL case TTY_OP_ICRNL: bit = ICRNL; } if (0) { #endif #ifdef IGNCR case TTY_OP_IGNCR: bit = IGNCR; } if (0) { #endif #ifdef IGNPAR case TTY_OP_IGNPAR: bit = IGNPAR; } if (0) { #endif #ifdef IMAXBEL case TTY_OP_IMAXBEL: bit = IMAXBEL; } if (0) { #endif #ifdef INLCR case TTY_OP_INLCR: bit = INLCR; } if (0) { #endif #ifdef INPCK case TTY_OP_INPCK: bit = INPCK; } if (0) { #endif #ifdef ISTRIP case TTY_OP_ISTRIP: bit = ISTRIP; } if (0) { #endif #ifdef IXANY case TTY_OP_IXANY: bit = IXANY; } if (0) { #endif #ifdef IXOFF case TTY_OP_IXOFF: bit = IXOFF; } if (0) { #endif #ifdef IXON case TTY_OP_IXON: bit = IXON; } if (0) { #endif #ifdef PARMRK case TTY_OP_PARMRK: bit = PARMRK; } if (0) { #endif } setbit = &set_iflag; if (0) { { #ifdef ECHO case TTY_OP_ECHO: bit = ECHO; } if (0) { #endif #ifdef ECHOCTL case TTY_OP_ECHOCTL: bit = ECHOCTL; } if (0) { #endif #ifdef ECHOE case TTY_OP_ECHOE: bit = ECHOE; } if (0) { #endif #ifdef ECHOK case TTY_OP_ECHOK: bit = ECHOK; } if (0) { #endif #ifdef ECHOKE case TTY_OP_ECHOKE: bit = ECHOKE; } if (0) { #endif #ifdef ECHONL case TTY_OP_ECHONL: bit = ECHONL; } if (0) { #endif #ifdef ICANON case TTY_OP_ICANON: bit = ICANON; } if (0) { #endif #ifdef IEXTEN case TTY_OP_IEXTEN: bit = IEXTEN; } if (0) { #endif #ifdef ISIG case TTY_OP_ISIG: bit = ISIG; } if (0) { #endif #ifdef NOFLSH case TTY_OP_NOFLSH: bit = NOFLSH; } if (0) { #endif #ifdef TOSTOP case TTY_OP_TOSTOP: bit = TOSTOP; } if (0) { #endif } setbit = &set_lflag; } if (0) { { #ifdef OPOST case TTY_OP_OPOST: bit = OPOST; } if (0) { #endif #ifdef ONLCR case TTY_OP_ONLCR: bit = ONLCR; } if (0) { #endif #ifdef OCRNL case TTY_OP_OCRNL: bit = OCRNL; } if (0) { #endif #ifdef ONOCR case TTY_OP_ONOCR: bit = ONOCR; } if (0) { #endif #ifdef ONLRET case TTY_OP_ONLRET: bit = ONLRET; } if (0) { #endif } setbit = &set_oflag; } if (0) { { #ifdef PARENB case TTY_OP_PARENB: bit = PARENB; } if (0) { #endif #ifdef PARODD case TTY_OP_PARODD: bit = PARODD; } if (0) { #endif } setbit = &set_cflag; } parse_data(rest,restlen,chanreq_parse_fail, PP_IGNORE(1), PP_UINT32(&v), PP_REST(&rest2,&restlen2) ); (*setbit)(&r->val,v?bit:0,v?0:bit); (*setbit)(&r->set,bit,0); } break; { unsigned int csval; { #ifdef CS7 case TTY_OP_CS7: csval = CS7; } if (0) { #endif #ifdef CS8 case TTY_OP_CS8: csval = CS8; } if (0) { #endif } parse_data(rest,restlen,chanreq_parse_fail, PP_IGNORE(1), PP_UINT32(&v), PP_REST(&rest2,&restlen2) ); if (v) { r->val.c_cflag = (r->val.c_cflag & ~CSIZE) | csval; r->set.c_cflag |= CSIZE; } } break; case TTY_OP_ISPEED: parse_data(rest,restlen,chanreq_parse_fail, PP_IGNORE(1), PP_UINT32(&v), PP_REST(&rest2,&restlen2) ); r->val.c_ispeed = v; r->set.c_ispeed = 1; break; case TTY_OP_OSPEED: parse_data(rest,restlen,chanreq_parse_fail, PP_IGNORE(1), PP_UINT32(&v), PP_REST(&rest2,&restlen2) ); r->val.c_ospeed = v; r->set.c_ospeed = 1; break; default: switch (*(const unsigned char *)rest) { case 1 ... 159: parse_data(rest,restlen,chanreq_parse_fail, PP_IGNORE(1), PP_SKIP(PP_UINT32(0)), PP_REST(&rest2,&restlen2) ); break; default: restlen2 = 0; break; } break; } rest = rest2; restlen = restlen2; } } static void set_tty_modes_mouse(PTY_REQ *r, const void *rest, int restlen) { const void *rest2; int restlen2; unsigned int v; while (restlen) { rest2 = 0; switch (*(const unsigned char *)rest) { unsigned int bit; { #ifdef ECHOPRT case MOUSETTY_OP_ECHOPRT: bit = ECHOPRT; } if (0) { #endif #ifdef ALTWERASE case MOUSETTY_OP_ALTWERASE: bit = ALTWERASE; } if (0) { #endif #ifdef NOKERNINFO case MOUSETTY_OP_NOKERNINFO: bit = NOKERNINFO; } if (0) { #endif } parse_data(rest,restlen,chanreq_parse_fail, PP_IGNORE(1), PP_BLOB(1,&v), PP_REST(&rest2,&restlen2) ); v = *(char *)&v; r->set.c_lflag |= bit; set_lflag(&r->val,v?bit:0,v?0:bit); break; case MOUSETTY_OP_CS: parse_data(rest,restlen,chanreq_parse_fail, PP_IGNORE(1), PP_BLOB(1,&v), PP_REST(&rest2,&restlen2) ); v = *(char *)&v; switch (v) { { #ifdef CS5 case 5: v = CS5; } if (0) { #endif #ifdef CS6 case 6: v = CS6; } if (0) { #endif #ifdef CS7 case 7: v = CS7; } if (0) { #endif #ifdef CS8 case 8: v = CS8; } if (0) { #endif } r->val.c_cflag = (r->val.c_cflag & ~CSIZE) | v; r->set.c_cflag |= CSIZE; break; } break; default: logmsg(LM_WARN|LM_PEER,"unknown mouse tty mode %02x",*(const unsigned char *)rest); restlen2 = 0; break; } rest = rest2; restlen = restlen2; } } static void session_setup(SSESSION *s) { if (! s->setup) { s->setup = malloc(sizeof(SESSION_SETUP)); s->setup->ptyreq = 0; s->setup->envreq = 0; } } static void set_env_req(SSESSION *s, ROSTR name, ROSTR val) { ENV_REQ *er; SESSION_SETUP *ss; session_setup(s); ss = s->setup; for (er=ss->envreq;er;er=er->flink) { if (str_equalcs(name,er->var)) { free_str(er->val); er->val = str_copyro(val); return; } } er = malloc(sizeof(ENV_REQ)); er->var = str_copyro(name); er->val = str_copyro(val); DLL_LINK_HEAD(er,ss->envreq); ss->envreq = er; } static int chanreq_pty_req(SSESSION *s, const void *rest, int restlen) { STR term_env; unsigned int x_c; unsigned int y_c; unsigned int x_p; unsigned int y_p; STR modestr; PTY_REQ *req; if (s->flags & SSF_STARTED) { logmsg(LM_WARN|LM_PEER,"pty-req on an already-started session"); return(CHANREQRET_FAIL); } parse_data(rest,restlen,chanreq_parse_fail, PP_STRING(&term_env), PP_UINT32(&x_c), PP_UINT32(&y_c), PP_UINT32(&x_p), PP_UINT32(&y_p), PP_STRING(&modestr), PP_ENDSHERE ); session_setup(s); if (s->setup->ptyreq) { logmsg(LM_WARN|LM_PEER,"duplicate pty-req received"); req = s->setup->ptyreq; } else { req = malloc(sizeof(PTY_REQ)); s->setup->ptyreq = req; bzero(&req->set.c_cc[0],sizeof(req->set.c_cc)); req->set.c_iflag = 0; req->set.c_oflag = 0; req->set.c_cflag = 0; req->set.c_lflag = 0; req->set.c_ispeed = 0; req->set.c_ospeed = 0; } req->x_c = x_c; req->y_c = y_c; req->x_p = x_p; req->y_p = y_p; set_tty_modes_std(req,modestr.data,modestr.len); free_str(modestr); set_env_req(s,cstr_to_rostr("TERM"),str_to_rostr(term_env)); free_str(term_env); return(CHANREQRET_OK); } static int chanreq_pty_fixup(SSESSION *s, const void *rest, int restlen) { if (s->flags & SSF_STARTED) { logmsg(LM_WARN|LM_PEER,"missing-pty-modes@rodents.montreal.qc.ca on an already-started session"); return(CHANREQRET_FAIL); } if (! s->setup->ptyreq) { logmsg(LM_WARN|LM_PEER,"missing-pty-modes@rodents.montreal.qc.ca with no pty-req"); return(CHANREQRET_OK); } set_tty_modes_mouse(s->setup->ptyreq,rest,restlen); return(CHANREQRET_OK); } static void set_nonblocking(int fd, int nb) { int f; f = fcntl(fd,F_GETFL,0); if (nb) f |= O_NONBLOCK; else f &= ~O_NONBLOCK; fcntl(fd,F_SETFL,f); } static int open_pty_pair(int *mfdp, int *sfdp, char **sptydev, PTY_REQ *prq) { char mdevname[16]; char sdevname[16]; int i; int j; int mfd; int sfd; struct termios tio; struct winsize wsz; for (i=0;;i++) { sprintf(&mdevname[0],"/dev/pty%c%x",'p'+(i>>4),i&15); strcpy(&sdevname[0],&mdevname[0]); sdevname[5] = 't'; mfd = open(&mdevname[0],O_RDWR,0); if (mfd < 0) { if (errno == ENOENT) { logmsg(LM_ERR|LM_PEER,"out of ptys"); return(1); } continue; } sfd = open(&sdevname[0],O_RDWR|O_NONBLOCK,0); if (sfd < 0) { logmsg(LM_ERR|LM_PEER,"opened %s but can't open %s: %s",&mdevname[0],&sdevname[0],strerror(errno)); close(mfd); continue; } if (tcgetattr(sfd,&tio) < 0) { logmsg(LM_ERR|LM_PEER,"tcgetattr failed on %s: %s",&sdevname[0],strerror(errno)); close(mfd); close(sfd); continue; } #define FOO(f) tio.f = (tio.f & ~prq->set.f) | (prq->val.f & prq->set.f) FOO(c_iflag); FOO(c_oflag); FOO(c_cflag); FOO(c_lflag); #undef FOO for (j=0;jset.c_cc[j]) tio.c_cc[j] = prq->val.c_cc[j]; if (prq->set.c_ispeed) tio.c_ispeed = prq->val.c_ispeed; if (prq->set.c_ospeed) tio.c_ospeed = prq->val.c_ospeed; if (tcsetattr(sfd,TCSAFLUSH,&tio) < 0) { logmsg(LM_ERR|LM_PEER,"tcsetattr failed on %s: %s",&sdevname[0],strerror(errno)); close(mfd); close(sfd); continue; } wsz.ws_row = prq->y_c; wsz.ws_col = prq->x_c; wsz.ws_xpixel = prq->x_p; wsz.ws_ypixel = prq->y_p; ioctl(sfd,TIOCSWINSZ,&wsz); set_nonblocking(sfd,0); { int on; on = 1; ioctl(mfd,TIOCPKT,&on); } *mfdp = mfd; *sfdp = sfd; *sptydev = strdup(&sdevname[0]); return(0); } } /* Use socketpair() rather than pipe() so we can use shutdown() */ static int make_pipe(int *fds) { if (socketpair(AF_LOCAL,SOCK_STREAM,0,fds) < 0) { logmsg(LM_ERR|LM_PEER,"socketpair: %s",strerror(errno)); return(1); } shutdown(fds[0],SHUT_WR); shutdown(fds[1],SHUT_RD); return(0); } static void close_pipe(int *fds) { close(fds[0]); close(fds[1]); } static void maybe_close_session(SSESSION *s) { if ( (s->kid < 0) && ( (s->flags & SSF_PTY) || (s->pipe.o.eof && s->pipe.e.eof) ) ) chan_close(s->chan); } static int wtest_ssess(int id __attribute__((__unused__)), void *sv) { return(oq_nonempty(&((SSESSION *)sv)->oq)); } static int rtest_ssess_pty(int id __attribute__((__unused__)), void *sv) { SSESSION *s; s = sv; return(chan_get_wwin(s->chan) > 0); } static void rd_ssess_pty(int id __attribute__((__unused__)), void *sv) { SSESSION *s; int n; int r; unsigned char buf[8192]; s = sv; if (s->pty.leof) return; n = chan_get_wwin(s->chan); if (n < 1) return; if (n > sizeof(buf)) n = sizeof(buf); r = read(s->pty.mfd,&buf[0],n); if (r < 0) { switch (errno) { case EINTR: case EWOULDBLOCK: return; break; } logmsg(LM_NOTE|LM_PEER,"session read error: %s",strerror(errno)); } if (r <= 0) { s->pty.leof = 1; chan_send_eof(s->chan); maybe_close_session(s); return; } if (buf[0] == TIOCPKT_DATA) { chan_send_data(s->chan,0,0,&buf[1],r-1); } else { /* for the moment, ignore non-data packets */ } } static void wr_ssess_pty(int id __attribute__((__unused__)), void *sv) { SSESSION *s; int w; int goteof; int n; NESTED int ateof(void *vp __attribute__((__unused__)), int i __attribute__((__unused__))) { goteof = 1; return(0); } s = sv; goteof = 0; w = oq_writev(&s->oq,s->pty.mfd,&ateof); if (goteof) { struct termios tio; if ( (tcgetattr(s->pty.mfd,&tio) == 0) && (cfsetspeed(&tio,0) == 0) ) tcsetattr(s->pty.mfd,TCSANOW,&tio); return; } if (w < 0) { switch (errno) { case EINTR: case EWOULDBLOCK: return; break; } logmsg(LM_NOTE|LM_PEER,"session write error: %s",strerror(errno)); exit(1); } oq_dropdata(&s->oq,w); if (! s->pty.reof) { n = s->advwin + oq_qlen(&s->oq); if (n < BUFFERSPACE/2) { chan_add_rwin(s->chan,BUFFERSPACE-n); s->advwin += BUFFERSPACE-n; } } } static int rtest_ssess_pipe_o(int id __attribute__((__unused__)), void *sv) { SSESSION *s; s = sv; return(!s->pipe.o.eof && (chan_get_wwin(s->chan) > 0)); } static int rtest_ssess_pipe_e(int id __attribute__((__unused__)), void *sv) { SSESSION *s; s = sv; return(!s->pipe.e.eof && (chan_get_wwin(s->chan) > 0)); } static void rd_ssess_pipe(SSESSION *s, SS_FD *f, int err) { int n; int r; unsigned char buf[8192]; if (f->eof) return; n = chan_get_wwin(s->chan); if (n < 1) return; if (n > sizeof(buf)) n = sizeof(buf); r = read(f->fd,&buf[0],n); if (r < 0) { switch (errno) { case EINTR: case EWOULDBLOCK: return; break; } logmsg(LM_NOTE|LM_PEER,"session read error: %s",strerror(errno)); } if (r <= 0) { f->eof = 1; if (s->pipe.o.eof && s->pipe.e.eof) { chan_send_eof(s->chan); maybe_close_session(s); } return; } chan_send_data(s->chan,err,SSH_EXTENDED_DATA_STDERR,&buf[0],r); } static void rd_ssess_pipe_o(int id __attribute__((__unused__)), void *sv) { rd_ssess_pipe(sv,&((SSESSION *)sv)->pipe.o,0); } static void rd_ssess_pipe_e(int id __attribute__((__unused__)), void *sv) { rd_ssess_pipe(sv,&((SSESSION *)sv)->pipe.e,1); } static void wr_ssess_pipe(int id __attribute__((__unused__)), void *sv) { SSESSION *s; int w; int goteof; int n; NESTED int ateof(void *vp __attribute__((__unused__)), int i __attribute__((__unused__))) { goteof = 1; return(0); } s = sv; goteof = 0; w = oq_writev(&s->oq,s->pipe.i.fd,&ateof); if (goteof) { shutdown(s->pipe.i.fd,SHUT_WR); return; } if (w < 0) { switch (errno) { case EINTR: case EWOULDBLOCK: return; break; case EPIPE: oq_flush(&s->oq); return; break; } logmsg(LM_NOTE|LM_PEER,"session write error: %s",strerror(errno)); exit(1); } oq_dropdata(&s->oq,w); if (! s->pipe.i.eof) { n = s->advwin + oq_qlen(&s->oq); if (n < BUFFERSPACE/2) { chan_add_rwin(s->chan,BUFFERSPACE-n); s->advwin += BUFFERSPACE-n; } } } static void free_session_setup(SESSION_SETUP *s) { if (! s) return; if (s->ptyreq) free(s->ptyreq); while (s->envreq) { ENV_REQ *er; er = s->envreq; DLL_UNLINK(er,s->envreq); free_str(er->var); free_str(er->val); free(er); } free(s); } static char **build_env(SESSION_SETUP *ss) { ENV_REQ *er; int n; char **env; int i; n = 0; for (er=ss->envreq;er;er=er->flink) n ++; env = malloc((n+1)*sizeof(char *)); i = 0; for (er=ss->envreq;er;er=er->flink) asprintf(&env[i++],"%.*s=%.*s",er->var.len,er->var.data,er->val.len,er->val.data); env[i] = 0; return(env); } static void handle_sigchld(int sig __attribute__((__unused__))) { int status; int pid; while (1) { pid = wait3(&status,WNOHANG,0); if (pid <= 0) break; write(deathpipe[1],&pid,sizeof(int)); write(deathpipe[1],&status,sizeof(int)); } } static void report_death(SSESSION *s, int status) { unsigned char *opp; if (WIFEXITED(status)) { opp = chan_req_hdr(s->chan,cstr_to_rostr("exit-status")); opp = put_uint32(opp,WEXITSTATUS(status)); chan_send_req_blind(s->chan,opp); } else if (WIFSIGNALED(status)) { int sig; const char *sigstr; char buf[32]; opp = chan_req_hdr(s->chan,cstr_to_rostr("exit-signal")); sig = WTERMSIG(status); switch (sig) { case SIGHUP: sigstr = "HUP"; break; case SIGINT: sigstr = "INT"; break; case SIGQUIT: sigstr = "QUIT"; break; case SIGILL: sigstr = "ILL"; break; case SIGABRT: sigstr = "ABRT"; break; case SIGFPE: sigstr = "FPE"; break; case SIGKILL: sigstr = "KILL"; break; case SIGSEGV: sigstr = "SEGV"; break; case SIGPIPE: sigstr = "PIPE"; break; case SIGALRM: sigstr = "ALRM"; break; case SIGTERM: sigstr = "TERM"; break; case SIGUSR1: sigstr = "USR1"; break; case SIGUSR2: sigstr = "USR2"; break; case SIGEMT: sigstr = "EMT@NetBSD"; break; case SIGBUS: sigstr = "BUS@NetBSD"; break; case SIGSYS: sigstr = "SYS@NetBSD"; break; case SIGURG: sigstr = "URG@NetBSD"; break; case SIGSTOP: sigstr = "STOP@NetBSD"; break; case SIGTSTP: sigstr = "TSTP@NetBSD"; break; case SIGCONT: sigstr = "CONT@NetBSD"; break; case SIGCHLD: sigstr = "CHLD@NetBSD"; break; case SIGTTIN: sigstr = "TTIN@NetBSD"; break; case SIGTTOU: sigstr = "TTOU@NetBSD"; break; case SIGIO: sigstr = "IO@NetBSD"; break; case SIGXCPU: sigstr = "XCPU@NetBSD"; break; case SIGXFSZ: sigstr = "XFSZ@NetBSD"; break; case SIGVTALRM: sigstr = "VTALRM@NetBSD"; break; case SIGPROF: sigstr = "PROF@NetBSD"; break; case SIGWINCH: sigstr = "WINCH@NetBSD"; break; case SIGINFO: sigstr = "INFO@NetBSD"; break; case SIGPWR: sigstr = "PWR@NetBSD"; break; default: sprintf(&buf[0],"%d@NetBSD",sig); sigstr = &buf[0]; break; } opp = put_string(opp,sigstr,-1); *opp++ = WCOREDUMP(status) ? 1 : 0; opp = put_string(opp,0,0); opp = put_string(opp,0,0); chan_send_req_blind(s->chan,opp); } } static void rd_deathpipe(int id __attribute__((__unused__)), void *sv __attribute__((__unused__))) { int pkt[2]; int r; SSESSION *s; s = sv; r = recv(deathpipe[0],&pkt[0],sizeof(pkt),MSG_WAITALL); if (VERB(PROGRESS)) verb(PROGRESS,"Child death: pid=%d status=%#x\n",pkt[0],pkt[1]); for (s=sessions;s;s=s->flink) { if (pkt[0] == s->kid) { if (VERB(PROGRESS)) verb(PROGRESS,"Child death: session found\n"); report_death(s,pkt[1]); s->kid = -1; maybe_close_session(s); } } } static void start_deathwatcher(void) { struct sigaction sa; if (! deathwatcher) { if (socketpair(AF_LOCAL,SOCK_STREAM,0,&deathpipe[0]) < 0) { logmsg(LM_ERR|LM_PEER,"socketpair: %s",strerror(errno)); exit(1); } fcntl(deathpipe[0],F_SETFD,1); fcntl(deathpipe[1],F_SETFD,1); sa.sa_handler = handle_sigchld; sigemptyset(&sa.sa_mask); sa.sa_flags = SA_NOCLDSTOP; sigaction(SIGCHLD,&sa,0); add_poll_fd(deathpipe[0],&rwtest_always,&rwtest_never,&rd_deathpipe,0,0); deathwatcher = 1; } } static void dmove(int from, int to) { if (from != to) { dup2(from,to); close(from); } } /* * This is necessary because the server ignores some signals that * processes forked by moussh shouldn't. An exec resets caught * signals to their defaults, but ignored signals remain ignored, so * we have to reset them here (called after forkign but before * execing) or they'll be (spuriously) ignored by processes we run. * * The list of signals we reset here must, of course, match the list of * signals set up elsewhere. At this writing, "elsewhere" means "in * server_setup()". */ static void reset_signals(void) { struct sigaction sa; sa.sa_handler = SIG_DFL; sigemptyset(&sa.sa_mask); sa.sa_flags = 0; sigaction(SIGCHLD,&sa,0); sigaction(SIGPIPE,&sa,0); } static int session_start(SSESSION *s, void (*runfn)(SUASTATE *, char **)) { int mfd; int sfd; int ipipe[2]; int opipe[2]; int epipe[2]; pid_t kid; int me; sigset_t m; sigset_t om; SUASTATE *uas; char **env; char lhn[NI_MAXHOST]; char lsn[NI_MAXSERV]; char rhn[NI_MAXHOST]; char rsn[NI_MAXSERV]; int e; if (s->setup && s->setup->ptyreq) { if (open_pty_pair(&mfd,&sfd,&s->pty.dev,s->setup->ptyreq)) return(CHANREQRET_FAIL); s->flags |= SSF_PTY; if (VERB(PROGRESS)) verb(PROGRESS,"Starting pty session\n"); } else { /* See the comment below on the dmove() calls (in the non-pty case) for some ordering considerations that are important here. */ if (make_pipe(&ipipe[0])) return(CHANREQRET_FAIL); if (make_pipe(&opipe[0])) { close_pipe(&ipipe[0]); return(CHANREQRET_FAIL); } if (make_pipe(&epipe[0])) { close_pipe(&ipipe[0]); close_pipe(&opipe[0]); return(CHANREQRET_FAIL); } s->flags &= SSF_PTY; if (VERB(PROGRESS)) verb(PROGRESS,"Starting pipe session\n"); } s->flags |= SSF_STARTED; uas = userauth_suastate(ualayer); if (! uas->haveuser) panic("no user"); start_deathwatcher(); fflush(0); kid = moussh_fork(); if (kid < 0) { logmsg(LM_ERR|LM_PEER,"fork: %s",strerror(errno)); exit(1); } if (kid > 0) { s->kid = kid; if (s->flags & SSF_PTY) { close(sfd); s->pty.mfd = mfd; set_nonblocking(mfd,1); fcntl(mfd,F_SETFD,1); s->pty.id = add_poll_fd(mfd,&rtest_ssess_pty,&wtest_ssess,&rd_ssess_pty,&wr_ssess_pty,s); s->pty.leof = 0; s->pty.reof = 0; } else { NESTED void fdsetup(SS_FD *f, int fd, int (*rtest)(int, void *), int (*wtest)(int, void *), void (*rd)(int, void *), void (*wr)(int, void *)) { f->fd = fd; f->eof = 0; set_nonblocking(fd,1); fcntl(fd,F_SETFD,1); f->id = add_poll_fd(fd,rtest,wtest,rd,wr,s); } close(ipipe[0]); close(opipe[1]); close(epipe[1]); fdsetup(&s->pipe.i,ipipe[1],&rwtest_never,&wtest_ssess,0,&wr_ssess_pipe); fdsetup(&s->pipe.o,opipe[0],&rtest_ssess_pipe_o,&rwtest_never,&rd_ssess_pipe_o,0); fdsetup(&s->pipe.e,epipe[0],&rtest_ssess_pipe_e,&rwtest_never,&rd_ssess_pipe_e,0); } free_session_setup(s->setup); s->setup = 0; return(CHANREQRET_OK); } set_env_req(s,cstr_to_rostr("USER"),cstr_to_rostr(uas->curuser)); set_env_req(s,cstr_to_rostr("HOME"),cstr_to_rostr(uas->homedir)); set_env_req(s,cstr_to_rostr("SHELL"),cstr_to_rostr(uas->shell)); e = getnameinfo((void *)&lcl,lcllen,&lhn[0],sizeof(lhn),&lsn[0],sizeof(lsn),NI_NUMERICHOST|NI_NUMERICSERV|NI_WITHSCOPEID) | getnameinfo((void *)&rem,remlen,&rhn[0],sizeof(rhn),&rsn[0],sizeof(rsn),NI_NUMERICHOST|NI_NUMERICSERV|NI_WITHSCOPEID); if (! e) { char *t; asprintf(&t,"%s %s %s %s",&rhn[0],&rsn[0],&lhn[0],&lsn[0]); set_env_req(s,cstr_to_rostr("SSH_CLIENT"),cstr_to_rostr(t)); free(t); } if (s->flags & SSF_PTY) set_env_req(s,cstr_to_rostr("SSH_TTY"),cstr_to_rostr(s->pty.dev)); if (s->auth_env) { if (VERB(AGENTF)) verb(AGENTF,"SSH_AGENT=%s\n",s->auth_env); set_env_req(s,cstr_to_rostr("SSH_AGENT"),cstr_to_rostr(s->auth_env)); } if (s->x_fwd) { NESTED void foo(const char *var, const char *val) { set_env_req(s,cstr_to_rostr(var),cstr_to_rostr(val)); } X_fwdreq_env(s->x_fwd,&foo); } env = build_env(s->setup); if (VERB(PROGRESS)) { int i; verb(PROGRESS,"Environment:\n"); for (i=0;env[i];i++) verb(PROGRESS,"\t%s\n",env[i]); } me = setsid(); if (s->flags & SSF_PTY) { sigfillset(&m); sigemptyset(&om); sigprocmask(SIG_BLOCK,&m,&om); ioctl(sfd,TIOCSCTTY,0); ioctl(sfd,TIOCSPGRP,&me); sigprocmask(SIG_SETMASK,&om,0); close(mfd); dmove(sfd,0); dup2(0,1); dup2(0,2); } else { /* The correctness of the dmove() dance here depends two things: (1) Creating a pipe uses the lowest two descriptors available; (2) Pipes are created in order i, o, e. If not for these, there would be a risk that the dup2() to put a pipe in place would destroy the descriptor for a later pipe. See the comment above at the make_pipe() calls. */ close(ipipe[1]); close(opipe[0]); close(epipe[0]); dmove(ipipe[0],0); dmove(opipe[1],1); dmove(epipe[1],2); } reset_signals(); (*runfn)(uas,env); logmsg(LM_WARN|LM_PEER,"can't exec %s (user %s): %s",uas->shell,uas->curuser,strerror(errno)); exit(1); /* if ((*runfn)(s,sfd)) { close(sfd); close(mfd); return(CHANREQRET_FAIL); } */ } static void run_shell(SUASTATE *uas, char **env) { char *t; if (chdir(uas->homedir) < 0) chdir("/"); t = strdup(uas->shell); t[0] = '-'; execle(uas->shell,t,(char *)0,(void *)env); } static void run_exec(SUASTATE *uas, char **env, ROSTR cmd) { if (chdir(uas->homedir) < 0) chdir("/"); execle(uas->shell,uas->shell,"-c",blk_to_cstr(cmd.data,cmd.len),(char *)0,(void *)env); } static int chanreq_shell(SSESSION *s, const void *rest, int restlen) { if (s->flags & SSF_STARTED) { logmsg(LM_WARN|LM_PEER,"shell request on an already-started session"); return(CHANREQRET_FAIL); } parse_data(rest,restlen,chanreq_parse_fail,PP_ENDSHERE); return(session_start(s,&run_shell)); } static int chanreq_exec(SSESSION *s, const void *rest, int restlen) { STR cmd; NESTED void foo(SUASTATE *uas, char **env) { run_exec(uas,env,str_to_rostr(cmd)); } if (s->flags & SSF_STARTED) { logmsg(LM_WARN|LM_PEER,"exec request on an already-started session"); return(CHANREQRET_FAIL); } parse_data(rest,restlen,chanreq_parse_fail, PP_STRING(&cmd), PP_ENDSHERE ); return(session_start(s,&foo)); } static int chanreq_window_change(SSESSION *s, const void *rest, int restlen) { unsigned int x_c; unsigned int y_c; unsigned int x_p; unsigned int y_p; struct winsize wsz; sigset_t m; sigset_t om; if (! (s->flags & SSF_STARTED)) { logmsg(LM_WARN|LM_PEER,"window-change request on a non-started session"); return(CHANREQRET_FAIL); } if (! (s->flags & SSF_PTY)) { logmsg(LM_WARN|LM_PEER,"window-change request on a non-pty session"); return(CHANREQRET_FAIL); } parse_data(rest,restlen,chanreq_parse_fail, PP_UINT32(&x_c), PP_UINT32(&y_c), PP_UINT32(&x_p), PP_UINT32(&y_p), PP_ENDSHERE ); sigfillset(&m); wsz.ws_row = y_c; wsz.ws_col = x_c; wsz.ws_xpixel = x_p; wsz.ws_ypixel = y_p; sigprocmask(SIG_BLOCK,&m,&om); ioctl(s->pty.mfd,TIOCSWINSZ,&wsz); sigprocmask(SIG_SETMASK,&om,0); return(CHANREQRET_OK); } static int rtest_sfa(int id __attribute__((__unused__)), void *fav) { SFWDAGENT *fa; fa = fav; return((fa->state != FAS_DEAD) && (chan_get_wwin(fa->chan) > 0)); } static int wtest_sfa(int id __attribute__((__unused__)), void *fav) { SFWDAGENT *fa; fa = fav; return((fa->state != FAS_DEAD) && oq_nonempty(&fa->oq)); } static void rd_sfa(int id __attribute__((__unused__)), void *fav) { SFWDAGENT *fa; int n; int r; char buf[8192]; fa = fav; if (fa->state == FAS_DEAD) return; n = chan_get_wwin(fa->chan); if (n > sizeof(buf)) n = sizeof(buf); if (n < 1) return; r = read(fa->fd,&buf[0],n); if (r < 0) { switch (errno) { case EWOULDBLOCK: case EINTR: return; break; } logmsg(LM_NOTE|LM_PEER,"forwarded agent read error: %s",strerror(errno)); } if (r <= 0) { fa->state = FAS_DEAD; chan_close(fa->chan); return; } chan_send_data(fa->chan,0,0,&buf[0],r); } static void wr_sfa(int id __attribute__((__unused__)), void *fav) { SFWDAGENT *fa; int w; int n; fa = fav; w = oq_writev(&fa->oq,fa->fd,0); if (w < 0) { switch (errno) { case EWOULDBLOCK: case EINTR: return; break; } logmsg(LM_NOTE|LM_PEER,"forwarded agent write error: %s",strerror(errno)); fa->state = FAS_DEAD; chan_close(fa->chan); return; } oq_dropdata(&fa->oq,w); n = fa->advwin + oq_qlen(&fa->oq); if (n < BUFFERSPACE/2) { chan_add_rwin(fa->chan,BUFFERSPACE-n); fa->advwin += BUFFERSPACE-n; } } static void sfa_opensucc(void *fav, int chan, ROSTR rest) { SFWDAGENT *fa; fa = fav; if (chan != fa->chan) panic("channel wrong"); if (rest.len) { logmsg(LM_WARN|LM_PEER,"protocol error: data (len=%d) in open confirmation",rest.len); exit(1); } fa->state = FAS_RUN; fa->advwin = BUFFERSPACE; chan_add_rwin(chan,BUFFERSPACE); fa->id = add_poll_fd(fa->fd,&rtest_sfa,&wtest_sfa,&rd_sfa,&wr_sfa,fa); } static void sfa_openfail(void *fav, int chan, unsigned int rcode, ROSTR reason, ROSTR lang __attribute__((__unused__))) { SFWDAGENT *fa; FILE *f; fa = fav; if (chan != fa->chan) panic("channel wrong"); f = logmsg_fopen(LM_WARN|LM_PEER); fprintf(f,"forwarding agent: channel open failed ("); print_openfail_rcode(f,rcode); fprintf(f,")"); if (reason.len) { fprintf(f,": "); print_escaped(f,reason.data,reason.len,1024); } fclose(f); DLL_UNLINK(fa,sfwdagents); close(fa->fd); free(fa); } static void sfa_gotdata(void *fav, int chan, int ext, unsigned int code, const void *buf, int len) { SFWDAGENT *fa; fa = fav; if (chan != fa->chan) panic("channel wrong"); fa->advwin -= len; if (ext) logmsg(LM_NOTE|LM_PEER,"agent channel got extended data, code %u",code); if (len == 0) { fa->state = FAS_DEAD; chan_close(fa->chan); return; } oq_queue_copy(&fa->oq,buf,len); } static void sfa_closed(void *fav, int chan, int final) { SFWDAGENT *fa; fa = fav; if (chan != fa->chan) panic("channel wrong"); if (!final && !chan_close(chan)) panic("close failed"); DLL_UNLINK(fa,sfwdagents); close(fa->fd); remove_poll_id(fa->id); oq_flush(&fa->oq); free(fa); } static CHANOPS sfa_ops = { &sfa_opensucc, &sfa_openfail, &sfa_gotdata, 0, 0, &sfa_closed }; static void got_agent(int fd, void *sv, AGENT_PROTO proto) { SSESSION *s; SFWDAGENT *fa; switch (proto) { default: panic("impossible agent protocol"); break; case AGENT_PROTO_NORMAL: break; case AGENT_PROTO_INTERACTIVE: close(fd); return; break; } s = sv; fa = malloc(sizeof(SFWDAGENT)); DLL_LINK_HEAD(fa,sfwdagents); fa->fd = fd; if (s->broken_auth) { fa->chan = chan_open_send(chan_open_hdr(cstr_to_rostr("auth-agent")),&sfa_ops,fa); } else { void *opp; opp = chan_open_hdr(cstr_to_rostr("fixed-auth-agent@rodents.montreal.qc.ca")); opp = put_uint32(opp,s->auth_id); fa->chan = chan_open_send(opp,&sfa_ops,fa); } fa->id = PL_NOID; fa->advwin = 0; oq_init(&fa->oq); fa->state = FAS_OPEN; } static int chanreq_auth_fwd(SSESSION *s, const void *rest, int restlen, int version) { char *dir; char *path; static int agent_serial = 0; unsigned int id; int single; if (VERB(PROGRESS)) verb(PROGRESS,"Agent forwarding requested\n"); if (s->auth_agent) { logmsg(LM_WARN|LM_PEER,"agent forwarding already requested"); return(CHANREQRET_FAIL); } s->broken_auth = 0; switch (version) { case 0: parse_data(rest,restlen,chanreq_parse_fail,PP_ENDSHERE); s->broken_auth = 1; break; case 1: parse_data(rest,restlen,chanreq_parse_fail, PP_UINT32(&id), PP_ENDSHERE ); single = 0; break; case 2: parse_data(rest,restlen,chanreq_parse_fail, PP_BOOL(&single), PP_UINT32(&id), PP_ENDSHERE ); break; } dir = agent_dir(); if (! dir) return(CHANREQRET_FAIL); asprintf(&path,"%s/fwd-%d-%d",dir,(int)getpid(),agent_serial++); free(dir); if (single) s->flags |= SSF_ONEAUTH; s->auth_agent = agent_listener(path,&got_agent,s,&s->auth_env,single?ALF_JUSTONCE:0); if (VERB(AGENTF)) verb(AGENTF,"agent %s -> %s\n",path,s->auth_env); s->auth_id = id; if (! s->auth_agent) { logmsg(LM_ERR|LM_PEER,"agent forwarding listener failed (%s)",strerror(errno)); return(CHANREQRET_FAIL); } return(CHANREQRET_OK); } static SFWDCONN *nascent_sfwdconn(void) { SFWDCONN *c; c = malloc(sizeof(SFWDCONN)); c->flink = 0; c->blink = 0; c->type = SFCT_UNSET; c->fd = -1; c->chan = CHAN_NO_CHANNEL; c->id = PL_NOID; c->advwin = 0; c->leof = 0; c->reof = 0; oq_init(&c->oq); return(c); } static const char *sfwdconn_parentext(SFWDCONN *c) { switch (c->type) { case SFCT_UNSET: return("nascent"); break; case SFCT_TCP: return(c->tcp.req?c->tcp.req->text:"remote"); break; case SFCT_X: return("X"); break; } panic("bad type"); } static void sfwdreq_ref(SFWDREQ *r) { r->refs ++; } static void sfwdreq_deref(SFWDREQ *r) { r->refs --; if (r->refs < 0) panic("negative refs"); if (r->refs == 0) { if (r->lfds) panic("lfds present"); free(r->text); free(r); } } static int exit_if_drained(void *arg __attribute__((__unused__))) { if (oq_empty(&ualayer->b->output)) { if (VERB(PROGRESS)) verb(PROGRESS,"No sessions left, exiting\n"); exit(0); } return(BLOCK_NIL); } static void maybe_set_exit(void) { if (!sessions && !sfwdconns && !sfwdagents) add_block_fn(&exit_if_drained,0); } static void close_and_free_fwdconn_s(SFWDCONN *c) { if (VERB(PROGRESS)) verb(PROGRESS,"[close_and_free_fwdconn_s for %d (%s)]\r\n",c->fd,sfwdconn_parentext(c)); if (c->fd >= 0) close(c->fd); DLL_UNLINK(c,sfwdconns); oq_flush(&c->oq); switch (c->type) { case SFCT_UNSET: break; case SFCT_TCP: if (c->tcp.req) sfwdreq_deref(c->tcp.req); free(c->tcp.clientaddr); free(c->tcp.toaddr); break; case SFCT_X: switch (c->x.kind) { case XK_TCP: free(c->x.tcp.clientaddr); break; case XK_LOCAL: break; default: panic("impossible X kind"); break; } free(c->x.chdr); break; default: panic("bad type"); break; } free(c); maybe_set_exit(); } static void sfwd_force_teardown(SFWDCONN *c) { c->leof = 1; c->reof = 1; chan_close(c->chan); if (VERB(PROGRESS)) verb(PROGRESS,"[sfwd_teardown %d (%s)]\r\n",c->fd,sfwdconn_parentext(c)); } static void sfwd_maybe_close(SFWDCONN *c) { if (c->leof && c->reof && oq_empty(&c->oq)) { chan_close(c->chan); if (VERB(PROGRESS)) verb(PROGRESS,"[sfwd_maybe_close closing %d (%s)]\r\n",c->fd,sfwdconn_parentext(c)); } } static int rtest_sfwd(int id __attribute__((__unused__)), void *cv) { SFWDCONN *c; c = cv; return(!c->leof && (chan_get_wwin(c->chan) > 0)); } static int wtest_sfwd(int id __attribute__((__unused__)), void *cv) { return(oq_nonempty(&((SFWDCONN *)cv)->oq)); } static void rd_sfwd(int id __attribute__((__unused__)), void *cv) { SFWDCONN *c; int n; int r; char buf[8192]; c = cv; if (c->leof) return; n = chan_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_sfwd %d (%s): read %d",c->fd,sfwdconn_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; } logmsg(LM_NOTE|LM_PEER,"forwarding %s: read error: %s",sfwdconn_parentext(c),strerror(errno)); } if (r <= 0) { c->leof = 1; shutdown(c->fd,SHUT_RD); chan_send_eof(c->chan); sfwd_maybe_close(c); return; } chan_send_data(c->chan,0,0,&buf[0],r); } static void wr_sfwd(int id __attribute__((__unused__)), void *cv) { SFWDCONN *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_sfwd %d (%s): goteof]\r\n",c->fd,sfwdconn_parentext(c)); shutdown(c->fd,SHUT_WR); sfwd_maybe_close(c); return; } if (w < 0) { switch (errno) { case EINTR: case EWOULDBLOCK: return; break; } logmsg(LM_WARN|LM_PEER,"forwarding %s: write error: %s",sfwdconn_parentext(c),strerror(errno)); sfwd_force_teardown(c); } if (VERB(FWDDATA)) verb(FWDDATA,"[wr_sfwd %d (%s): wrote %d]\r\n",c->fd,sfwdconn_parentext(c),w); oq_dropdata(&c->oq,w); if (! c->reof) { n = c->advwin + oq_qlen(&c->oq); if (n < BUFFERSPACE/2) { chan_add_rwin(c->chan,BUFFERSPACE-n); c->advwin += BUFFERSPACE-n; } } } static void sfwd_up(SFWDCONN *c) { c->id = add_poll_fd(c->fd,&rtest_sfwd,&wtest_sfwd,&rd_sfwd,&wr_sfwd,c); chan_add_rwin(c->chan,BUFFERSPACE); c->advwin = BUFFERSPACE; } static void sfwd_opensucc(void *cv, int ch, ROSTR rest) { SFWDCONN *c; c = cv; if (ch != c->chan) panic("channel wrong"); if (rest.len) { logmsg(LM_WARN|LM_PEER,"protocol error: data (len=%d) in open confirmation",rest.len); exit(1); } if (VERB(PROGRESS)) verb(PROGRESS,"[forwarding %s: channel open for %d]\r\n",sfwdconn_parentext(c),c->fd); sfwd_up(c); } static void sfwd_openfail(void *cv, int ch, unsigned int rcode, ROSTR reason, ROSTR lang __attribute__((__unused__))) { SFWDCONN *c; FILE *f; c = cv; if (ch != c->chan) panic("channel wrong"); f = logmsg_fopen(LM_NOTE|LM_PEER); fprintf(f,"forwarding %s: channel open failed (",sfwdconn_parentext(c)); print_openfail_rcode(f,rcode); fprintf(f,")"); if (reason.len) { fprintf(f,": "); print_escaped(f,reason.data,reason.len,1024); } fclose(f); close_and_free_fwdconn_s(c); } static void sfwd_gotdata(void *cv, int cno, int ext __attribute__((__unused__)), unsigned int code __attribute__((__unused__)), const void *buf, int len) { SFWDCONN *c; c = cv; if (cno != c->chan) panic("channel wrong"); if (VERB(FWDDATA)) verb(FWDDATA,"[sfwd_gotdata %d (%s): gotdata %d]\r\n",c->fd,sfwdconn_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 sfwd_closed(void *cv, int cno, int final) { SFWDCONN *c; c = cv; if (cno != c->chan) panic("channel wrong"); if (VERB(PROGRESS)) verb(PROGRESS,"[sfwd_closed %d (%s): final=%d]\r\n",c->fd,sfwdconn_parentext(c),final); if (!final && !chan_close(cno)) panic("close failed"); remove_poll_id(c->id); close_and_free_fwdconn_s(c); } static const CHANOPS sfwd_ops = { &sfwd_opensucc, &sfwd_openfail, &sfwd_gotdata, 0, 0, &sfwd_closed }; static void sxfwd_morewin(void *cv, int ch, unsigned int addl __attribute__((__unused__)), unsigned int newwin) { SFWDCONN *c; int n; c = cv; if (ch != c->chan) panic("channel wrong"); if (c->x.cptr >= c->x.clen) return; n = c->x.clen - c->x.cptr; if (n > newwin) n = newwin; chan_send_data(ch,0,0,c->x.chdr+c->x.cptr,n); c->x.cptr += n; } static void sxfwd_gotdata(void *cv, int cno, int ext __attribute__((__unused__)), unsigned int code __attribute__((__unused__)), const void *buf, int len) { SFWDCONN *c; c = cv; if (cno != c->chan) panic("channel wrong"); if (VERB(FWDDATA)) verb(FWDDATA,"[sfwd_gotdata %d (%s): gotdata %d]\r\n",c->fd,sfwdconn_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 sxfwd_opensucc(void *cv, int ch, ROSTR rest) { sfwd_opensucc(cv,ch,rest); sxfwd_morewin(cv,ch,0,chan_get_wwin(ch)); } static const CHANOPS sxfwd_ops = { &sxfwd_opensucc, &sfwd_openfail, &sxfwd_gotdata, &sxfwd_morewin, 0, &sfwd_closed }; static void *put_broken_x_sfwdconn_info(void *opp, SFWDCONN *c) { switch (c->x.kind) { case XK_TCP: opp = put_string(opp,c->x.tcp.clientaddr,-1); opp = put_uint32(opp,c->x.tcp.clientport); break; case XK_LOCAL: /* * Not clear what to do here. RFC 4254 does not appear to * realize that X can be carried over anything other than TCP; * the spec is totally silent on what to do if "originator * address" and "originator port" do not exist. */ opp = put_string(opp,"",0); opp = put_uint32(opp,0); break; default: panic("impossible X kind in put_broken_x_sfwdconn_info"); break; } return(opp); } static void *put_fixed_x_sfwdconn_info(void *opp, SFWDCONN *c) { switch (c->x.kind) { case XK_TCP: opp = put_string(opp,"tcp",-1); opp = put_string(opp,c->x.tcp.clientaddr,-1); opp = put_uint32(opp,c->x.tcp.clientport); break; case XK_LOCAL: opp = put_string(opp,"local",-1); break; default: panic("impossible X kind in put_fixed_x_sfwdconn_info"); break; } return(opp); } static void got_broken_x(int fd, unsigned char *chdr, void *sv) { SSESSION *s; SFWDCONN *c; int e; unsigned char *opp; struct sockaddr_storage rss; socklen_t rsslen; s = sv; rsslen = sizeof(rss); if (getpeername(fd,(void *)&rss,&rsslen) < 0) { logmsg(LM_NOTE|LM_PEER,"X getpeername: %s",strerror(errno)); close(fd); return; } c = nascent_sfwdconn(); DLL_LINK_HEAD(c,sfwdconns); switch (rss.ss_family) { case AF_INET: { char rhn[NI_MAXHOST]; char rsn[NI_MAXSERV]; e = getnameinfo((void *)&rss,rsslen,&rhn[0],sizeof(rhn),&rsn[0],sizeof(rsn),NI_NUMERICHOST|NI_NUMERICSERV|NI_WITHSCOPEID); if (e) { logmsg(LM_NOTE|LM_PEER,"got X remote getnameinfo: %s",strerror(e)); close(fd); close_and_free_fwdconn_s(c); return; } c->x.kind = XK_TCP; c->x.tcp.clientaddr = strdup(&rhn[0]); c->x.tcp.clientport = strtoul(&rsn[0],0,0); } break; case AF_LOCAL: c->x.kind = XK_LOCAL; break; default: logmsg(LM_NOTE|LM_PEER,"X getpeername returned unknown family %d",(int)rss.ss_family); close(fd); close_and_free_fwdconn_s(c); return; break; } c->type = SFCT_X; c->x.clen = 12 + X_pad_roundup(s->x_authproto.len) + X_pad_roundup(s->x_authcookie.len); c->x.chdr = malloc(c->x.clen); bzero(c->x.chdr,c->x.clen); bcopy(chdr,c->x.chdr,6); X_put_2(c->x.chdr[0],c->x.chdr+6,s->x_authproto.len); bcopy(s->x_authproto.data,c->x.chdr+6,s->x_authproto.len); X_put_2(c->x.chdr[0],c->x.chdr+6+X_pad_roundup(s->x_authproto.len),s->x_authcookie.len); bcopy(s->x_authcookie.data,c->x.chdr+6+X_pad_roundup(s->x_authproto.len),s->x_authcookie.len); c->x.cptr = 0; c->x.s = s; c->fd = fd; opp = chan_open_hdr(cstr_to_rostr("x11")); opp = put_broken_x_sfwdconn_info(opp,c); c->chan = chan_open_send(opp,&sxfwd_ops,c); c->id = PL_NOID; c->advwin = 0; c->leof = 0; c->reof = 0; oq_init(&c->oq); } static void got_x(int fd, unsigned char *chdr, void *sv) { SSESSION *s; SFWDCONN *c; int e; unsigned char *opp; struct sockaddr_storage rss; socklen_t rsslen; s = sv; rsslen = sizeof(rss); if (getpeername(fd,(void *)&rss,&rsslen) < 0) { logmsg(LM_NOTE|LM_PEER,"X getpeername: %s",strerror(errno)); close(fd); return; } c = nascent_sfwdconn(); DLL_LINK_HEAD(c,sfwdconns); switch (rss.ss_family) { case AF_INET: { char rhn[NI_MAXHOST]; char rsn[NI_MAXSERV]; e = getnameinfo((void *)&rss,rsslen,&rhn[0],sizeof(rhn),&rsn[0],sizeof(rsn),NI_NUMERICHOST|NI_NUMERICSERV|NI_WITHSCOPEID); if (e) { logmsg(LM_NOTE|LM_PEER,"got X remote getnameinfo: %s",strerror(e)); close(fd); return; } c->x.kind = XK_TCP; c->x.tcp.clientaddr = strdup(&rhn[0]); c->x.tcp.clientport = strtoul(&rsn[0],0,0); } break; case AF_LOCAL: c->x.kind = XK_LOCAL; break; default: logmsg(LM_NOTE|LM_PEER,"X getpeername returned unknown family %d",(int)rss.ss_family); close(fd); close_and_free_fwdconn_s(c); return; break; } c->type = SFCT_X; c->x.chdr = malloc(12); bcopy(chdr,c->x.chdr,6); bzero(c->x.chdr+6,6); c->x.cptr = 0; c->x.clen = 12; c->x.s = s; c->fd = fd; if (s->x_broken) { opp = chan_open_hdr(cstr_to_rostr("x11")); opp = put_broken_x_sfwdconn_info(opp,c); } else { opp = chan_open_hdr(cstr_to_rostr("fixed-x11@rodents.montreal.qc.ca")); opp = put_uint32(opp,s->x_id); opp = put_fixed_x_sfwdconn_info(opp,c); } c->chan = chan_open_send(opp,&sxfwd_ops,c); c->id = PL_NOID; c->advwin = 0; c->leof = 0; c->reof = 0; oq_init(&c->oq); } static int chanreq_X_fwd(SSESSION *s, const void *rest, int restlen, int version) { unsigned int id; const void *rest2; int rest2len; int single; SFXREQ *req; STR auth_proto; STR auth_cookie; static void (*gotfn)(int, unsigned char *, void *); unsigned int flags; if (VERB(PROGRESS) || VERB(X)) pverb(VERBOSE_PROGRESS|VERBOSE_X,"X forwarding requested\n"); if (s->x_fwd) { logmsg(LM_ERR|LM_PEER,"X forwarding already requested"); return(CHANREQRET_FAIL); } flags = 0; s->x_broken = 0; switch (version) { case 0: parse_data(rest,restlen,chanreq_parse_fail, PP_BOOL(&single), PP_STRING(&auth_proto), PP_STRING(&auth_cookie), PP_REST(&rest2,&rest2len) ); gotfn = &got_broken_x; flags |= XFF_TCPONLY; if (single) flags |= XFF_SINGLE; s->x_broken = 1; s->x_authproto = auth_proto; s->x_authcookie = auth_cookie; break; case 1: parse_data(rest,restlen,chanreq_parse_fail, PP_UINT32(&id), PP_REST(&rest2,&rest2len) ); gotfn = &got_x; break; case 2: parse_data(rest,restlen,chanreq_parse_fail, PP_BOOL(&single), PP_UINT32(&id), PP_REST(&rest2,&rest2len) ); gotfn = &got_x; if (single) flags |= XFF_SINGLE; break; } req = X_fwdreq_start(rest2,rest2len,&rest2,&rest2len,chanreq_parse_fail,gotfn,s,flags); if (req == 0) { logmsg(LM_ERR|LM_PEER,"X forwarding setup failed"); if (s->x_broken) { free_str(s->x_authproto); free_str(s->x_authcookie); } return(CHANREQRET_FAIL); } if (rest2len) { X_fwdreq_abort(req); parse_data(rest2,rest2len,chanreq_parse_fail,PP_ENDSHERE); } X_fwdreq_go(req); s->x_fwd = req; s->x_id = id; return(CHANREQRET_OK); } static void session_gotdata(void *sv, int chan, int ext, unsigned int code, const void *buf, int len) { SSESSION *s; s = sv; if (chan != s->chan) panic("channel wrong"); if (len == 0) { if (s->flags & SSF_PTY) { /* what does EOF map to when using a pty? */ } else { oq_queue_special(&s->oq,0,0); s->pipe.i.eof = 1; } return; } if (ext) { logmsg(LM_WARN|LM_PEER,"extended data (code %d) on session channel",code); logmsg(LM_WARN|LM_PEER,"merging with regular data stream"); } s->advwin -= len; oq_queue_copy(&s->oq,buf,len); } static int session_chanreq(void *sv, int chan, ROSTR req, int wantrep __attribute__((__unused__)), const void *rest, int restlen) { SSESSION *s; int rv; NESTED void fail(const void *at __attribute__((__unused__)), const char *fmt, ...) { va_list ap; int i; FILE *f; f = logmsg_fopen(LM_WARN|LM_PEER); fprintf(f,"protocol error: unparseable `%.*s': ",req.len,req.data); va_start(ap,fmt); vfprintf(f,fmt,ap); va_end(ap); fclose(f); f = logmsg_fopen(LM_WARN|LM_PEER); for (i=0;ichan) panic("channel wrong"); if (VERB(PROGRESS)) verb(PROGRESS,"Channel request `%.*s'\n",req.len,req.data); rv = CHANREQRET_UNK; chanreq_parse_fail = &fail; if (str_equalcC(req,"pty-req")) { rv = chanreq_pty_req(s,rest,restlen); } else if (str_equalcC(req,"missing-pty-modes@rodents.montreal.qc.ca")) { rv = chanreq_pty_fixup(s,rest,restlen); } else if (str_equalcC(req,"shell")) { rv = chanreq_shell(s,rest,restlen); } else if (str_equalcC(req,"exec")) { rv = chanreq_exec(s,rest,restlen); } else if (str_equalcC(req,"window-change")) { rv = chanreq_window_change(s,rest,restlen); } else if (str_equalcC(req,"auth-agent-req")) { rv = chanreq_auth_fwd(s,rest,restlen,0); } else if (str_equalcC(req,"fixed-auth-agent-req@rodents.montreal.qc.ca")) { rv = chanreq_auth_fwd(s,rest,restlen,1); } else if (str_equalcC(req,"fixed-auth-agent-req-2@rodents.montreal.qc.ca")) { rv = chanreq_auth_fwd(s,rest,restlen,2); } else if (str_equalcC(req,"x11-req")) { rv = chanreq_X_fwd(s,rest,restlen,0); } else if (str_equalcC(req,"fixed-x11-req@rodents.montreal.qc.ca")) { rv = chanreq_X_fwd(s,rest,restlen,1); } else if (str_equalcC(req,"fixed-x11-req-2@rodents.montreal.qc.ca")) { rv = chanreq_X_fwd(s,rest,restlen,2); } else if (str_equalcC(req,"keepalive@openssh.com")) { rv = CHANREQRET_FAIL; } return(rv); } static void session_cleanup(SSESSION *s) { bpp_del_abort(chan_layer()->b,s->abortid); if (s->auth_agent) agent_listener_abort(s->auth_agent); free(s->auth_env); if (s->x_fwd) X_fwdreq_abort(s->x_fwd); if (s->setup) free_session_setup(s->setup); if (s->flags & SSF_STARTED) { if (s->flags & SSF_PTY) { close(s->pty.mfd); free(s->pty.dev); remove_poll_id(s->pty.id); } else { close(s->pipe.i.fd); remove_poll_id(s->pipe.i.id); close(s->pipe.o.fd); remove_poll_id(s->pipe.o.id); close(s->pipe.e.fd); remove_poll_id(s->pipe.e.id); } } oq_flush(&s->oq); } static void session_aborted(void *sv) { session_cleanup(sv); } static void session_closed(void *sv, int chan, int final) { SSESSION *s; s = sv; if (chan != s->chan) panic("channel wrong"); if (VERB(PROGRESS)) verb(PROGRESS,"Session channel closed\n"); if (!final && !chan_close(chan)) panic("close failed"); DLL_UNLINK(s,sessions); session_cleanup(s); free(s); maybe_set_exit(); } static CHANOPS session_ops = { 0, 0, &session_gotdata, 0, &session_chanreq, &session_closed }; static void sfwd_connect_fail(SFWDCONN *c) { chan_open_fail(c->chan,SSH_OPEN_CONNECT_FAILED,cstr_to_rostr(""),cstr_to_rostr("")); c->fd = -1; close_and_free_fwdconn_s(c); } static void sfwd_connect_try(SIPSTATE *); /* forward */ static void sfwd_connect_ok(SFWDCONN *c, int sock) { c->fd = sock; chan_open_ok(c->chan,cstr_to_rostr(""),&sfwd_ops,c); sfwd_up(c); } static void sfwd_connect_next(SIPSTATE *ss) { ss->ai = ss->ai->ai_next; sfwd_connect_try(ss); } static void sfwd_connect_trial(int id, void *ssv) { SIPSTATE *ss; int err; socklen_t errlen; ss = ssv; remove_poll_id(id); errlen = sizeof(err); if ((getsockopt(ss->sock,SOL_SOCKET,SO_ERROR,&err,&errlen) < 0) || err) { close(ss->sock); sfwd_connect_next(ss); } else { freeaddrinfo(ss->ai0); sfwd_connect_ok(ss->c,ss->sock); free(ss); } } static void sfwd_connect_try(SIPSTATE *ss) { int sock; for (;ss->ai;ss->ai=ss->ai->ai_next) { sock = socket(ss->ai->ai_family,ss->ai->ai_socktype,ss->ai->ai_protocol); if (sock < 0) continue; set_nonblocking(sock,1); ss->sock = sock; if (connect(sock,ss->ai->ai_addr,ss->ai->ai_addrlen) < 0) { if (errno == EINPROGRESS) { ss->id = add_poll_fd(sock,&rwtest_never,&rwtest_always,0,&sfwd_connect_trial,ss); return; } close(sock); continue; } freeaddrinfo(ss->ai0); sfwd_connect_ok(ss->c,sock); free(ss); return; } freeaddrinfo(ss->ai0); sfwd_connect_fail(ss->c); free(ss); } static void sfwd_start_connect(SFWDCONN *c) { struct addrinfo hints; char *portstr; SIPSTATE *ss; int err; if (c->type != SFCT_TCP) panic("type wrong"); ss = malloc(sizeof(SIPSTATE)); ss->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.toport); err = getaddrinfo(c->tcp.toaddr,portstr,&hints,&ss->ai0); free(portstr); if (err) { free(ss); if (VERB(PROGRESS)) verb(PROGRESS,"[sfwd_start_connect: getaddrinfo(%s/%u): %s\r\n",c->tcp.toaddr,c->tcp.toport,gai_strerror(err)); sfwd_connect_fail(c); } else { ss->ai = ss->ai0; sfwd_connect_try(ss); } } static void chanopen_s(ROSTR type, int chan, const void *rest, int restlen) { NESTED void failure(const void *errat __attribute__((__unused__)), const char *fmt, ...) { va_list ap; FILE *f; f = logmsg_fopen(LM_WARN|LM_PEER); fprintf(f,"protocol error: bad %.*s request: ",type.len,type.data); va_start(ap,fmt); vfprintf(f,fmt,ap); va_end(ap); fclose(f); exit(1); } if (str_equalcC(type,"session")) { SSESSION *s; parse_data(rest,restlen,&failure,PP_ENDSHERE); s = malloc(sizeof(SSESSION)); DLL_LINK_HEAD(s,sessions); s->chan = chan; s->flags = 0; s->auth_agent = 0; s->auth_env = 0; s->x_fwd = 0; s->setup = 0; s->kid = -1; s->advwin = BUFFERSPACE; oq_init(&s->oq); if (VERB(PROGRESS)) verb(PROGRESS,"[chanopen: type session]\n"); chan_open_ok(chan,ROSTRZERO,&session_ops,s); chan_add_rwin(chan,BUFFERSPACE); s->abortid = bpp_add_abort(chan_layer()->b,&session_aborted,s); } else if (str_equalcC(type,"direct-tcpip")) { SFWDCONN *c; STR tohost; unsigned int toport; STR fromhost; unsigned int fromport; parse_data(rest,restlen,&failure, PP_STRING(&tohost), PP_UINT32(&toport), PP_STRING(&fromhost), PP_UINT32(&fromport), PP_ENDSHERE ); c = nascent_sfwdconn(); DLL_LINK_HEAD(c,sfwdconns); c->chan = chan; c->type = SFCT_TCP; c->tcp.req = 0; c->tcp.clientaddr = blk_to_cstr(fromhost.data,fromhost.len); c->tcp.clientport = fromport; c->tcp.toaddr = blk_to_cstr(tohost.data,tohost.len); c->tcp.toport = toport; sfwd_start_connect(c); free_str(tohost); free_str(fromhost); } else { printf("[chanopen: type %.*s restlen %d]\n",type.len,type.data,restlen); chan_open_fail(chan,SSH_OPEN_UNKNOWN_CHANNEL_TYPE,cstr_to_rostr(""),cstr_to_rostr("")); } } static void sfwdlfd_acc(int id __attribute__((__unused__)), void *lv) { SFWDLFD *l; SFWDCONN *c; int new; int e; unsigned char *opp; struct sockaddr_storage lss; socklen_t lsslen; struct sockaddr_storage rss; socklen_t rsslen; char lhn[NI_MAXHOST]; char lsn[NI_MAXSERV]; char rhn[NI_MAXHOST]; char rsn[NI_MAXSERV]; l = lv; rsslen = sizeof(rss); new = accept(l->fd,(void *)&rss,&rsslen); if (new < 0) { switch (errno) { case EWOULDBLOCK: case EINTR: return; break; } logmsg(LM_ERR|LM_PEER,"accept %s: %s",l->req->text,strerror(errno)); } lsslen = sizeof(lss); if (getsockname(new,(void *)&lss,&lsslen) < 0) { logmsg(LM_ERR|LM_PEER,"accept %s getsockname: %s",l->req->text,strerror(errno)); close(new); return; } e = getnameinfo((void *)&lss,lsslen,&lhn[0],sizeof(lhn),&lsn[0],sizeof(lsn),NI_NUMERICHOST|NI_NUMERICSERV|NI_WITHSCOPEID); if (e) { logmsg(LM_ERR|LM_PEER,"accept %s local getnameinfo: %s",l->req->text,strerror(e)); close(new); return; } e = getnameinfo((void *)&rss,rsslen,&rhn[0],sizeof(rhn),&rsn[0],sizeof(rsn),NI_NUMERICHOST|NI_NUMERICSERV|NI_WITHSCOPEID); if (e) { logmsg(LM_ERR|LM_PEER,"accept %s remote getnameinfo: %s",l->req->text,strerror(e)); close(new); return; } if (VERB(PROGRESS)) verb(PROGRESS,"accepted %d from %s/%s to %s/%s on %s for %s\n",new,&rhn[0],&rsn[0],&lhn[0],&lsn[0],l->text,l->req->text); c = nascent_sfwdconn(); DLL_LINK_HEAD(c,sfwdconns); c->type = SFCT_TCP; c->tcp.req = l->req; sfwdreq_ref(l->req); c->tcp.clientaddr = strdup(&rhn[0]); c->tcp.clientport = strtoul(&rsn[0],0,0); c->tcp.toaddr = 0; c->tcp.toport = 0; c->fd = new; opp = chan_open_hdr(cstr_to_rostr(c->tcp.req->broken?"forwarded-tcpip":"fixed-forwarded-tcpip@rodents.montreal.qc.ca")); opp = put_string(opp,&lhn[0],-1); opp = put_uint32(opp,strtoul(&lsn[0],0,0)); opp = put_string(opp,c->tcp.clientaddr,-1); opp = put_uint32(opp,c->tcp.clientport); if (! c->tcp.req->broken) opp = put_uint32(opp,c->tcp.req->id); c->chan = chan_open_send(opp,&sfwd_ops,c); c->id = PL_NOID; c->advwin = 0; c->leof = 0; c->reof = 0; oq_init(&c->oq); } static int setup_tcpfwd(STR acchost, unsigned int accport, int broken, unsigned int id) { char *shost; char *sport; int err; struct addrinfo *ai0; struct addrinfo *ai; struct addrinfo hints; SFWDLFD *fwdlist; SFWDLFD *l; SFWDREQ *r; int s; int n; char **errstrs; shost = blk_to_cstr(acchost.data,acchost.len); free_str(acchost); asprintf(&sport,"%u",accport); 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; err = getaddrinfo(shost[0]?shost:0,sport,&hints,&ai0); if (err) { logmsg(LM_NOTE|LM_PEER,"tcpip-forward %s/%s: %s",shost,sport,gai_strerror(err)); free(shost); free(sport); return(GLOBALREQ_FAIL); } if (! ai0) { logmsg(LM_NOTE|LM_PEER,"tcpip-forward %s/%s: lookup succeeded with no addresses?",shost,sport); free(shost); free(sport); return(GLOBALREQ_FAIL); } n = 0; for (ai=ai0;ai;ai=ai->ai_next) n ++; errstrs = malloc(n*sizeof(char *)); fwdlist = 0; for (ai=ai0,n=0;ai;ai=ai->ai_next,n++) { char hn[NI_MAXHOST]; char sn[NI_MAXSERV]; s = socket(ai->ai_family,ai->ai_socktype,ai->ai_protocol); if (s < 0) { asprintf(&errstrs[n],"socket: %s",strerror(errno)); continue; } if (bind(s,ai->ai_addr,ai->ai_addrlen) < 0) { asprintf(&errstrs[n],"bind: %s",strerror(errno)); close(s); continue; } errstrs[n] = 0; listen(s,10); set_nonblocking(s,1); l = malloc(sizeof(SFWDLFD)); l->fd = s; l->addrlen = ai->ai_addrlen; l->addr = malloc(l->addrlen); bcopy(ai->ai_addr,l->addr,l->addrlen); DLL_LINK_HEAD(l,fwdlist); err = getnameinfo(ai->ai_addr,ai->ai_addrlen,&hn[0],sizeof(hn),&sn[0],sizeof(sn),NI_NUMERICHOST|NI_NUMERICSERV|NI_WITHSCOPEID); if (err) { asprintf(&l->text,"[can't format address: %s]",strerror(err)); } else { asprintf(&l->text,"%s/%s",&hn[0],&sn[0]); } } if (! fwdlist) { logmsg(LM_NOTE|LM_PEER,"tcpip-forward %s/%s: can't establish any listening sockets",shost,sport); for (ai=ai0,n=0;ai;ai=ai->ai_next,n++) { char hn[NI_MAXHOST]; char sn[NI_MAXSERV]; err = getnameinfo(ai->ai_addr,ai->ai_addrlen,&hn[0],sizeof(hn),&sn[0],sizeof(sn),NI_NUMERICHOST|NI_NUMERICSERV|NI_WITHSCOPEID); if (err) { logmsg(LM_NOTE|LM_PEER,"tcpip-forward %s/%s: [can't format address: %s]: %s",shost,sport,strerror(err),errstrs[n]); } else { logmsg(LM_NOTE|LM_PEER,"tcpip-forward %s/%s: %s/%s: %s",shost,sport,&hn[0],&sn[0],errstrs[n]); } free(errstrs[n]); } free(errstrs); free(shost); free(sport); return(GLOBALREQ_FAIL); } for (ai=ai0,n=0;ai;ai=ai->ai_next,n++) free(errstrs[n]); free(errstrs); r = malloc(sizeof(SFWDREQ)); r->refs = 0; r->lfds = 0; asprintf(&r->text,"%s/%s",shost,sport); r->broken = broken; r->id = id; DLL_LINK_HEAD(r,sfwdreqs); sfwdreq_ref(r); while (fwdlist) { l = fwdlist; DLL_UNLINK(l,fwdlist); l->req = r; DLL_LINK_HEAD(l,r->lfds); sfwdreq_ref(r); l->id = add_poll_fd(l->fd,&rwtest_always,&rwtest_never,&sfwdlfd_acc,0,l); if (VERB(PROGRESS)) verb(PROGRESS,"tcpfwd: %s on %d for %s\n",l->text,l->fd,r->text); } free(shost); free(sport); return(GLOBALREQ_OK); } static int fwdmatch(struct sockaddr *a1, int l1, struct sockaddr *a2, int l2) { if ((l1 != l2) || (a1->sa_family != a2->sa_family)) return(0); switch (a1->sa_family) { case AF_INET: #define sin1 ((struct sockaddr_in *)a1) #define sin2 ((struct sockaddr_in *)a2) return( (sin1->sin_port == sin2->sin_port) && (sin1->sin_addr.s_addr == sin2->sin_addr.s_addr) ); #undef sin1 #undef sin2 break; case AF_INET6: #define sin61 ((struct sockaddr_in6 *)a1) #define sin62 ((struct sockaddr_in6 *)a2) return( (sin61->sin6_port == sin62->sin6_port) && IN6_ARE_ADDR_EQUAL(&sin61->sin6_addr,&sin62->sin6_addr) ); #undef sin61 #undef sin62 break; } logmsg(LM_ERR|LM_PEER,"fwdmatch: don't know how to compare af %d",a1->sa_family); return(0); } static int cancel_tcpfwd(STR acchost, unsigned int accport) { char *shost; char *sport; int err; struct addrinfo *ai0; struct addrinfo *ai; struct addrinfo hints; SFWDLFD *l; SFWDLFD *l2; SFWDREQ *r; SFWDREQ *r2; int n; shost = blk_to_cstr(acchost.data,acchost.len); free_str(acchost); asprintf(&sport,"%u",accport); 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; err = getaddrinfo(shost[0]?shost:0,sport,&hints,&ai0); if (err) { logmsg(LM_ERR|LM_PEER,"cancel-tcpip-forward %s/%s: %s",shost,sport,gai_strerror(err)); free(shost); free(sport); return(GLOBALREQ_FAIL); } if (! ai0) { logmsg(LM_ERR|LM_PEER,"cancel-tcpip-forward %s/%s: lookup succeeded with no addresses?",shost,sport); free(shost); free(sport); return(GLOBALREQ_FAIL); } for (r=sfwdreqs;r;r=r2) { r2 = r->flink; for <"lfwd"> (l=r->lfds;l;l=l2) { l2 = l->flink; for (ai=ai0,n=0;ai;ai=ai->ai_next,n++) { if (fwdmatch(l->addr,l->addrlen,ai->ai_addr,ai->ai_addrlen)) { DLL_UNLINK(l,r->lfds); if (VERB(PROGRESS)) verb(PROGRESS,"cancel-tcpip-forward: cancelling fd %d for %s\n",l->fd,l->text); remove_poll_id(l->id); close(l->fd); free(l->text); free(l->addr); sfwdreq_deref(l->req); free(l); continue <"lfwd">; } } } if (! r->lfds) { DLL_UNLINK(r,sfwdreqs); if (VERB(PROGRESS)) verb(PROGRESS,"cancel-tcpip-forward: cancelling request %s\n",r->text); sfwdreq_deref(r); } } freeaddrinfo(ai0); return(GLOBALREQ_OK); } static void dump_sfwdreqs(FILE *to) { SFWDREQ *r; SFWDLFD *l; for (r=sfwdreqs;r;r=r->flink) { fprintf(to,"%p: flink=%p blink=%p\n",(void *)r,(void *)r->flink,(void *)r->blink); fprintf(to," refs=%d text=%s broken=%d id=%d\n",r->refs,r->text,r->broken,r->id); fprintf(to," lfds:\n"); for (l=r->lfds;l;l=l->flink) { fprintf(to," %p: flink=%p blink=%p\n",(void *)l,(void *)l->flink,(void *)l->blink); fprintf(to," req=%p fd=%d id=%d text=%s\n",(void *)l->req,l->fd,l->id,l->text); if (l->req != r) fprintf(to,"**** req wrong\n"); switch (((struct sockaddr *)l->addr)->sa_family) { case AF_INET: { const char *v; char h[NI_MAXHOST]; char s[NI_MAXSERV]; v = "IPv4"; if (0) { case AF_INET6: v= "IPv6"; } if (getnameinfo(l->addr,l->addrlen,&h[0],NI_MAXHOST,&s[0],NI_MAXSERV,NI_NUMERICHOST|NI_NUMERICSERV|NI_WITHSCOPEID)) { fprintf(to," addr=[%s n->a error: %s]\n",v,strerror(errno)); } else { fprintf(to," addr=%s/%s\n",&h[0],&s[0]); } } break; default: fprintf(to," addr=[af%d]\n",((struct sockaddr *)l->addr)->sa_family); break; } } } } static int globalreq_s(ROSTR name, int wantrepl, const void *rest, int restlen) { NESTED void failure(const void *errat __attribute__((__unused__)), const char *fmt, ...) { va_list ap; FILE *f; f = logmsg_fopen(LM_NOTE|LM_PEER); fprintf(f,"protocol error: bad %.*s request: ",name.len,name.data); va_start(ap,fmt); vfprintf(f,fmt,ap); va_end(ap); fclose(f); exit(1); } if (str_equalcC(name,"tcpip-forward")) { STR acchost; unsigned int accport; parse_data(rest,restlen,&failure, PP_STRING(&acchost), PP_UINT32(&accport), PP_ENDSHERE ); return(setup_tcpfwd(acchost,accport,1,0)); } if (str_equalcC(name,"fixed-tcpip-forward@rodents.montreal.qc.ca")) { STR acchost; unsigned int accport; unsigned int id; parse_data(rest,restlen,&failure, PP_STRING(&acchost), PP_UINT32(&accport), PP_UINT32(&id), PP_ENDSHERE ); return(setup_tcpfwd(acchost,accport,0,id)); } if (str_equalcC(name,"cancel-tcpip-forward")) { STR acchost; unsigned int accport; parse_data(rest,restlen,&failure, PP_STRING(&acchost), PP_UINT32(&accport), PP_ENDSHERE ); return(cancel_tcpfwd(acchost,accport)); } if (str_equalcC(name,"fixed-cancel-tcpip@rodents.montreal.qc.ca")) { STR acchost; unsigned int accport; unsigned int serial; parse_data(rest,restlen,&failure, PP_STRING(&acchost), PP_UINT32(&accport), PP_UINT32(&serial), PP_ENDSHERE ); return(cancel_tcpfwd(acchost,accport)); } if (str_equalcC(name,"server-debug@rodents.montreal.qc.ca")) { STR args; char *buf; int len; FILE *f; unsigned char *opp; if (VERB(PROGRESS)) verb(PROGRESS,"[globalreq: server-debug]\n"); parse_data(rest,restlen,&failure,PP_STRING(&args),PP_ENDSHERE); f = fopen_alloc(&buf,&len); if (str_equalsC(args,"sfwd")) { dump_sfwdreqs(f); } else if (str_equalsC(args,"teardown")) { fprintf(f,"Not yet implemented\n"); } else { fprintf(f,"Unknown server debug request `%.*s'\n",args.len,args.data); fprintf(f," sfwd Dump forwarding requests\n"); fprintf(f," teardown Turn on connection teardown debugging\n"); } fclose(f); free_str(args); opp = global_rep_hdr(GREQ_OK); opp = put_string(opp,buf,len); global_send_rep(opp); return(GLOBALREQ_DONE); } printf("[globalreq: name %.*s wantreply %d restlen %d]\n",name.len,name.data,wantrepl,restlen); return(GLOBALREQ_UNK); } static NONCHANOPS gbl_ops_s = { &chanopen_s, &globalreq_s }; static void acc_accept(int id __attribute__((__unused__)), void *accv) { ACC *a; int new; pid_t kid; int i; BPP *b; char hbuf[NI_MAXHOST]; char sbuf[NI_MAXSERV]; a = accv; remlen = sizeof(rem); new = accept(a->fd,(void *)&rem,&remlen); if (new < 0) { logmsg(LM_ERR|LM_PEER,"accept (%s): %s",a->accstr,strerror(errno)); return; } lcllen = sizeof(lcl); if (getsockname(new,(void *)&lcl,&lcllen) < 0) { logmsg(LM_ERR|LM_PEER,"getsockname (%s): %s",a->accstr,strerror(errno)); close(new); return; } config_sa_ip_port(&rem,"remote-ip","remote-port"); config_sa_ip_port(&lcl,"local-ip","local-port"); if (! config_bool("one-conn")) { fflush(0); kid = moussh_fork(); if (kid != 0) { close(new); return; } } if (config_bool("net-keepalive")) { i = 1; setsockopt(new,SOL_SOCKET,SO_KEEPALIVE,&i,sizeof(i)); } remove_block_id(daemon_idle_id); for (i=AT__N-1;i>=0;i--) { int j; const ALGTYPE *t; void *a; t = at__list[i]; for (j=0;(a=(*t->list)(j));j++) (*t->servproc)(a); } for (i=0;iid); close(a->fd); free(a); } free(accs); sessions = 0; sfwdreqs = 0; sfwdconns = 0; b = malloc(sizeof(BPP)); bpp_setup(b,1); if (getnameinfo((void *)&rem,remlen,&hbuf[0],NI_MAXHOST,&sbuf[0],NI_MAXSERV,NI_NUMERICHOST|NI_NUMERICSERV|NI_WITHSCOPEID)) { asprintf(&b->peer_text,"[af %d, can't get strings (%s)]",lcl.ss_family,strerror(errno)); } else { asprintf(&b->peer_text,"%s/%s",&hbuf[0],&sbuf[0]); } verb(PROGRESS,"Accepted connection, server pid %d: %s\n",(int)getpid(),b->peer_text); setproctitle("serving %s",b->peer_text); fcntl(new,F_SETFD,1); b->fd = new; sendversion(b); push_layer(b,&layer_base); push_layer(b,&layer_d_i_d); push_layer(b,&layer_transport_s); ualayer = push_layer(b,&layer_userauth_conn_s); push_layer(b,&layer_channels); push_layer(b,&layer_catchall); set_globalops(&gbl_ops_s); bpp_add_poll(b); start_ssh_keepalive(); } static void add_acc(const char *s) { const char *slash; char *host; const char *port; struct addrinfo *ai0; struct addrinfo *ai; struct addrinfo hints; int err; int sock; char *txt; int v; char hn[NI_MAXHOST]; char sn[NI_MAXSERV]; slash = index(s,'/'); if (slash) { host = blk_to_cstr(s,slash-s); port = slash[1] ? slash+1 : "22"; } else { host = 0; port = s; } 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; err = getaddrinfo(host,port,&hints,&ai0); if (err) { logmsg(LM_ERR,"%s: %s",s,gai_strerror(err)); exit(1); } if (! ai0) { logmsg(LM_ERR,"%s: lookup succeeded with no addresses?",s); exit(1); } for (ai=ai0;ai;ai=ai->ai_next) { ACC *a; err = getnameinfo(ai->ai_addr,ai->ai_addrlen,&hn[0],sizeof(hn),&sn[0],sizeof(sn),NI_NUMERICHOST|NI_NUMERICSERV|NI_WITHSCOPEID); if (err) { asprintf(&txt,"%s [getnameinfo failed: %s]",s,strerror(err)); } else { asprintf(&txt,"%s/%s",&hn[0],&sn[0]); } sock = socket(ai->ai_family,ai->ai_socktype,ai->ai_protocol); if (sock < 0) { logmsg(LM_WARN,"%s: socket: %s",txt,strerror(errno)); } else if ((v=1),(setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&v,sizeof(v)) < 0)) { logmsg(LM_WARN,"%s: setsockopt SO_REUSEADDR: %s",txt,strerror(errno)); close(sock); } else if (bind(sock,ai->ai_addr,ai->ai_addrlen) < 0) { logmsg(LM_WARN,"%s: bind: %s",txt,strerror(errno)); close(sock); } else { listen(sock,10); a = malloc(sizeof(ACC)); a->n = nacc; a->sa = malloc(ai->ai_addrlen); bcopy(ai->ai_addr,a->sa,ai->ai_addrlen); a->accstr = txt; txt = 0; a->fd = sock; a->id = add_poll_fd(sock,&rwtest_always,&rwtest_never,&acc_accept,0,a); accs = realloc(accs,(nacc+1)*sizeof(*accs)); accs[nacc++] = a; } free(txt); } freeaddrinfo(ai0); free(host); } static int daemon_idle_block(void *arg __attribute__((__unused__))) { int rv; int srv; int i; rv = BLOCK_NIL; for (i=AT__N-1;i>=0;i--) { int j; const ALGTYPE *t; void *a; t = at__list[i]; for (j=0;(a=(*t->list)(j));j++) { srv = (*t->servidle)(a); switch (srv) { case BLOCK_NIL: case BLOCK_LOOP: break; default: if (srv < 0) panic("bad servidle status"); break; } /* New rv, in each of the nine possible cases: rv= NIL LOOP n>0 srv= \ -------------------- NIL | NIL LOOP rv LOOP | LOOP LOOP LOOP n>0 | srv LOOP min(rv,srv) Calling these cases 1 2 3 / 4 5 6 / 7 8 9, and putting a number in (parens) if the case is handled by an earlier test.... */ if ( (rv != BLOCK_LOOP) && /* cases 2, 5, 8 */ (srv != BLOCK_NIL) && /* cases 1, (2), 3 */ ( (srv == BLOCK_LOOP) || /* cases 4, (5), 6 */ (rv == BLOCK_NIL) || /* cases (1), (4), 7 */ (srv < rv) ) ) /* case 9 is all that's left */ { rv = srv; } } } return(rv); } void server_setup(void) { int i; struct sigaction sa; accs = 0; nacc = 0; if (nports < 1) { add_acc("22"); } else { for (i=0;i=0;i--) { int j; const ALGTYPE *t; void *a; t = at__list[i]; for (j=0;(a=(*t->list)(j));j++) (*t->servidle_init)(a); } daemon_idle_id = add_block_fn(&daemon_idle_block,0); setproctitle("server"); }