#ifdef _WIN32 #include #endif #include #include #include #include #include #ifndef _WIN32 #include #include #include #include #include #include #include #endif #include #ifdef _WIN32 #define close closesocket #define ioctl ioctlsocket #define WSAerr WSAGetLastError() #endif #define ME "tcprelay: " #define strncpyz(d,s,l) *(strncpy(d,s,l)+l)=0 #define lwrcase(a) ((a>64 && a<91) ? a+32:a) #define uprcase(a) ((a>96 && a<123) ? a-32:a) #define BadPtr(ptr) (!ptr || !*ptr) #define H (2*sizeof(void *)) #define Tail(ptr) (*((void **)ptr-2)) // only on head #define Prev(ptr) (*((void **)ptr-2)) #define Next(ptr) (*((void **)ptr-1)) #define PrevA(ptr) ptr=*((void **)ptr-2) #define NextA(ptr) ptr=*((void **)ptr-1) #define DBUFSZ 2032 #define DBUF_POOL 8192 #define PS 8188 #define QS 65536 struct dbufbuf_s { struct dbufbuf_s *link; char buf[DBUFSZ]; }; struct dbuf_s { int len; int off; struct dbufbuf_s *head; struct dbufbuf_s *tail; }; struct conn_s { struct dbuf_s q1,q2; int s1,s2; char st; }; typedef struct dbuf_s dbuf_t; typedef struct conn_s conn_t; #define dbuf_len(a) ((a)->len) static struct dbufbuf_s *freelist=0; static int dbuf_blocks=0; static struct sockaddr_in connaddr; static struct sockaddr_in socks_addr; static char *inclist=0; static char *exclist=0; static int maxfds=50; static struct dbufbuf_s *dbuf_alloc(void) { struct dbufbuf_s *db; if(freelist) { db=freelist; freelist=freelist->link; db->link=0; return db; } if(dbuf_blocks*DBUFSZ>DBUF_POOL*1024) return 0; if(!(db=malloc(sizeof(struct dbufbuf_s)))) return 0; db->link=0; dbuf_blocks++; return db; } static int dbuf_put(dbuf_t *dbuf,char *pt,int sz) { struct dbufbuf_s *db; int off,chunk; if(!sz) return 0; off=(dbuf->off+dbuf->len)%DBUFSZ; chunk=DBUFSZ-off; if(!dbuf->len) { if(!(db=dbuf_alloc())) return 0; dbuf->head=db; dbuf->tail=db; } else { if(off) db=dbuf->tail; else { if(!(db=dbuf_alloc())) return 0; dbuf->tail->link=db; dbuf->tail=db; } } dbuf->len+=sz; while(sz) { if(!db) { if(!(db=dbuf_alloc())) return 0; dbuf->tail->link=db; dbuf->tail=db; } if(chunk>sz) chunk=sz; memcpy(db->buf+off,pt,chunk); sz-=chunk; pt+=chunk; db=0; off=0; chunk=DBUFSZ; } return 1; } static int dbuf_get(dbuf_t *dbuf,char *pt,int sz) { struct dbufbuf_s *db; int off,chunk,len,size; if(!dbuf->len) return 0; size=0; len=dbuf->len; db=dbuf->head; off=dbuf->off; chunk=DBUFSZ-off; while(sz && len) { if(chunk>len) chunk=len; if(chunk>sz) chunk=sz; memcpy(pt,db->buf+off,chunk); len-=chunk; sz-=chunk; pt+=chunk; size+=chunk; off=0; db=db->link; chunk=DBUFSZ; } return size; } static void dbuf_del(dbuf_t *dbuf,int sz) { struct dbufbuf_s *db; int off,chunk; if(!dbuf->len) return; db=dbuf->head; off=dbuf->off; chunk=DBUFSZ-off; while(sz && dbuf->len) { if(chunk>dbuf->len) chunk=dbuf->len; if(chunk>sz) chunk=sz; dbuf->len-=chunk; dbuf->off+=chunk; sz-=chunk; if(dbuf->off==DBUFSZ || !dbuf->len) { dbuf->head=db->link; db->link=freelist; freelist=db; db=dbuf->head; if(!dbuf->len) dbuf->tail=0; dbuf->off=0; } chunk=DBUFSZ; } } static void dbuf_clear(dbuf_t *dbuf) { struct dbufbuf_s *db; if(!dbuf->len) return; dbuf->len=0; dbuf->off=0; dbuf->tail=0; db=dbuf->head; while(db) { dbuf->head=db->link; db->link=freelist; freelist=db; db=dbuf->head; } } static inline void *_Append(void **head,size_t size) { void *temp; if(!(temp=malloc(size+H))) return 0; temp=(char *)temp+H; memset(temp,0,size); Next(temp)=0; if(*head) { Prev(temp)=Tail(*head); Next(Tail(*head))=temp; Tail(*head)=temp; return temp; } Tail(temp)=temp; *head=temp; return temp; } static inline void *_Delete(void **head,void *ptr) { void *ret; if(ptr==*head) { if(Next(ptr)) { Tail(Next(ptr))=Tail(ptr); ret=Next(ptr); *head=ret; free((char *)ptr-H); return ret; } free((char *)ptr-H); *head=0; return 0; } if(ptr==Tail(*head)) Tail(*head)=Prev(ptr); if(Prev(ptr)) Next(Prev(ptr))=Next(ptr); if(Next(ptr)) Prev(Next(ptr))=Prev(ptr); ret=Next(ptr); free((char *)ptr-H); return ret; } static inline void _Clear(void **head) { void *ptr,*tmp; ptr=*head; while(ptr) { tmp=ptr; ptr=Next(ptr); free((char *)tmp-H); } *head=0; } static inline int _Count(void *ptr) { int cnt=1; if(!ptr) return 0; while((NextA(ptr))) cnt++; return cnt; } #define Append(base,size) _Append((void **)&base,size) #define Delete(base,ptr) _Delete((void **)&base,ptr) #define Clear(base) _Clear((void **)&base) #define Count(ptr) _Count(ptr) static char isstrdec(char *s,size_t l) { while(l--) { if(*s<48 || *s>57) return 0; } return 1; } static char isstrhex(char *s,size_t l) { while(l--) { if(*s<48 || (*s>57 && *s<65) || (*s>70 && *s<97) || *s>102) return 0; } return 1; } static char bad_inaddr(void *in) { int i; for(i=0;i<4;i++) { if(*((u_char *)in+i)!=255) return 0; } return 1; } static char any_inaddr(void *in) { int i; for(i=0;i<4;i++) { if(*((u_char *)in+i)!=0) return 0; } return 1; } static char lo_inaddr(void *in) { if(*(u_char *)in==127) return 1; return 0; } static char *inetntoa(void *in) { static char buf[16]; u_char *s=(u_char *)in; int a,b,c,d; a=(int)*s++; b=(int)*s++; c=(int)*s++; d=(int)*s++; sprintf(buf,"%d.%d.%d.%d",a,b,c,d); return buf; } static char inetaton(char *s,void *d) { char *p; int i=0; memset(d,-1,4); p=s; while(*p && i<4) { if(!(p=strchr(s,'.'))) { if(*s=='*') { for(;i<4;i++) *((u_char *)d+i)=0; return 0; } p=strchr(s,0); } if((p-s)>3 || !isstrdec(s,p-s)) { memset(d,-1,4); return 1; } *((u_char *)d+i)=(u_char)atoi(s); i++; s=p+1; } if(*p || i!=4) { memset(d,-1,4); return 1; } return 0; } static char *cmpstr(char *a,char *b) { char *c,*d,*t; t=b; while(1) { c=a; d=t; while(*c && lwrcase(*c)==lwrcase(*d)) { c++; d++; } if(!*c) return(d); t++; if(!*t) return(0); } } static char iswm(char *s,char *w) { char st[128]; char *a,*p; if(!s || !w || !*s || !*w) return 0; a=s; while(1) { while(*w=='?') { w++; a++; if(!*a) { if(!*w) return 1; return 0; } } while(*w=='*') { w++; if(!*w) return 1; p=st; while(*w && *w!='*' && *w!='?') *p++=*w++; *p=0; while(1) { a=cmpstr(st,a); if(!a) return 0; if(*w) break; if(!*a) return 1; } } while(*w && *w!='*' && *w!='?' && lwrcase(*a)==lwrcase(*w)) { a++; w++; } if(!*w) { if(!*a) return 1; return 0; } if(*w!='*' && *w!='?') return 0; } return 0; } static char ip_match(char *s,char *m) { char mask[64],str_s[24],str_m[24]; char *p,*p1; u_long bf; u_long ip_s,ip_m; int t; if(strlen(m)>40) return 0; strcpy(mask,m); if(!(p=strchr(mask,'/'))) return iswm(s,mask); *p=0; p++; bf=atoi(p); if(bf>32) return 0; if(strchr(mask,'*')) return iswm(s,mask); inetaton(mask,&ip_m); inetaton(s,&ip_s); if(ip_m==0xffffffff) return 0; bf=htonl(0xffffffff<<(32-bf)); ip_m&=bf; if((ip_s&bf)==ip_m) return 1; return 0; } static int mk_conn(struct sockaddr_in addr) { int opt=1; int win=65536; int s; if((s=socket(AF_INET,SOCK_STREAM,0))<0) return -1; if(setsockopt(s,IPPROTO_TCP,TCP_NODELAY,(void *)&opt,sizeof(int))<0) { close(s); printf(ME "setsockopt() failed\n"); return -1; } #if defined(__CYGWIN__) || defined(_WIN32) if(setsockopt(s,SOL_SOCKET,SO_RCVBUF,(void *)&win,sizeof(int))<0) { close(s); fprintf(stderr,ME "setsockopt(SO_RCVBUF) failed\n"); exit(1); } if(setsockopt(s,SOL_SOCKET,SO_SNDBUF,(void *)&win,sizeof(int))<0) { close(s); fprintf(stderr,ME "setsockopt(SO_SNDBUF) failed\n"); exit(1); } #endif if(ioctl(s,FIONBIO,&opt)<0) { close(s); printf(ME "ioctl() failed\n"); return -1; } if(connect(s,(struct sockaddr *)&addr,sizeof(struct sockaddr_in))<0) { #ifdef _WIN32 if(WSAerr!=WSAEWOULDBLOCK) #else if(errno!=EINPROGRESS) #endif { close(s); printf(ME "connect() failed\n"); return -1; } } return s; } static int mk_listen(int port) { struct sockaddr_in addr; int opt=1; int win=65536; int s; memset(&addr.sin_addr,0,4); addr.sin_family=AF_INET; addr.sin_port=htons(port); if((s=socket(AF_INET,SOCK_STREAM,0))<0) { printf(ME "socket() failed\n"); exit(1); } if(setsockopt(s,SOL_SOCKET,SO_REUSEADDR,(void *)&opt,sizeof(int))<0) { close(s); printf(ME "setsockopt() failed\n"); exit(1); } #if defined(__CYGWIN__) || defined(_WIN32) if(setsockopt(s,SOL_SOCKET,SO_RCVBUF,(void *)&win,sizeof(int))<0) { close(s); fprintf(stderr,ME "setsockopt(SO_RCVBUF) failed\n"); exit(1); } if(setsockopt(s,SOL_SOCKET,SO_SNDBUF,(void *)&win,sizeof(int))<0) { close(s); fprintf(stderr,ME "setsockopt(SO_SNDBUF) failed\n"); exit(1); } #endif if(ioctl(s,FIONBIO,&opt)<0) { close(s); printf(ME "ioctl() failed\n"); exit(1); } if(bind(s,(struct sockaddr *)&addr,sizeof(addr))<0) { close(s); printf(ME "bind() failed\n"); exit(1); } if(listen(s,5)<0) { close(s); printf(ME "listen() failed\n"); exit(1); } printf("Listening on port %d\n",port); return s; } static int accept_conn(int s) { struct sockaddr_in addr; char *ip,*p; int opt=1; int s2; int l; l=sizeof(struct sockaddr_in); if((s2=accept(s,(struct sockaddr *)&addr,&l))<0) return -1; if(ioctl(s2,FIONBIO,&opt)<0) { close(s2); return -1; } ip=inetntoa(&addr.sin_addr); if(inclist) { for(p=inclist;p;NextA(p)) { if(ip_match(ip,p)) break; } if(!p) { close(s2); printf("Unknown connection: %s\n",ip); return -1; } } for(p=exclist;p;NextA(p)) { if(ip_match(ip,p)) break; } if(p) { close(s2); printf("Unknown connection: %s\n",ip); return -1; } printf("Connection accepted: %s\n",ip); return s2; } static void do_relay(int sl) { static char buf[8192]; static fd_set rds,wrs,exs; struct timeval tw; conn_t *cs=0; conn_t *c; int r,l; int i; char dw1,dw2; while(1) { FD_ZERO(&rds); FD_ZERO(&wrs); FD_ZERO(&exs); FD_SET(sl,&rds); c=cs; while(c) { if(c->st==1) { FD_SET(c->s2,&wrs); #ifdef _WIN32 FD_SET(c->s2,&exs); #endif NextA(c); continue; } if(c->st==2) { if(dbuf_len(&c->q1)s2,&rds); if(dbuf_len(&c->q2)s1,&rds); if(dbuf_len(&c->q1)) FD_SET(c->s1,&wrs); if(dbuf_len(&c->q2)) FD_SET(c->s2,&wrs); NextA(c); continue; } if(c->st==3) { if(c->s1>=0) { if(dbuf_len(&c->q1)) FD_SET(c->s1,&wrs); else { close(c->s1); dbuf_clear(&c->q1); c->st=0; } } if(c->s2>=0) { if(dbuf_len(&c->q2)) FD_SET(c->s2,&wrs); else { close(c->s2); dbuf_clear(&c->q2); c->st=0; } } } if(!c->st) c=Delete(cs,c); else NextA(c); } tw.tv_sec=2; tw.tv_usec=0; #ifdef _WIN32 r=select(FD_SETSIZE,&rds,&wrs,&exs,&tw); #else r=select(FD_SETSIZE,&rds,&wrs,0,&tw); #endif if(r<0) { close(sl); printf(ME "select() failed\n"); exit(1); } if(!r) continue; for(c=cs;c;NextA(c)) { if(c->st==3) { if(c->s1>=0 && FD_ISSET(c->s1,&wrs)) { l=dbuf_get(&c->q1,buf,PS); r=send(c->s1,buf,l,0); #ifdef _WIN32 if(r<0 && WSAerr!=WSAENOBUFS && WSAerr!=WSAEWOULDBLOCK) #else if(r<0 && errno!=ENOBUFS && errno!=EAGAIN) #endif { close(c->s1); dbuf_clear(&c->q1); c->st=0; } if(r>0) dbuf_del(&c->q1,r); } if(c->s2>=0 && FD_ISSET(c->s2,&wrs)) { l=dbuf_get(&c->q2,buf,PS); r=send(c->s2,buf,l,0); #ifdef _WIN32 if(r<0 && WSAerr!=WSAENOBUFS && WSAerr!=WSAEWOULDBLOCK) #else if(r<0 && errno!=ENOBUFS && errno!=EAGAIN) #endif { close(c->s2); dbuf_clear(&c->q2); c->st=0; } if(r>0) dbuf_del(&c->q2,r); } } if(c->st==2) { dw1=0,dw2=0; if(FD_ISSET(c->s1,&rds)) { r=recv(c->s1,buf,PS,0); if(r<0) { close(c->s1); close(c->s2); dbuf_clear(&c->q1); dbuf_clear(&c->q2); c->st=0; continue; } if(!r) { close(c->s1); dbuf_clear(&c->q1); c->s1=-1; c->st=3; } if(r>0) { if(!dbuf_len(&c->q2)) dw2=1; dbuf_put(&c->q2,buf,r); } } if(FD_ISSET(c->s2,&rds)) { r=recv(c->s2,buf,PS,0); if(r<0) { close(c->s1); close(c->s2); dbuf_clear(&c->q1); dbuf_clear(&c->q2); c->st=0; continue; } if(!r) { close(c->s2); dbuf_clear(&c->q2); c->s2=-1; c->st=3; } if(r>0) { if(!dbuf_len(&c->q1)) dw1=1; dbuf_put(&c->q1,buf,r); } } if(c->st!=2) continue; if(dw1 || FD_ISSET(c->s1,&wrs)) { l=dbuf_get(&c->q1,buf,PS); r=send(c->s1,buf,l,0); #ifdef _WIN32 if(r<0 && WSAerr!=WSAENOBUFS && WSAerr!=WSAEWOULDBLOCK) #else if(r<0 && errno!=ENOBUFS && errno!=EAGAIN) #endif { close(c->s1); close(c->s2); dbuf_clear(&c->q1); dbuf_clear(&c->q2); c->st=0; continue; } if(r>0) dbuf_del(&c->q1,r); } if(dw2 || FD_ISSET(c->s2,&wrs)) { l=dbuf_get(&c->q2,buf,PS); r=send(c->s2,buf,l,0); #ifdef _WIN32 if(r<0 && WSAerr!=WSAENOBUFS && WSAerr!=WSAEWOULDBLOCK) #else if(r<0 && errno!=ENOBUFS && errno!=EAGAIN) #endif { close(c->s1); close(c->s2); dbuf_clear(&c->q1); dbuf_clear(&c->q2); c->st=0; continue; } if(r>0) dbuf_del(&c->q2,r); } } if(c->st==1) { #ifdef _WIN32 if(FD_ISSET(c->s2,&exs)) { close(c->s1); close(c->s2); dbuf_clear(&c->q1); dbuf_clear(&c->q2); c->st=0; continue; } #endif if(FD_ISSET(c->s2,&wrs)) { #ifdef SO_ERROR int err; l=sizeof(int); if(!getsockopt(c->s2,SOL_SOCKET,SO_ERROR,(char *)&err,&l)) { if(err) { close(c->s1); close(c->s2); dbuf_clear(&c->q1); dbuf_clear(&c->q2); c->st=0; continue; } c->st=2; } #else c->st=2; #endif } } } if(FD_ISSET(sl,&rds)) { int s=accept_conn(sl); if(s>=0) { if(Count(cs)>maxfds) close(s); else { c=Append(cs,sizeof(conn_t)); c->s1=s; if((s=mk_conn(connaddr))<0) { printf("mk_conn() failed\n"); close(c->s1); } else { c->s2=s; c->st=1; } } } } } } static void usage(void) { printf("Usage: tcprelay [-I ] [-X ] \n"); exit(0); } int main(int argc,char *argv[]) { static char socks_host[80]; #ifdef _WIN32 WSADATA WSAData; #endif int port; int i; for(i=1;i=argc || BadPtr(argv[i])) { printf(ME "option '-n' needs argument\n"); exit(1); } if(!(maxfds=atoi(argv[i]))) { printf(ME "invalid argument for '-n' option\n"); exit(1); } if(maxfds>500) { printf(ME "invalid argument for '-n' option\n"); exit(1); } continue; } if(!strcmp(argv[i],"-I")) { char *str; i++; if(i>=argc || BadPtr(argv[i])) { printf(ME "option '-I' needs argument\n"); exit(1); } str=Append(inclist,strlen(argv[i])+1); strcpy(str,argv[i]); continue; } if(!strcmp(argv[i],"-X")) { char *str; i++; if(i>=argc || BadPtr(argv[i])) { printf(ME "option '-X' needs argument\n"); exit(1); } str=Append(exclist,strlen(argv[i])+1); strcpy(str,argv[i]); continue; } printf(ME "Unknown option\n"); exit(1); } if(argc<=i+2) usage(); if(BadPtr(argv[i]) || BadPtr(argv[i+1]) || BadPtr(argv[i+2])) { printf(ME "Invalid arguments\n"); exit(1); } if(!(port=atoi(argv[i+2]))) { printf(ME "Invalid port specified\n"); exit(1); } connaddr.sin_family=AF_INET; connaddr.sin_port=htons(port); if(!(port=atoi(argv[i]))) { printf(ME "Invalid port specified\n"); exit(1); } if(inetaton(argv[i+1],&connaddr.sin_addr)) { struct hostent *he; if(!(he=gethostbyname(argv[i+1]))) { printf(ME "Unable to resolve %s\n",argv[i+1]); exit(1); } if(he->h_length!=4) { printf(ME "Error resolving %s\n",argv[i+1]); exit(1); } memcpy(&connaddr.sin_addr,he->h_addr,he->h_length); } #ifdef _WIN32 if(WSAStartup(MAKEWORD(2,0),&WSAData)) { printf(ME "WSAStartup() failed\n"); exit(1); } #else signal(SIGPIPE,SIG_IGN); #endif do_relay(mk_listen(port)); return 0; }