/* This file is in the public domain. */ #include #include #include #include #include extern const char *__progname; #include "pp.h" #include "rnd.h" #include "msgs.h" #include "util.h" #include "errf.h" #include "panic.h" #include "params.h" #include "config.h" #include "nested.h" #include "pollloop.h" #include "pkt-util.h" #include "algs-list.h" #include "bpp.h" static BPP *the_bpp = 0; static int abortcb_id = 0; LAYER *disconn_layer; void above_ipkt(LAYER *l) { (*l->above->ipkt)(l->above,l->above->arg); } void below_opkt(LAYER *l) { (*l->below->opkt)(l->below,l->below->arg); } void set_rekey(BPP *b) { b->want_rekey = 1; } void abort_bpp(void) { ABORTCB *cbs; ABORTCB *cb; cbs = the_bpp->abortcb; the_bpp->abortcb = 0; while (cbs) { cb = cbs; cbs = cb->link; (*cb->fn)(cb->arg); free(cb); } } #define abort_bpp(x) (abort_bpp)() static void svs_save(BPP *b, unsigned char ch) { if (*b->rem_vlen >= SSH_MAX_VERSION_LEN) { logmsg(LM_WARN|LM_PEER,"peer version string too long"); abort_bpp(b); exit(1); } b->rem_version[(*b->rem_vlen)++] = ch; } static int crypt_i(ENCALG *alg, void *state, void (*w)(const void *, int), const void *data, int len, int want) { int rv; unsigned char *obuf; int olen; NESTED void wfn(const void *b, int n) { obuf = realloc(obuf,olen+n); bcopy(b,obuf+olen,n); olen += n; (*w)(b,n); } if (VERB(PKT_CRYPT)) { verb(PKT_CRYPT,"In crypt (%d):\n",len); verb_data_block(VERBOSE_PKT_CRYPT,data,len); obuf = 0; olen = 0; } rv = encalg_i(alg,state,VERB(PKT_CRYPT)?&wfn:w,data,len,want); if (VERB(PKT_CRYPT)) { if (rv < len) verb(PKT_CRYPT,"Consumed only %d\n",rv); verb(PKT_CRYPT,"In clear (%d):\n",olen); verb_data_block(VERBOSE_PKT_CRYPT,obuf,olen); free(obuf); } return(rv); } static void crypt_o(ENCALG *alg, void *state, void (*w)(const void *, int), const void *data, int len) { unsigned char *obuf; int olen; NESTED void wfn(const void *b, int n) { obuf = realloc(obuf,olen+n); bcopy(b,obuf+olen,n); olen += n; (*w)(b,n); } if (VERB(PKT_CRYPT)) { verb(PKT_CRYPT,"Out clear (%d):\n",len); verb_data_block(VERBOSE_PKT_CRYPT,data,len); obuf = 0; olen = 0; } encalg_o(alg,state,VERB(PKT_CRYPT)?&wfn:w,data,len); if (VERB(PKT_CRYPT)) { verb(PKT_CRYPT,"Out crypt (%d):\n",olen); verb_data_block(VERBOSE_PKT_CRYPT,obuf,olen); free(obuf); } } static void bottom_i(BPP *b, const void *data, int len) { int n; int prevril; int i; NESTED void wraw(const void *d, int l) { if (b->pkt.ril+l > SSH_MAX_PACKET_LEN) panic("packet overflow"); bcopy(d,&b->rawipkt[b->pkt.ril],l); b->pkt.ril += l; } NESTED void wpay(const void *d, int l) { if (b->iplen+l > SSH_MAX_PAYLOAD_LEN) { logmsg(LM_ERR|LM_PEER,"bad packet on wire (payload overflow %d+%d > %d)",b->iplen,l,SSH_MAX_PAYLOAD_LEN); abort_bpp(b); exit(1); } bcopy(d,&b->ipkt[b->iplen],l); b->iplen += l; } if (VERB(PKT_WIRE)) { verb(PKT_WIRE,"In wire (%d",len); if (b->pkt.ril > 0) verb(PKT_WIRE,"+%d",b->pkt.ril); verb(PKT_WIRE,"):\n"); verb_data_block(VERBOSE_PKT_WIRE,data,len); } while (len > 0) { prevril = b->pkt.ril; if (b->pkt.ril < 5) { int pktlen; int padlen; int unit; n = crypt_i(b->r_enc,b->r_encstate,&wraw,data,len,5-b->pkt.ril); data = ((const char *)data) + n; len -= n; if (b->pkt.ril < 5) continue; pktlen = get_uint32(&b->rawipkt[0]); padlen = b->rawipkt[4]; if ( (pktlen < 5) || (pktlen > SSH_MAX_PACKET_LEN-4-b->r_mac->maclen) ) { logmsg(LM_WARN|LM_PEER,"bad packet on wire (length %#x)",pktlen); abort_bpp(b); exit(1); } if (padlen < 4) { logmsg(LM_WARN|LM_PEER,"bad packet on wire (too little padding %u)",padlen); abort_bpp(b); exit(1); } if (padlen+1 >= pktlen) { logmsg(LM_WARN|LM_PEER,"bad packet on wire (no payload: pktlen %u padlen %u)",pktlen,padlen); abort_bpp(b); exit(1); } unit = b->r_enc->blksize; if (unit < 8) unit = 8; if ((4+pktlen) % unit) { logmsg(LM_WARN|LM_PEER,"bad packet on wire (length %u not a multiple of %d)",4+pktlen,unit); abort_bpp(b); exit(1); } b->pkt.riw = 4 + pktlen; b->pkt.rit = b->pkt.riw + b->r_mac->maclen; b->pkt.pay = pktlen - padlen - 1; } if (b->pkt.ril < b->pkt.riw) { if (len > 0) { n = crypt_i(b->r_enc,b->r_encstate,&wraw,data,len,b->pkt.riw-b->pkt.ril); data = ((const char *)data) + n; len -= n; } } else if (b->pkt.ril < b->pkt.rit) { if (len > 0) { n = b->pkt.rit - b->pkt.ril; if (n > len) n = len; wraw(data,n); data = ((const char *)data) + n; len -= n; } } if ((prevril < b->pkt.riw) && (b->pkt.ril >= b->pkt.riw)) { b->iplen = 0; if (VERB(PKT_COMP) && (b->w_comp != &compalg_none)) { verb(PKT_COMP,"In comp (%d):\n",b->pkt.riw-5); verb_data_block(VERBOSE_PKT_COMP,&b->rawipkt[5],b->pkt.riw-5); } (*b->r_comp->process)(b->r_compstate,&b->rawipkt[5],b->pkt.pay,&wpay); (*b->r_comp->flush)(b->r_compstate,&wpay); } if ((prevril < b->pkt.rit) && (b->pkt.ril >= b->pkt.rit)) { unsigned char seqbuf[4]; b->r_seq ++; put_uint32(&seqbuf[0],b->r_seq); if (VERB(PKT_MAC)) { verb(PKT_MAC,"In MAC (%d):\n",4+b->pkt.riw); verb_data_blocks(VERBOSE_PKT_MAC,5, (const void *)&seqbuf[0], 4, (const void *)0, -1, (const void *)&b->rawipkt[0], b->iplen, (const void *)0, -1, (const void *)&b->rawipkt[b->pkt.riw], b->pkt.rit-b->pkt.riw ); } if (! (*b->r_mac->check)(b->r_macstate,&b->rawipkt[b->pkt.riw],2, (const void *)&seqbuf[0],4, (const void *)&b->rawipkt[0],b->pkt.riw)) { if (VERB(PKT_MAC)) { unsigned char good[b->r_mac->maclen]; verb(PKT_MAC,"MAC failure: "); (*b->r_mac->gen)(b->r_macstate,&good[0],2, (const void *)&seqbuf[0],4, (const void *)&b->rawipkt[0],b->pkt.riw); for (i=0;ir_mac->maclen;i++) verb(PKT_MAC," %02x",good[i]); verb(PKT_MAC,"\n"); } logmsg(LM_WARN|LM_PEER,"bad packet on wire (MAC failure)"); abort_bpp(b); exit(1); } if (b->ipkts_to_rekey-- <= 0) set_rekey(b); if ((b->ibytes_to_rekey-=b->pkt.riw) <= 0) set_rekey(b); if (VERB(PKT_DATA)) { verb(PKT_DATA,"In data (%d):\n",b->iplen); verb_data_block(VERBOSE_PKT_DATA,&b->ipkt[0],b->iplen); } (*b->botlayer->ipkt)(b->botlayer,b->botlayer->arg); b->pkt.ril = 0; } } } static void getversion(BPP *b, const void *data, int len) { const unsigned char *dp; int c; int l; char *t; char *p; do <"gotvers"> { dp = data; while (len > 0) { c = *dp++; len --; switch <"svs.state"> (b->svs.state) { case SVS_ATNL: switch (c) { case 'S': b->svs.state = SVS_GOT_S; break; case '\n': b->svs.state = SVS_ATNL; break; default: b->svs.state = SVS_FLUSHING; break; } break; case SVS_GOT_S: switch (c) { case 'S': b->svs.state = SVS_GOT_SS; break; case '\n': b->svs.state = SVS_ATNL; break; default: b->svs.state = SVS_FLUSHING; break; } break; case SVS_GOT_SS: switch (c) { case 'H': b->svs.state = SVS_GOT_SSH; break; case '\n': b->svs.state = SVS_ATNL; break; default: b->svs.state = SVS_FLUSHING; break; } break; case SVS_GOT_SSH: switch (c) { case '-': b->svs.state = SVS_GOT_SSH_; break; case '\n': b->svs.state = SVS_ATNL; break; default: b->svs.state = SVS_FLUSHING; break; } break; case SVS_GOT_CR: switch (c) { case '\n': break <"gotvers">; default: svs_save(b,'\r'); case <"svs.state"> SVS_GOT_SSH_: switch (c) { case '\n': break <"gotvers">; case '\r': b->svs.state = SVS_GOT_CR; break; default: svs_save(b,c); break; } break; } break; case SVS_FLUSHING: switch (c) { case '\n': b->svs.state = SVS_ATNL; break; } break; } } return; } while (0); if (VERB(PROGRESS)) verb(PROGRESS,"remote version string: %.*s\n",*b->rem_vlen,b->rem_version); if (! ( ((*b->rem_vlen >= 8) && !bcmp(b->rem_version,"SSH-2.0-",8)) || ((*b->rem_vlen >= 9) && !bcmp(b->rem_version,"SSH-1.99-",9)) ) ) { logmsg(LM_WARN|LM_PEER,"unrecognized peer version"); abort_bpp(b); exit(1); } t = blk_to_cstr(b->rem_version,*b->rem_vlen); config_set_str("remote-version-entire",t); free(t); c = index(b->rem_version+4,'-') - (b->rem_version+4); t = blk_to_cstr(b->rem_version+4,c); config_set_str("remote-version-proto",t); free(t); p = b->rem_version + 4 + c + 1; l = *b->rem_vlen - 4 - c - 1; for <"swvers"> (c=0;c; } } t = blk_to_cstr(p,c); config_set_str("remote-version-sw",t); free(t); p += c; l -= c; for <"skipsep"> (c=0;c; } } t = blk_to_cstr(p+c,l-c); config_set_str("remote-version-comment",t); free(t); if (VERB(PROGRESS)) verb(PROGRESS,"version string done, starting stack\n"); b->input = &bottom_i; b->pkt.ril = 0; if (len) bottom_i(b,dp,len); } void bpp_setup(BPP *b, int serverp) { b->fd = -1; /* id set in bpp_add_poll, not here */ b->peer_text = 0; b->noreads = 0; b->w_enc = (*at_enc.find_c)("none"); b->w_encstate = encalg_init(b->w_enc,"","",'w'); b->r_enc = b->w_enc; b->r_encstate = encalg_init(b->r_enc,"","",'r'); b->w_comp = (*at_comp.find_c)("none"); b->w_compstate = compalg_init(b->w_comp,'w'); b->r_comp = b->w_comp; b->r_compstate = compalg_init(b->r_comp,'r'); b->w_mac = (*at_mac.find_c)("none"); b->w_macstate = macalg_init(b->w_mac,""); b->r_mac = b->w_mac; b->r_macstate = macalg_init(b->r_mac,""); /* kex, hk, K_S, kex_k, kex_klen, kex_h, kex_hlen, kex_hash, sessid, sessidlen not set until key exchange happens */ b->w_seq = 0U - 1; b->r_seq = 0U - 1; /* hashbuf set and used only in transport.c */ b->toplayer = 0; b->botlayer = 0; b->serverp = serverp; if (serverp) { b->lcl_version = &b->s_version[0]; b->lcl_vlen = &b->s_vlen; b->rem_version = &b->c_version[0]; b->rem_vlen = &b->c_vlen; } else { b->lcl_version = &b->c_version[0]; b->lcl_vlen = &b->c_vlen; b->rem_version = &b->s_version[0]; b->rem_vlen = &b->s_vlen; } bcopy("SSH-",b->rem_version,4); *b->rem_vlen = 4; b->svs.state = SVS_ATNL; b->c_kexinit_payload = 0; /* c_kexinit_paylen doesn't need initalizing */ b->s_kexinit_payload = 0; /* s_kexinit_paylen doesn't need initalizing */ b->ipkts_to_rekey = 1<<30; b->opkts_to_rekey = 1<<30; b->ibytes_to_rekey = 1<<30; b->obytes_to_rekey = 1<<30; b->want_rekey = 0; b->input = &getversion; oq_init(&b->output); /* rawipkt, ipkt, iplen, rawopkt, opkt, oplen don't need initializing */ b->abortcb = 0; the_bpp = b; } void sendversion(BPP *b) { strcpy(&b->lcl_version[0],"SSH-2.0-Moussh0.1 (fnord!)"); *b->lcl_vlen = strlen(b->lcl_version); oq_queue_point(&b->output,b->lcl_version,*b->lcl_vlen); oq_queue_point(&b->output,"\r\n",2); } static void *base_init(LAYER *l) { disconn_layer = l; return(0); } static void base_o(LAYER *l, void *arg __attribute__((__unused__))) { BPP *b; unsigned int padlen; unsigned int padmod; unsigned int paylen; unsigned int pktlen; unsigned int n; unsigned char seqbuf[4]; unsigned char *owbuf; int owlen; NESTED void wdata(const void *data, int len) { if (VERB(PKT_WIRE)) { owbuf = realloc(owbuf,owlen+len); bcopy(data,owbuf+owlen,len); owlen += len; } oq_queue_copy(&b->output,data,len); } NESTED void writepay(const void *data, int len) { if (5+paylen+len+b->w_mac->maclen > SSH_MAX_PACKET_LEN) { logmsg(LM_ERR|LM_PEER,"compressed data too long (%d+%d+%d+%d > %d)",5,paylen,len,b->w_mac->maclen,SSH_MAX_PACKET_LEN); abort_bpp(b); exit(1); } bcopy(data,&b->rawopkt[5+paylen],len); paylen += len; } b = l->b; if (b->oplen > SSH_MAX_PAYLOAD_LEN) { logmsg(LM_ERR|LM_PEER,"writing too-large packet (payload length %u)",b->oplen); abort_bpp(b); exit(1); } paylen = 0; if (VERB(PKT_DATA)) { verb(PKT_DATA,"Out data (%d):\n",b->oplen); verb_data_block(VERBOSE_PKT_DATA,&b->opkt[0],b->oplen); } (*b->w_comp->process)(b->w_compstate,&b->opkt[0],b->oplen,writepay); (*b->w_comp->flush)(b->w_compstate,writepay); if (VERB(PKT_COMP) && (b->w_comp != &compalg_none)) { verb(PKT_COMP,"Out comp (%d):\n",paylen); verb_data_block(VERBOSE_PKT_COMP,&b->rawopkt[5],paylen); } padmod = b->w_enc->blksize; if (padmod < 8) padmod = 8; padlen = 4; pktlen = 5 + paylen + padlen; n = pktlen % padmod; if (n) padlen += padmod - n; if (pktlen < 16) { padlen = 16 - (5 + paylen); pktlen = 5 + paylen + padlen; n = pktlen % padmod; if (n) padlen += padmod - n; } pktlen = 5 + paylen + padlen; random_data(&b->rawopkt[5+paylen],padlen); put_uint32(&b->rawopkt[0],pktlen-4); b->rawopkt[4] = padlen; b->w_seq ++; put_uint32(&seqbuf[0],b->w_seq); (*b->w_mac->gen)(b->w_macstate,&b->rawopkt[pktlen],2, (const void *)&seqbuf[0],4, (const void *)&b->rawopkt[0],pktlen); if (VERB(PKT_MAC)) { verb(PKT_MAC,"Out MAC (%d):\n",4+pktlen); verb_data_blocks(VERBOSE_PKT_MAC,6, (const void *)&seqbuf[0], 4, (const void *)&b->rawopkt[0], pktlen-padlen, (const void *)0, -1, (const void *)&b->rawopkt[pktlen-padlen], padlen, (const void *)0, -1, (const void *)&b->rawopkt[pktlen], b->w_mac->maclen ); } if (VERB(PKT_WIRE)) { owbuf = 0; owlen = 0; } crypt_o(b->w_enc,b->w_encstate,&wdata,&b->rawopkt[0],pktlen); if (b->w_mac->maclen) wdata(&b->rawopkt[pktlen],b->w_mac->maclen); if (VERB(PKT_WIRE)) { verb(PKT_WIRE,"Out wire (%d):\n",pktlen+b->w_mac->maclen); verb_data_block(VERBOSE_PKT_WIRE,owbuf,owlen); free(owbuf); } if (b->opkts_to_rekey-- <= 0) set_rekey(b); if ((b->obytes_to_rekey-=pktlen) <= 0) set_rekey(b); } LAYERDESC layer_base = { &base_init, 0, &base_o }; static void d_i_d_i(LAYER *l, void *arg __attribute__((__unused__))) { BPP *b; b = l->b; if (b->iplen < 1) { logmsg(LM_WARN|LM_PEER,"ignoring empty protocol packet"); return; } switch (b->ipkt[0]) { case SSH_MSG_DISCONNECT: { unsigned int reason; STR desc; STR lang; parse_packet(b,&pp_fail, PP_IGNORE(1), PP_UINT32(&reason), PP_STRING(&desc), PP_STRING(&lang), PP_ENDSHERE ); printf("Disconnect received: "); switch (reason) { case SSH_DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT: printf("Host not allowed to connect"); break; case SSH_DISCONNECT_PROTOCOL_ERROR: printf("Protocol error"); break; case SSH_DISCONNECT_KEY_EXCHANGE_FAILED: printf("Key exchange failed"); break; case SSH_DISCONNECT_RESERVED: printf("Reserved"); break; case SSH_DISCONNECT_MAC_ERROR: printf("Mac error"); break; case SSH_DISCONNECT_COMPRESSION_ERROR: printf("Compression error"); break; case SSH_DISCONNECT_SERVICE_NOT_AVAILABLE: printf("Service not available"); break; case SSH_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED: printf("Protocol version not supported"); break; case SSH_DISCONNECT_HOST_KEY_NOT_VERIFIABLE: printf("Host key not verifiable"); break; case SSH_DISCONNECT_CONNECTION_LOST: printf("Connection lost"); break; case SSH_DISCONNECT_BY_APPLICATION: printf("By application"); break; case SSH_DISCONNECT_TOO_MANY_CONNECTIONS: printf("Too many connections"); break; case SSH_DISCONNECT_AUTH_CANCELLED_BY_USER: printf("Auth cancelled by user"); break; case SSH_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE: printf("No more auth methods available"); break; case SSH_DISCONNECT_ILLEGAL_USER_NAME: printf("Illegal user name"); break; default: printf("unknown reason %u",reason); break; } printf("\n"); abort_bpp(b); exit(0); } break; case SSH_MSG_IGNORE: break; case SSH_MSG_DEBUG: { int disp; STR msg; STR lang; parse_packet(b,&pp_fail, PP_IGNORE(1), PP_BOOL(&disp), PP_STRING(&msg), PP_STRING(&lang), PP_ENDSHERE ); printf("Debug message [%.*s]: %.*s\n",lang.len,lang.data,msg.len,msg.data); } break; default: above_ipkt(l); break; } } LAYERDESC layer_d_i_d = { 0, &d_i_d_i, 0 }; static void catchall_i(LAYER *l, void *arg __attribute__((__unused__))) { logmsg(LM_WARN|LM_PEER,"unhandled input packet, type %d",l->b->ipkt[0]); abort_bpp(l->b); exit(1); } LAYERDESC layer_catchall = { 0, &catchall_i, 0 }; static void bypass_i(LAYER *l, void *arg __attribute__((__unused__))) { above_ipkt(l); } static void bypass_o(LAYER *l, void *arg __attribute__((__unused__))) { below_opkt(l); } LAYER *push_layer(BPP *b, const LAYERDESC *ld) { LAYER *l; l = malloc(sizeof(LAYER)); l->b = b; l->ipkt = ld->ifn ?: &bypass_i; l->opkt = ld->ofn ?: &bypass_o; l->below = b->toplayer; l->above = 0; if (l->below) l->below->above = l; else b->botlayer = l; b->toplayer = l; l->arg = ld->init ? (*ld->init)(l) : 0; l->desc = ld; return(l); } static int rtest_bpp(int id __attribute__((__unused__)), void *arg) { return(!((BPP *)arg)->noreads); } static int wtest_bpp(int id __attribute__((__unused__)), void *arg) { return(oq_nonempty(&((BPP *)arg)->output)); } static void net_read(int id __attribute__((__unused__)), void *arg) { BPP *b; char buf[8192]; int n; b = arg; n = read(b->fd,&buf[0],sizeof(buf)); if (n < 0) { switch (errno) { case EINTR: case EWOULDBLOCK: return; break; } logmsg(LM_NOTE|LM_PEER,"net read: %s",strerror(errno)); abort_bpp(b); exit(1); } if (n == 0) { logmsg(LM_NOTE|LM_PEER,"net read EOF"); abort_bpp(b); exit(1); } (*b->input)(b,&buf[0],n); } static void net_write(int id __attribute__((__unused__)), void *arg) { BPP *b; int w; b = arg; w = oq_writev(&b->output,b->fd,&oq_canthappen); if (w < 0) { switch (errno) { case EINTR: case EWOULDBLOCK: return; break; } logmsg(LM_NOTE|LM_PEER,"net write: %s",strerror(errno)); abort_bpp(b); exit(1); } if (w == 0) { logmsg(LM_NOTE|LM_PEER,"zero net write"); abort_bpp(b); exit(1); } oq_dropdata(&b->output,w); } void bpp_add_poll(BPP *b) { b->id = add_poll_fd(b->fd,&rtest_bpp,&wtest_bpp,&net_read,&net_write,b); } void send_disconnect(int reason, const void *desc, int desclen, const void *lang, int langlen) { unsigned char *opp; opp = &disconn_layer->b->opkt[0]; *opp++ = SSH_MSG_DISCONNECT; opp = put_uint32(opp,reason); opp = put_string(opp,desc,desclen); opp = put_string(opp,lang,langlen); disconn_layer->b->oplen = opp - &disconn_layer->b->opkt[0]; (*disconn_layer->opkt)(disconn_layer,disconn_layer->arg); net_write(0,disconn_layer->b); } void bpp_done_reading(BPP *b) { b->noreads = 1; } const char *bpp_peer_text(void) { if (the_bpp && the_bpp->peer_text) return(the_bpp->peer_text); return("(no peer)"); } int bpp_add_abort(BPP *bpp, void (*fn)(void *), void *arg) { ABORTCB *cb; cb = malloc(sizeof(ABORTCB)); cb->id = abortcb_id++; cb->fn = fn; cb->arg = arg; cb->link = bpp->abortcb; bpp->abortcb = cb; return(cb->id); } void bpp_del_abort(BPP *bpp, int id) { ABORTCB **cbp; ABORTCB *cb; cbp = &bpp->abortcb; while ((cb = *cbp)) { if (cb->id == id) { *cbp = cb->link; free(cb); } else { cbp = &cb->link; } } }