#include #include #include #include #include #include #include #include "pp.h" #include "rnd.h" #include "bpp.h" #include "algs.h" #include "msgs.h" #include "cmdline.h" #include "pkt-util.h" #include "transport.h" #include "userauth-conn.h" extern const char *__progname; typedef struct transport_state TRANSPORT_STATE; struct transport_state { int dummy; } ; static int fd; static FILE *fw; static FILE *fr; static BPP bpp; static LAYER *base_layer; static void openconn(void) { struct addrinfo hints; struct addrinfo *ai0; struct addrinfo *ai; int e; int se; hints.ai_flags = 0; hints.ai_family = PF_UNSPEC; 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; if (! host) { fprintf(stderr,"%s: need a host to connect to\n",__progname); exit(1); } if (! port) port = "22"; e = getaddrinfo(host,port,&hints,&ai0); if (e) { fprintf(stderr,"%s: %s/%s: %s\n",__progname,host,port,gai_strerror(e)); exit(1); } if (! ai0) { fprintf(stderr,"%s: %s/%s: successful lookup but no addresses?\n",__progname,host,port); exit(1); } se = 0; for (ai=ai0;ai;ai=ai->ai_next) { 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) { char hnbuf[NI_MAXHOST]; char pnbuf[NI_MAXSERV]; e = errno; if (getnameinfo(ai->ai_addr,ai->ai_addrlen,&hnbuf[0],NI_MAXHOST,&pnbuf[0],NI_MAXSERV,NI_NUMERICHOST|NI_NUMERICSERV)) { fprintf(stderr,"%s: %s%s%s: connect failed [%s], can't get numeric hostname info [%s]\n",__progname,host,port?"/":"",port?:"",strerror(e),strerror(errno)); } else { if (ai0->ai_next) { fprintf(stderr,"%s: connect %s [%s/%s]: %s\n",__progname,host,&hnbuf[0],&pnbuf[0],strerror(e)); } else { fprintf(stderr,"%s: connect %s/%s: %s\n",__progname,&hnbuf[0],&pnbuf[0],strerror(e)); } } close(fd); continue; } freeaddrinfo(ai0); fw = fdopen(fd,"w"); fr = fdopen(fd,"r"); return; } if (se > 0) { fprintf(stderr,"%s: socket: %s\n",__progname,strerror(se)); } else if (se == 0) { fprintf(stderr,"%s: can't connect, can't tell why\n",__progname); } exit(1); } static void sendversion(void) { strcpy(&bpp.c_version[0],"SSH-2.0-Moussh0.1 (fnord!)"); bpp.c_vlen = strlen(&bpp.c_version[0]); fprintf(fw,"%s\r\n",&bpp.c_version[0]); fflush(fw); } static void getversion(void) { int state; #define GVS_ATNL 1 #define GVS_GOT_S 2 #define GVS_GOT_SS 3 #define GVS_GOT_SSH 4 #define GVS_GOT_SSH_ 5 #define GVS_GOT_CR 6 #define GVS_FLUSHING 7 int c; static void vstr_save(unsigned char ch) { if (bpp.s_vlen >= SSH_MAX_VERSION_LEN) { fprintf(stderr,"%s: peer version string too long\n",__progname); exit(1); } bpp.s_version[bpp.s_vlen++] = ch; } state = GVS_ATNL; bcopy("SSH-",&bpp.s_version[0],4); bpp.s_vlen = 4; while <"getvers"> (1) { c = getc(fr); if (c == EOF) { fprintf(stderr,"%s: missing/incomplete peer version string\n",__progname); exit(1); } switch (state) { case GVS_ATNL: switch (c) { case 'S': state = GVS_GOT_S; break; case '\n': state = GVS_ATNL; break; default: state = GVS_FLUSHING; break; } break; case GVS_GOT_S: switch (c) { case 'S': state = GVS_GOT_SS; break; case '\n': state = GVS_ATNL; break; default: state = GVS_FLUSHING; break; } break; case GVS_GOT_SS: switch (c) { case 'H': state = GVS_GOT_SSH; break; case '\n': state = GVS_ATNL; break; default: state = GVS_FLUSHING; break; } break; case GVS_GOT_SSH: switch (c) { case '-': state = GVS_GOT_SSH_; break; case '\n': state = GVS_ATNL; break; default: state = GVS_FLUSHING; break; } break; case GVS_GOT_SSH_: switch (c) { case '\n': break <"getvers">; case '\r': state = GVS_GOT_CR; break; default: vstr_save(c); break; } break; case GVS_GOT_CR: switch (c) { case '\n': break <"getvers">; default: vstr_save('\r'); ungetc(c,fr); state = GVS_GOT_SSH_; break; } break; case GVS_FLUSHING: switch (c) { case '\n': state = GVS_ATNL; break; } break; } } printf("remote version string: %.*s\n",bpp.s_vlen,&bpp.s_version[0]); #undef GVS_ATNL #undef GVS_GOT_S #undef GVS_GOT_SS #undef GVS_GOT_SSH #undef GVS_GOT_SSH_ #undef GVS_FLUSHING } static void checkversion(void) { if ((bpp.s_vlen >= 8) && !bcmp(&bpp.s_version[0],"SSH-2.0-",8)) return; if ((bpp.s_vlen >= 9) && !bcmp(&bpp.s_version[0],"SSH-1.99-",9)) return; fprintf(stderr,"%s: unrecognized peer version\n",__progname); exit(1); } static void rd_fp(void *fvp, void *buf, int len) { fread(buf,1,len,fvp); } static void wr_fp(void *fvp, const void *buf, int len) { fwrite(buf,1,len,fvp); } static void toplayer_r(BPP *b) { return((*b->toplayer->rpkt)(b->toplayer,b->toplayer->arg)); } #if 0 static void toplayer_w(BPP *b) { (*b->toplayer->wpkt)(b->toplayer,b->toplayer->arg); } #endif void lower_read(LAYER *l) { (*l->lower->rpkt)(l->lower,l->lower->arg); } void lower_write(LAYER *l) { (*l->lower->wpkt)(l->lower,l->lower->arg); } void send_disconnect(int reason, const void *desc, int desclen, const void *lang, int langlen) { unsigned char *opp; opp = &bpp.opkt[0]; *opp++ = SSH_MSG_DISCONNECT; opp = put_uint32(opp,reason); opp = put_string(opp,desc,desclen); opp = put_string(opp,lang,langlen); bpp.oplen = opp - &bpp.opkt[0]; (*base_layer->wpkt)(base_layer,base_layer->arg); } static void bypass_r(LAYER *l, void *arg __attribute__((__unused__))) { lower_read(l); } static void bypass_w(LAYER *l, void *arg __attribute__((__unused__))) { lower_write(l); } static void push_layer(BPP *b, const LAYERDESC *ld) { LAYER *l; l = malloc(sizeof(LAYER)); l->b = b; l->rpkt = ld->rfn ?: bypass_r; l->wpkt = ld->wfn ?: bypass_w; l->arg = ld->init ? (*ld->init)(l) : 0; l->lower = b->toplayer; b->toplayer = l; } static void *base_init(LAYER *l) { base_layer = l; return(0); } static void base_r(LAYER *l, void *arg __attribute__((__unused__))) { BPP *b; unsigned int pktlen; unsigned char padlen; unsigned int paylen; unsigned int uclen; unsigned char seqbuf[4]; static void writepay(const void *data, int len) { if (uclen+len > SSH_MAX_PAYLOAD_LEN) { fprintf(stderr,"%s: bad packet on wire: uncompressed data too long (%d+%d > %d)\n",__progname,uclen,len,SSH_MAX_PAYLOAD_LEN); exit(1); } bcopy(data,&b->ipkt[uclen],len); uclen += len; } b = l->b; fflush(fw); encalg_r(b->r_enc,b->r_encstate,rd_fp,fr,&b->rawipkt[0],5); pktlen = get_uint32(&b->rawipkt[0]); if (4+pktlen+b->r_mac->maclen > SSH_MAX_PACKET_LEN) { fprintf(stderr,"%s: bad packet on wire (length %#x)\n",__progname,pktlen); exit(1); } padlen = b->rawipkt[4]; if (padlen+1 >= pktlen) { fprintf(stderr,"%s: bad packet on wire (no payload: pktlen %u padlen %u)\n",__progname,pktlen,padlen); exit(1); } paylen = pktlen - padlen - 1; encalg_r(b->r_enc,b->r_encstate,rd_fp,fr,&b->rawipkt[5],pktlen-1); rd_fp(fr,&b->rawipkt[5+pktlen-1],b->r_mac->maclen); uclen = 0; (*b->r_comp->process)(b->r_compstate,&b->rawipkt[5],paylen,writepay); (*b->r_comp->flush)(b->r_compstate,writepay); b->iplen = uclen; b->r_seq ++; put_uint32(&seqbuf[0],b->r_seq); if (! (*b->r_mac->check)(b->r_macstate,&b->rawipkt[4+pktlen],2, (const void *)&seqbuf[0],4, (const void *)&b->rawipkt[0],5+paylen+padlen)) { fprintf(stderr,"%s: bad packet on wire (MAC failure)\n",__progname); exit(1); } if (b->ipkts_to_rekey-- <= 0) b->want_rekey = 1; if ((b->ibytes_to_rekey-=5+paylen+padlen) <= 0) b->want_rekey = 1; } static void base_w(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]; static void writepay(const void *data, int len) { if (5+paylen+len+b->w_mac->maclen > SSH_MAX_PACKET_LEN) { fprintf(stderr,"%s: compressed data too long (%d+%d+%d+%d > %d)\n",__progname,5,paylen,len,b->w_mac->maclen,SSH_MAX_PACKET_LEN); exit(1); } bcopy(data,&b->rawopkt[5+paylen],len); paylen += len; } b = l->b; if (b->oplen > SSH_MAX_PAYLOAD_LEN) { fprintf(stderr,"%s: writing too-large packet (payload length %u)\n",__progname,b->oplen); exit(1); } paylen = 0; (*b->w_comp->process)(b->w_compstate,&b->opkt[0],b->oplen,writepay); (*b->w_comp->flush)(b->w_compstate,writepay); 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); encalg_w(b->w_enc,b->w_encstate,wr_fp,fw,&b->rawopkt[0],pktlen); wr_fp(fw,&b->rawopkt[pktlen],b->w_mac->maclen); if (b->opkts_to_rekey-- <= 0) b->want_rekey = 1; if ((b->obytes_to_rekey-=pktlen) <= 0) b->want_rekey = 1; } static LAYERDESC layer_base = { &base_init, &base_r, &base_w }; static void d_i_d_r(LAYER *l, void *arg __attribute__((__unused__))) { BPP *b; b = l->b; while (1) { lower_read(l); if (b->iplen < 1) { fprintf(stderr,"%s: ignoring empty protocol packet\n",__progname); continue; } switch (b->ipkt[0]) { case SSH_MSG_DISCONNECT: { unsigned int reason; STR desc; STR lang; parse_packet(b,pp_fail, PP_SKIP(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"); exit(0); } break; case SSH_MSG_IGNORE: break; case SSH_MSG_DEBUG: { int disp; STR msg; STR lang; parse_packet(b,pp_fail, PP_SKIP(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: return; break; } } } static LAYERDESC layer_d_i_d = { 0, &d_i_d_r, 0 }; int main(int, char **); int main(int ac, char **av) { handleargs(ac,av); openconn(); sendversion(); getversion(); checkversion(); bpp.w_enc = encalg_vfind_c("none"); bpp.w_encstate = encalg_init(bpp.w_enc,"","",'w'); bpp.r_enc = bpp.w_enc; bpp.r_encstate = encalg_init(bpp.r_enc,"","",'r'); bpp.w_comp = compalg_vfind_c("none"); bpp.w_compstate = compalg_init(bpp.w_comp,'w'); bpp.r_comp = bpp.w_comp; bpp.r_compstate = compalg_init(bpp.r_comp,'r'); bpp.w_mac = macalg_vfind_c("none"); bpp.w_macstate = macalg_init(bpp.w_mac,""); bpp.r_mac = bpp.w_mac; bpp.r_macstate = macalg_init(bpp.r_mac,""); bpp.w_seq = 0U - 1; bpp.r_seq = 0U - 1; bpp.toplayer = 0; bpp.ipkts_to_rekey = 1<<30; bpp.opkts_to_rekey = 1<<30; bpp.ibytes_to_rekey = 1<<30; bpp.obytes_to_rekey = 1<<30; push_layer(&bpp,&layer_base); push_layer(&bpp,&layer_d_i_d); push_layer(&bpp,&layer_transport); push_layer(&bpp,&layer_userauth_conn); toplayer_r(&bpp); fprintf(stderr,"%s: unrecognized packet type %d\n",__progname,bpp.ipkt[0]); exit(1); }