/* This file is in the public domain. */ /* * Client implementation. * * Many struct tags here begin with an otherwise undocumented c (and * their corresponding types, C). This stands for `client'; the * corresponding things in the server implementation typically begin * with s (resp. S). */ #include #include #include #include #include #include #include #include #include #include #include extern const char *__progname; #include "x.h" #include "bpp.h" #include "errf.h" #include "msgs.h" #include "util.h" #include "mpipe.h" #include "panic.h" #include "config.h" #include "nested.h" #include "pollloop.h" #include "pkt-util.h" #include "transport.h" #include "connshare.h" #include "withscopeid.h" #include "userauth-conn.h" #include "client.h" /* * States a session can be in. * * _NIL means "nascent, not yet set up". This should exist only very * briefly during the creation of a CSESSION. * * _OPEN means "open initiated, not yet completed". This covers quite * a lot of protocol back-and-forth; it begins when we send the * CHANNEL_OPEN and continues until we get a successful response to an * exec or shell request. * * _UP means "setup completed, normal operation proceeding". Most * CSESSIONs spend msot of their time in this state. * * _FLUSH means "protocol shut down, sticking around just to flush * pending output". * * _DEAD means "gone"; this should exist only transiently, after a * session is completely shut down but the code to finally free the * data structure hasn't yet run. */ typedef enum { CSS__NIL = 1, CSS_OPEN, CSS_UP, CSS_FLUSH, CSS_DEAD, } CS_STATE; /* * States an RTOL forwarding can be in. * * _START means we've requested the forwarding but haven't seen any * response yet. * * _LIVE represents normal operation. * * _STOP means it was once LIVE and we've now requsted cancellation, * but haven't got a response to the cancel request yet. * * _BROKEN means we requested cancellation but the request was refused. * Arguably this indicates a server bug, but the spec does not require * that servers accept forwading cancellations. We reject channel * open attempts for forwardings in this state. * * _DEAD indicates the forwarding has been shut down but the code to * free it up hasn't yet run. This state should exist only * transiently. * * _BLIP means we've requested the forwarding, but, before we got any * response, we ere locally told to shut it down. This is just like * _START except that our response to getting the server's reaction to * the forwarding request is different. */ typedef enum { RFS_START = 1, RFS_LIVE, RFS_STOP, RFS_BROKEN, RFS_DEAD, RFS_BLIP, } RTOL_STATE; /* * A forwarded connection type. * * _UNSET means "not yet set"; it should exist only transiently during * connection setup. * * _TCP and _X indicate, respectively, TCP and X connections. */ typedef enum { CFCT_UNSET = 1, CFCT_TCP, CFCT_X, } CFC_TYPE; /* * What technology an X conneciton used. * * _TCP indicates TCP; _LOCAL indicates AF_LOCAL sockets * (/tmp/.X11-unix/). _OTHER indicates "something else", ie, some * technology we don't understand. This is used only when the server * reports a technology the client doesn't recognize. */ typedef enum { CFXM_TCP = 1, CFXM_LOCAL, CFXM_OTHER, } CFX_METHOD; /* * State of a forwarded agent connection. These apply to data flowing * from the SSH server towards the agent, data we receive over the * channel and pass along to the fd. We care about this because we * have to do the FORWARDING_NOTICE dance. Data in the other * direction is always copied blindly; there is nothing there we have * to care about. * * _IDLE is indicates that nothing is going on on the connection - it * is between packets. * * _HEADER indicates that we're collecting a packet header. * * _NOTICE means we're copying the body of a FORWARDING_NOTICE packet. * * _BODY means we're copying the body of a packet other than a * FORWARDING_NOTICE packet. * * _DEAD is a transient state used during shutdown. */ typedef enum { CFAS_IDLE = 1, CFAS_HEADER, CFAS_NOTICE, CFAS_BODY, CFAS_DEAD, } CFA_STATE; /* * Reprompting state. * * The desire here is to (a) not have the CLI prompt appear immediately * when doing a server debug request and (b) get any input typed while * waiting for the server reprinted after the output happens and the * prompt is reissued. The reprompt_* stuff makes this happen. * * _NONE means "reprompt not in progress". * * _DRAIN means "output draining, print prompt once it's done". * * _PROMPT means "output has drained, now set PENDIN to retype input". */ typedef enum { RP_NONE = 1, RP_DRAIN, RP_PROMPT, } RP_STATE; /* * States local communication with the user can be in. * * These apply to tty-using operation only; non-tty operation has no * escape character processing or CLI, ever. (It also doesn't need * it, because it doesn't muck with the tty modes.) * * _NO means "normal operation, no special local state". * * _ESC means "escape character recognized". (This is not quite the * same as "escape character typed", because the escape character is * recognized only when typed after a CR or LF.) * * _CMD means "in the local CLI". */ typedef enum { LCL_NO = 1, LCL_ESC, LCL_CMD, } LOCALMODE; typedef struct ops OPS; typedef struct csession CSESSION; typedef struct cfwdreq CFWDREQ; typedef struct cfwdlfd CFWDLFD; typedef struct dq DQ; typedef struct cfwdconn CFWDCONN; typedef struct cfwdagent CFWDAGENT; /* * A forwarded agent connection. The reason we don't make agent * connections a subtype of CFWDCONN is that they are significantly * different: whereas TCP and X connections drop into a "just blindly * forward data both ways" mode (immediately, for TCP; after the X * connection setup data, for X), agent connectins never do, because * of the constant need to notice FORWARDING_NOTICE packets. (We need * to inject our own FORWARDING_NOTICE packet just before every packet * which is not immediately preceded by a FORWARDING_NOTICE packet, so * we have to parse packets enough to, at the very least, find packet * boundaries and identify which packets are FORWARDING_NOTICEs.) * * These are kept in a DLL through .flink and .blink, rooted in the * cfwdagents global. * * .ah is the opaque cookie returned by open_agent_connection(). * * .id is the pol ID for doing I/O on .ah. * * .chan is the channel number for this connection. * * .advwin is how much window we're advertising to the server. * * .state is the state of this connection. See the CFA_STATE * documentation for more. * * .oq is a queue of data waiting to be written to the agent. * * .hdr is usd to buffer the message header when in state CFAS_HEADER; * .fill is its fill "pointer". * * .bodyleft is the amount of body left to copy in states _NOTICE and * _BODY. */ struct cfwdagent { CFWDAGENT *flink; CFWDAGENT *blink; void *ah; int id; int chan; unsigned int advwin; CFA_STATE state; OQ oq; unsigned char hdr[5]; int fill; int bodyleft; } ; /* * A forwarded connection. This is the forwarded connection itself, * not the forwarding request. * * These are kept in a DLL thorugh .flink and .blink, rooted in the * cfwdconns global. * * .type is the type of the connection. * * .tcp is data specific to type CFCT_TCP: * * .tcp.req is the CFWDREQ for which this connection was opened. * * .tcp.clientadd and .tcp.clientport are the client address (text * form) and port number (binary) of the client - loosely put, * these are what getpeeraddr() gave. * * .x is data specific to type CFCT_X: * * .x.method describes what connection technology the X client * used to connect to the ssh server's pseudo-X-server. * * .x.tcp is data specific to .x.method CFXM_TCP: * * .x.tcp.clientaddr and .x.tcp.clientport are the client * address and port number. * * There is no data specif cto .x.method CFXM_LOCAL. * * .x.other is data specific to .x.method CFXM_OTHER: * * .x.other.method is the method name. * * .x.chdr is the connection authorization header buffer; .x.chlen * is its length. .x.chptr is a fill pointer into .x.chdr. These * are used only when processing the initial connection data. * * .x.session is a backpointer to the relevant CSESSION. * * .serial is a serial number for this connection. These exist to give * connections unique names in the CLI. * * .fd is the file descriptor for the local peer for this connection. * * .chan is the channel number for the channel for this connection. * * .id is the poll ID for I/O on .fd. * * .advwin is how much window we currently are advertising to the * server. * * .leof and .reof are booleans; .leof indicates we've seen EOF on the * local fd while .reof indicates we've received an EOF indication * from the server. * * .oq is a queue of output wiating to be written to .fd. */ 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; unsigned int advwin; int leof; int reof; OQ oq; } ; /* * A data queue. This is basically just a queue of data (.q) and a * file descriptor to send it to (.fd), plus a brief text description * (.tag) suitable for use in error messages. */ struct dq { int fd; const char *tag; OQ q; } ; /* * A listening file descriptor for LTOR TCP forwardings. There's a * list of these for each CFWDREQ. The reason there's a list rather * than just one is that a given forwading may involve listening on * multiple addresses, either because of multiple protocols (eg, IPv4 * and IPv6) or because we've been told to listen on a name that has * multiple addresses. * * These are kept in a DLL through .flink and .blink, rooted in the * CFWDREQ's .ltor.lfds. * * .req is a backpointer to the CFWDREQ this is for. * * .fd is the listening fd. * * .id is the poll ID waiting for connections. * * .text is a brief text ID for this accept socket, suitable for use in * error messages and the like. */ struct cfwdlfd { CFWDLFD *flink; CFWDLFD *blink; CFWDREQ *req; int fd; int id; char *text; } ; /* * A TCP forwarding request. * * These are kept in a DLL thorugh .flink and .blink, rooted in the * CSESSION's .fwdreqs member. * * .s is the CSESSION this request is for. * * .refs is a refcount. There are various things which an refer to * forwarding requests; refcounting makes it easy to keep them around * as long as they're needed but free them promptly when they're not. * * .dir is the direction for this forwarding; it's FF_DIR_LTOR or * FF_DIR_RTOL, from config.h. (Arguably this should be an enum....) * * .serial is an ID number for this forwarding. These provide * unambiguous names for forwardings in the CLI. * * .listenhost and .listenport are the host and port to listen on for * this forwarding; .connhost and .connport are the connect-to host * and port. Which ones we use locally and which ones are just passed * to the server depends on .dir. * * .text is brief descriptive text for this forwarding, suitable for * use in printed error messages or CLI list-of-forwadings output. * * .ltor is data specific to dir FF_DIR_LTOR: * * .ltor.lfds is the root of a list of listen-on file descriptors. * See the CFWDLFD comment for more. * * .rtol is data specific to dir FF_DIR_RTOL: * * .rtol.state is the forwarding's state. See the RTOL_STATE * comment for more. * * .rtol.flags is various flag bits: * * _BROKEN indicates we've fallen back to broken * forwarding for this request. * * _MUST indicates the forwarding is a "must work or don't * start" forwarding. */ 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; } ; } ; /* * A session. * * These are kept in a DLL through .flink and .blink, rooted in * csessions. * * .refs is a refcount. There are various things which an refer to * session; refcounting makes it easy to keep them around as long as * they're needed but free them promptly when they're not. * * .cmd is the command to execute, or nil if we're to do an interactive * login. * * .serial is a serial number for this session; these exist to give * each session an unambiguous name for use by the client CLI. * * .x_cookie is the cookie used in (fixed) X forwarding requests * generated for this session; .a_cookie is the same thing for (fixed) * agent forwarding requests. * * .state is the session's state; see the CS_STATE comment for more. * * .flags is various flags. See the comment header on check_pend for * an important note on the CSF_xPND bits. * * .autobg_id is a block ID, used transiently when auto-backgrounding * the client. * * .flush_id is a block ID, used when flushing pending output during * session shutdown. * * .odq and .edq are output and error data queues for the session. See * the comments on the codq and cedq globals for more. * * .chan is the channel number. * * .advwin is the amount of window we are advertising to our peer, when * not in split-window mode. (When we are in split-window mode, this * value is not used for antyhing.) * * .fwdreqs is the root of the list of TCP forwardings requested for * this session. * * .opendone is a function to be called as soon as the session enters * state CSS_UP. This may be nil if no function needs to be called * then. * * .xfreq is the X forwarding state, if any, for this session. This is * a handle, opaque outside x.c. * * .bxcookie is the random part of the cookie we use as authorization * data for broken X forwarding; .bxclen is its length. (Fixed X * forwarding doesn't pass X authorization info over the ssh * connection.) */ struct csession { CSESSION *flink; CSESSION *blink; int refs; char *cmd; unsigned int serial; unsigned int x_cookie; unsigned int a_cookie; CS_STATE state; unsigned int flags; #define CSF_IEOF 0x00000001 /* read EOF local->remote */ #define CSF_OEOF 0x00000002 /* read EOF remote->local */ #define CSF_SUSP 0x00000004 /* suspended */ #define CSF_AFWD 0x00000008 /* agent forwarding requested */ #define CSF_BAF 0x00000010 /* broken agent forwarding requested */ #define CSF_AFJO 0x00000020 /* Agent forwarding is just-once style */ #define CSF_AUSE 0x00000040 /* Agent forwarding has occurred */ #define CSF_APND 0x00000080 /* Agent forwarding pending */ #define CSF_XFWD 0x00000100 /* X forwarding requested */ #define CSF_BXF 0x00000200 /* broken X forwarding requested */ #define CSF_XFJO 0x00000400 /* X forwarding is just-once style */ #define CSF_XUSE 0x00000800 /* X forwarding has occurred */ #define CSF_XPND 0x00001000 /* X forwarding pending */ #define CSF_CPND 0x00002000 /* command execution pending */ int autobg_id; int flush_id; DQ odq; DQ edq; int chan; unsigned int advwin; CFWDREQ *fwdreqs; void (*opendone)(CSESSION *); CFXREQ *xfreq; unsigned char *bxcookie; int bxclen; } ; /* * In order to simplify switching between using channels.c's facilities * for non-shared use and connshare-s.c's failicites for shared use, * we define an ops vector that holds the functions we want to switch. */ 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_split_window) split_window; __typeof__(&chan_is_split_recv) splitp_recv; __typeof__(&chan_is_split_send) splitp_send; __typeof__(&chan_is_split_send_3) split3_send; __typeof__(&chan_get_send_window) get_send_window; __typeof__(&chan_get_recv_window) get_recv_window; __typeof__(&chan_add_rwin) add_rwin; __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; } ; /* * True if we're running in tty-aware mode; false, plain-data-stream. */ static int using_tty; /* * See the comment on the ops struct for more. * * This is the pointer that points to whichever OPS we're using. */ static const OPS *ops; /* * The BPP (see bpp.h) via which we speak with our peer. */ static BPP *bpp; /* * The root of the list of all sessions. */ static CSESSION *csessions; /* * The root of the list of all live forwarded TCP and X connections. */ static CFWDCONN *cfwdconns; /* * The root of the list of all live forwarded agent connections. */ static CFWDAGENT *cfwdagents; /* * Data describing our connection to the server. See client.h's * description of PEERID for more. */ static PEERID rem_id; /* * Flags indicating what kinds of state are saved for later * restoration. */ static unsigned int saved = 0; #define SAVE_TTY 0x00000001 /* stdin termios state saved */ #define SAVE_BLOCK 0x00000002 /* 0/1/2 blockingness saved */ /* * Saved termios state for stdin (_i) and output (_o). These are valid * iff SAVE_TTY is set in saved. */ static struct termios tio_i; static struct termios tio_o; /* * termios state for stdin when we want to do raw input. * * There is a potential bug lurking here. We initialize this to a * copyof tio_i, then use cfmakeraw() on it. This is fine for input, * but cfmakeraw() also affects output settings. Normally this is not * an issue, but if stdin and stdout are both ttys but are different * ttys, this means we will change the output settings on the input * tty but not on the output tty, which is probably a wrong thing. * * XXX Should revisit this, maybe do similar things to tio_o and have a * tio_o_raw? Make sure to think about the common case where stdin * and stdout are the same tty. */ static struct termios tio_i_raw; /* * Saved blocking state. Each of these is a boolean, saving the * blockingness of stdin (nonblock_i), stdout (_o), or stderr (_e). * A variable is true for "nonblocking", false for "blocking". */ static int nonblock_i; static int nonblock_o; static int nonblock_e; /* * Window size values. This is used to hold the window size settings * for sending to the server in pty-req and window-change requests. * It's also used to cache our current idea of the window size, so we * can tell whether it's actually changed when processing SIGWINCH. */ static struct winsize wsz; /* * Boolean for whether stdin is in raw mode (true) or not (false). */ static int raw_tty; /* * This holds a copy of c_cc[VSUSP]. This is used to support * suspending tty-using sessions. * * This can also hold -1, which represents "no suspc set". */ static int in_suspc = -1; /* * A printable representation of in_suspc. This is for printing, not * for comparing input against; for example, if in_suspc is 1, * suspc_txt is "^A". * * If this holds a non-nil pointer, it points to a mallocked string. */ static char *suspc_txt = 0; /* * These are counts of the number of DQs referring to stdout (ocount) * or stderr (ecount). These exist so that we can close a descriptor * once nothing is using it, but don't close it as long as something * still might be, even if something else that's using it shuts down. */ static int ocount; static int ecount; /* * Input escape character. This can be <0, which means "no escape * character", or an unsigned char value. */ static int in_esc; /* * A printable representatino of in_esc. This is for printing, not for * comparing input against. */ static const char *esc_txt; /* * Data queues for stdout (codq) and stderr(cedq). * * These are used for moussh interacting directly with the user, not * for stuff coming from a remote session; for the latter, the * session's odq and edq members are used instead. This separation * allows us to handle data windows more right; it also allows us to * freeze output from a session immediately when we suspend the * session, rather than having to let a potentially large amount of * data drain first. */ static DQ codq; static DQ cedq; /* * These are stdio interfaces to codq and cedq, used when a FILE * is * more convenient than a DQ. * * Note that fflush is needed to push data from the stdio buffers into * the DQs where it will actually get printed. */ static FILE *opf; static FILE *epf; /* * See the comment on the ops struct for more. * * These are the only OPS objects that exist; we just pick whichever * one is appropriate to the way we're operating. */ /* * Block ID for calling reprompt_block (qv). */ static int reprompt_id; /* * Reprompting state. See the comment on RP_STATE for more. */ static RP_STATE reprompt_state = RP_NONE; /* * Poll IDs for doing input on stdin (iid) and output on stdout (oid) * and stderr (eid). These are responsible for interfacing between * the underlying fds (0, 1, 2) and the various mechanisms we use * internally for handling the data. */ static int iid; static int oid; static int eid; /* * The current session. This is the session which input goes to and * the session whose output we consume and pass on to our output. * * May be nil, though normally not more than transiently. */ static CSESSION *cursession; /* * The current state of local communication with the user. See the * LOCALMODE comment for more. */ static LOCALMODE localmode = LCL_NO; /* * Boolean recording whether we're immediately after a CR-or-LF. This * controls whether the escape character is recognized. (The "CR or * LF" part of this is not configurable. P]erhaps it should be?) */ static int in_nl = 0; /* * Buffer used for accumulating commands in the local CLI. cmdbuf is * the buffer. cmdalloc is the amount of memory allocated to cmdbuf * (ie, the size passed to the malloc/realloc that gave us cmdbuf). * cmdlen is the current length of the command line. * * cmdalloc never decreases. cmdlen is reset to zero for each new * command line. */ static unsigned char *cmdbuf = 0; static int cmdalloc = 0; static int cmdlen = 0; /* * A pipe from us to ourselves, used to handle SIGWINCH without needing * to make more than very minimal assumptions about what we can do in * a signal handler. When we want to provoke window size handling, * either because of a received SIGWINCH or some other reason (eg, we * suspended ourselves and got resumed), we write a junk byte to this * pipe. Its readability from the other end trips the window-size * handling code. * * We set both ends of this nonblocking, to avoid a (theoretical only, * as far as I know) constipation deadlock. If the write end is * blocking, then a sufficient flood of SIGWINCH signals could lead to * the pipe filling up, leading to our blocking in write() inside the * SIGWINCH handler, blocking waiting for a read which will never * happen because the main line can't run until the handler returns. * * Because we don't care about exactly how many SIGWINCHes we've * received - all that matters is we've received at least one since we * last ran - we just set the write end nonblocking and let the * write() fail harmlessly in the above scenario. */ static int winchpipe[2]; /* * When in local CLI mode (localmode == LCL_CMD), this is a block * function ID for flushing opf's and epf's stdio buffers. * * When not in local CLI mode, this is meaningless. */ static int flusher_id; static const OPS ops_nonshared = { 0, &chan_open_hdr, &chan_open_send, &chan_open_ok, &chan_open_fail, &chan_set_ops, &chan_split_window, &chan_is_split_recv, &chan_is_split_send, &chan_is_split_send_3, &chan_get_send_window, &chan_get_recv_window, &chan_add_rwin, &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_split_window, &share_chan_is_split_recv, &share_chan_is_split_send, &share_chan_is_split_send_3, &share_chan_get_send_window, &share_chan_get_recv_window, &share_chan_add_rwin, &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 }; #if 0 #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 /* * 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 #include "pp.h" #include "oq.h" #include "str.h" #include "rnd.h" #include "dll.h" #include "agent.h" #include "escaped.h" #include "channels.h" #include "channels.h" #include "keepalive.h" #include "stdio-util.h" #include "agent-client.h" #define BUFFERSPACE 65536 typedef struct cmd CMD; typedef struct cipstate CIPSTATE; struct cipstate { CFWDCONN *c; struct addrinfo *ai0; struct addrinfo *ai; int sock; int id; } ; struct cmd { const char *cmd; void (*handler)(unsigned char *, int); } ; static unsigned int conn_serial = 0; static unsigned int session_serial = 0; static unsigned int fwdreq_serial = 0; static const OPS *ops; static void (*when_session_up)(void) = 0; /* 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 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 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 dq_weof(DQ *q) { oq_queue_special(&q->q,0,0); } 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(flusher_id); 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 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); } } /* * 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 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); } const PEERID *client_peer_id(void) { return(&rem_id); } void client_session_up(void (*fn)(void)) { when_session_up = fn; } #endif /* * Append data to a DQ. This always does an oq_queue_copy-style * append. */ 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); } /* * funopen()-style write callback used by opf and epf. */ static int pf_write(void *dqv, const char *data, int len) { dq_append(dqv,data,len); return(len); } /* * Print to standard output. The only thing this offers over simply * fprintf()ing to opf is that it tests to make sure we're doing * tty-style interaction, panicking if not. */ 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); } /* * Print to stdandard error. This may be used whether using_tty is set * or not; it prints to epf if so, stderr if not. */ 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); } /* * Initialize a DQ, given its fd and tag string. */ static void dq_init(DQ *q, int fd, const char *tag) { q->fd = fd; q->tag = tag; oq_init(&q->q); } /* * Figure out whether we want to do pty-style interaction or not. def * specifies a default decision, used if we can't find any * configuration specifying the answer. */ static int want_pty(int def) { return( config_isset("request-pty") ? config_bool("request-pty") : def ); } /* * Restore saved tty state, if any. * * Note we restore only stdin. We could restore stdout too, but we * don't explicitly change stdout, so, if it needs restoring, it got * changed 'cause it's the same as stdin - and in that case, restoring * stdin will restore stdout too. */ static void restore_tty(void) { if (saved & SAVE_TTY) { tcsetattr(0,TCSADRAIN|TCSASOFT,&tio_i); raw_tty = 0; } } /* * Save the blockingness of stdin, stdout, and stderr. */ 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; } /* * Set input, output, and error nonblocking - iff we're operating in * tty-aware mode. */ 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); } } /* * Restore saved blocking state for stdin/out/err, if any. */ 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); } } /* * Clean up and croak. * * This does not print any messages. If a message is appropriate, it * must be generated elsewhere. */ static void die(int ecode) { restore_tty(); restore_blocking(); exit(ecode); } /* * Put /dev/null on a descriptor. */ 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); } } /* * The server proposes to open a new channel. Handle the request. * * The only types of channel the server has any business initiating are * forwarded connections of various sorts: TCP, agent, and X. So we * reject any attempt to open anything else. */ static void c_gbl_chanopen(ROSTR type, int chan, const void *rest, int restlen) { #if 0 /* XXX FIX */ 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 #else rest=rest; #endif { 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("")); } } /* * Handle a global request. * * moussh implements no global requests which should ever be sent from * server to client, so we fail all requests. We also print a message * if we're using a tty, to alert the user to the misbehaviour. * * The reason we use the "x=x;" idiom to ignore rest rather than * marking it unused in the arglist is the expectatino that we will * eventualy support global requests in this direction. */ static REQRV c_gbl_globalreq(ROSTR name, int wantrepl, const void *rest, int restlen) { if (using_tty) printf("[globalreq: name %.*s wantreply %d restlen %d]\r\n",name.len,name.data,wantrepl,restlen); rest=rest; return(REQRET_UNK); } /* * Our global ops vector. We have only one of these; code that wants * other global ops lives in other files. */ static NONCHANOPS gbl_ops_c = { &c_gbl_chanopen, &c_gbl_globalreq }; /* * Open the underlying connection to the server. * * The primary thing this does is to set up the BPP's fd. It also sets * the remote-ip and remote-port config-file variables and, if they're * used for anything, local-ip and local-port as well. */ 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); } /* * Return a mallocked string holding a printable text representation of * a character, suitable for use in help messages and the like. */ 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); } /* * Save the tty state. Returns true if it worked, false if not. * * This stashes input state in tio_i and output state in tio_o; it also * computes tio_i_raw for future use. It does not, however, actually * do anything with tio_i_raw, leaving setting it up to code elsewhere * to handle. */ 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); } /* * Make sure the tty modes are set correctly. * * If we're not running in a tty-aware way, this is a total no-op. * Otherwise, it makes sure we're in raw mode whenever we're not * talking to the local CLI and have a live session which is up and * not suspended. * * This updates raw_tty as necessary to track the current rawness * state. It also uses it to avoid "changing" from raw to raw. */ static void modecheck(void) { int wantraw; if (! using_tty) return; wantraw = (localmode != LCL_CMD) && cursession && (cursession->state == CSS_UP) && !(cursession->flags & CSF_SUSP); if (wantraw == raw_tty) return; tcsetattr(0,TCSADRAIN|TCSASOFT,wantraw?&tio_i_raw:&tio_i); raw_tty = wantraw; } /* * Set up the escape character. This sets in_esc and esc_txt based on * configuration settings. */ 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); } /* * Block function to handle reprompting. See the comment on RP_STATE * for more. */ 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); } /* * Get the session's send window. This is complicated by the * possibility that split-window is in use. */ static int session_get_send_window(CSESSION *s) { return((*ops->get_send_window)( s->chan, (*ops->splitp_send)(s->chan) ? DFID_MAIN() : DFID_ALL() )); } /* * Check whether a session wants input; return true if so, false if * not. */ static int input_for_session(CSESSION *s) { return( s && (s->state == CSS_UP) && !(s->flags & (CSF_IEOF|CSF_SUSP)) && (session_get_send_window(s) > 0) ); } /* * Provoke a recheck of the window-size settings. This is the SIGWINCH * handler, but is sometimes called directly (eg, from suspend_it(), * after resuming). */ static void handle_winch(int sig __attribute__((__unused__))) { write(winchpipe[1],"",1); } /* * Send the current window size to the session. * * We send the request blind because we don't care whether it fails. * There's really nothing much we can do in that case. */ static void send_size(CSESSION *s) { unsigned char *opp; if (s->state == CSS_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); } } /* * Check the current window size settings. If they've changed, save * them, and, if we have a current session, tell it about the change. */ 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); } } /* * Called when the read end of winchpipe has becomke readable. This * drains up to 64 bytes per call; we could do more, but needing even * more than one is unusual enough I consider this sufficient. We * could loop until we get EWOULDBLOCK, but that would mean an * unnecessary syscall in the common case. Looping as long as we keep * getting full bufferfuls would fix that, but really strikes me as * unnecessary complexity. Having mokre than 64 bytes pending at all * is already a pretty pathological case. */ static void rd_winch(int id __attribute__((__unused__)), void *arg __attribute__((__unused__))) { char buf[64]; read(winchpipe[0],&buf[0],sizeof(buf)); check_size(); } /* * Suspend the client. This implements the escape-plus-suspc (also * escape-z) local action. */ 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); } /* * Quit the current session. (If there is no current session, just * beeps.) */ static void quit_cur(void) { if (cursession) (*ops->close)(cursession->chan); else oprf("\a"); } /* * Block function to flush opf's and epf's stdio buffers. This is set * as a block function upon entering local CLI mode and removed when * leaving it. * * We just do an all-FILEs flush and see if it resulted in anything * further being enqueued on codq or cedq. If so, we return * BLOCK_LOOP and let the data get written by the corresponding wtest * and write mechanisms; if not, BLOCK_NIL. * * This does mean that unrelated FILE *s are liable to get fflush()ed * at more or less unpredictable times when in local CLI mode. If * this becomes a problem, this code may need attention. */ 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 ); } /* * Enter local CLI mode. */ static void command_mode(void) { localmode = LCL_CMD; flusher_id = add_block_fn(&flush_out,0); modecheck(); cmdlen = 0; if (cursession) cursession->flags |= CSF_SUSP; oprf("\n%s> ",__progname); } /* * Background ourselves. This is not a reversible operation; it is * intended for cases where there are forwarded connections or some * such that mean we don't want to just exit, but the session has been * shut down and the user doesn't want to open another. * * The reason we remove iid rather than just letting it get EOF is that * we are probably running in tty-aware mode, in which case stdin * isn't just a stream-o'-bytes and EOF doesn't work the way it does * when in non-tty-aware mode. * * This routine returns in the child. The parent does not return; it * exits instead. */ 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); if (cursession) (*ops->send_eof)(cursession->chan,DFID_ALL(),0,0); 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 local_command(void) { /* XXX FIX THIS 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; } /* * Handle a byte of input to the local CLI. * * Not much to do here; just accumulate into cmdbuf until we get a CR * or LF, at which point we call local_command() to process the * accumulated command line. */ 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; } } /* * Read tty input. * * Unlike pipe input, we are always interested (in the pollloop.h * sense) in input when we're using a tty. This is because we want to * be able to recognize and handle escapes even if input is blocked. * We have to do _something_ when we get data while blocked by * back-pressure, though; we beep when that happens. */ 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 = session_get_send_window(cursession); if (win >= len) { (*ops->send_data)(cursession->chan,DFID_MAIN(),buf,len); } else { (*ops->send_data)(cursession->chan,DFID_MAIN(),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); } } /* * Input read test for non-tty-aware operation. This keeps us from * trying to read when there's nothing to be done with input if we did * read any. */ static int rtest_i(int id __attribute__((__unused__)), void *arg __attribute__((__unused__))) { return(input_for_session(cursession)); } /* * Read non-tty stdin. */ 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 = session_get_send_window(cursession); 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,DFID_ALL(),0,0); cursession->flags |= CSF_IEOF; } else { (*ops->send_data)(cursession->chan,DFID_MAIN(),&buf[0],r); } } /* * Write routine for stdout and stderr output. cq is the global (ie, * moussh-itself) DQ, codq or cedq; sq is the session's, or nil if * there is no session. countp points to the count figure for this * output stream, so we can close the fd on EOF if nobody else is * using it. */ 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) && sq && 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 == CSS_UP) && !(cursession->flags & CSF_OEOF) ) { XXX XXX XXX;; 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; } } } /* * Write routine for stdout output. Just punts to wr_oe. */ static void wr_o(int id __attribute__((__unused__)), void *arg __attribute__((__unused__))) { wr_oe(&codq,cursession?&cursession->odq:0,&ocount); } /* * Write routine for stderr output. Just punts to wr_oe. */ static void wr_e(int id __attribute__((__unused__)), void *arg __attribute__((__unused__))) { wr_oe(&cedq,cursession?&cursession->edq:0,&ecount); } /* * Write test routine for stdout output. */ 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) ) ); } /* * Write test routine for stderr output. */ 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) ) ); } /* * Main interface to this file. This is called by main.c when we're * running in client mode. * * In processes which should run polling loops, we just set everything * up, kick off any initial poll fds and/or block functions, and * return. main.c then drops into the polling loop. */ 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 (mpipe(&winchpipe[0],"winchpipe") < 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); fcntl(winchpipe[1],F_SETFL,fcntl(winchpipe[1],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); }