/* execnet.cpp */ /* Synchronet command shell/module TCP/IP Network functions */ /* $Id$ */ /**************************************************************************** * @format.tab-size 4 (Plain Text/Source Code File Header) * * @format.use-tabs true (see http://www.synchro.net/ptsc_hdr.html) * * * * Copyright 2007 Rob Swindell - http://www.synchro.net/copyright.html * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License * * as published by the Free Software Foundation; either version 2 * * of the License, or (at your option) any later version. * * See the GNU General Public License for more details: gpl.txt or * * http://www.fsf.org/copyleft/gpl.html * * * * Anonymous FTP access to the most recent released source is available at * * ftp://vert.synchro.net, ftp://cvs.synchro.net and ftp://ftp.synchro.net * * * * Anonymous CVS access to the development source and modification history * * is available at cvs.synchro.net:/cvsroot/sbbs, example: * * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs login * * (just hit return, no password is necessary) * * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs checkout src * * * * For Synchronet coding style and modification guidelines, see * * http://www.synchro.net/source.html * * * * You are encouraged to submit any modifications (preferably in Unix diff * * format) via e-mail to mods@synchro.net * * * * Note: If this box doesn't appear square, then you need to fix your tabs. * ****************************************************************************/ #include "sbbs.h" #include "cmdshell.h" #define TIMEOUT_SOCK_READLINE 30 /* seconds */ #define TIMEOUT_SOCK_LISTEN 30 /* seconds */ #define TIMEOUT_FTP_RESPONSE 300 /* seconds */ int sbbs_t::exec_net(csi_t* csi) { char str[512],rsp[512],buf[1025],ch,*p,**pp,**pp1,**pp2; ushort w; uint i; BOOL rd; int32_t *lp,*lp1,*lp2; time_t start; switch(*(csi->ip++)) { /* sub-op-code stored as next byte */ case CS_SOCKET_OPEN: lp=getintvar(csi,*(int32_t *)csi->ip); csi->ip+=4; csi->logic=LOGIC_FALSE; csi->socket_error=0; if(csi->sockets>=MAX_SOCKETS) return(0); if(lp!=NULL) { SOCKET sock=open_socket(SOCK_STREAM, NULL); if(sock!=INVALID_SOCKET) { SOCKADDR_IN addr; memset(&addr,0,sizeof(addr)); addr.sin_addr.s_addr = htonl(startup->telnet_interface); addr.sin_family = AF_INET; if((i=bind(sock, (struct sockaddr *) &addr, sizeof (addr)))!=0) { csi->socket_error=ERROR_VALUE; close_socket(sock); return(0); } *lp=sock; for(i=0;i<csi->sockets;i++) if(!csi->socket[i]) break; csi->socket[i]=*lp; if(i==csi->sockets) csi->sockets++; csi->logic=LOGIC_TRUE; } } return(0); case CS_SOCKET_CLOSE: lp=getintvar(csi,*(int32_t *)csi->ip); csi->ip+=4; csi->logic=LOGIC_FALSE; csi->socket_error=0; if(lp && *lp) { csi->logic=close_socket((SOCKET)*lp); csi->socket_error=ERROR_VALUE; for(i=0;i<csi->sockets;i++) if(csi->socket[i]==(SOCKET)*lp) csi->socket[i]=0; *lp=0; } return(0); case CS_SOCKET_CHECK: lp=getintvar(csi,*(int32_t *)csi->ip); csi->ip+=4; csi->logic=LOGIC_FALSE; csi->socket_error=0; if(lp==NULL || *lp==INVALID_SOCKET) return(0); if(socket_check(*lp,NULL,NULL,0)==TRUE) csi->logic=LOGIC_TRUE; else csi->socket_error=ERROR_VALUE; return(0); case CS_SOCKET_CONNECT: lp=getintvar(csi,*(int32_t *)csi->ip); /* socket */ csi->ip+=4; pp=getstrvar(csi,*(int32_t *)csi->ip); /* address */ csi->ip+=4; w=*(ushort *)csi->ip; /* port */ csi->ip+=2; csi->logic=LOGIC_FALSE; csi->socket_error=0; if(!lp || !*lp || !pp || !*pp || !w) return(0); ulong ip_addr; if((ip_addr=resolve_ip(*pp))==INADDR_NONE) return(0); SOCKADDR_IN addr; memset(&addr,0,sizeof(addr)); addr.sin_addr.s_addr = ip_addr; addr.sin_family = AF_INET; addr.sin_port = htons(w); if((i=connect(*lp, (struct sockaddr *)&addr, sizeof(addr)))!=0) { csi->socket_error=ERROR_VALUE; return(0); } csi->logic=LOGIC_TRUE; return(0); case CS_SOCKET_ACCEPT: lp1=getintvar(csi,*(int32_t *)csi->ip); /* socket */ csi->ip+=4; csi->socket_error=0; /* TODO */ return(0); case CS_SOCKET_NREAD: lp1=getintvar(csi,*(int32_t *)csi->ip); /* socket */ csi->ip+=4; lp2=getintvar(csi,*(int32_t *)csi->ip); /* var */ csi->ip+=4; csi->logic=LOGIC_FALSE; csi->socket_error=0; if(!lp1 || !lp2) return(0); if(ioctlsocket(*lp1, FIONREAD, (ulong*)lp2)==0) csi->logic=LOGIC_TRUE; else csi->socket_error=ERROR_VALUE; return(0); case CS_SOCKET_PEEK: case CS_SOCKET_READ: lp=getintvar(csi,*(int32_t *)csi->ip); /* socket */ csi->ip+=4; pp=getstrvar(csi,*(int32_t *)csi->ip); /* buffer */ csi->ip+=4; w=*(ushort *)csi->ip; /* length */ csi->ip+=2; csi->logic=LOGIC_FALSE; csi->socket_error=0; if(!lp || !pp) return(0); if(w<1 || w>sizeof(buf)-1) w=sizeof(buf)-1; if((i=recv(*lp,buf,w ,*(csi->ip-13)==CS_SOCKET_PEEK ? MSG_PEEK : 0))>0) { csi->logic=LOGIC_TRUE; buf[i]=0; if(csi->etx) { p=strchr(buf,csi->etx); if(p) *p=0; } *pp=copystrvar(csi,*pp,buf); } else csi->socket_error=ERROR_VALUE; return(0); case CS_SOCKET_READLINE: lp=getintvar(csi,*(int32_t *)csi->ip); /* socket */ csi->ip+=4; pp=getstrvar(csi,*(int32_t *)csi->ip); /* buffer */ csi->ip+=4; w=*(ushort *)csi->ip; /* length */ csi->ip+=2; csi->logic=LOGIC_FALSE; csi->socket_error=0; if(!lp || !pp) return(0); if(w<1 || w>sizeof(buf)-1) w=sizeof(buf)-1; start=time(NULL); for(i=0;i<w;) { if(!online) return(1); if(!socket_check(*lp,&rd,NULL,1000)) return(0); if(!rd) { if(time(NULL)-start>TIMEOUT_SOCK_READLINE) { lprintf(LOG_WARNING,"!socket_readline: timeout (%d) exceeded" ,TIMEOUT_SOCK_READLINE); return(0); } continue; } if(recv(*lp, &ch, 1, 0)!=1) { csi->socket_error=ERROR_VALUE; return(0); } if(ch=='\n' && i>=1) break; buf[i++]=ch; } if(i>0 && buf[i-1]=='\r') buf[i-1]=0; else buf[i]=0; if(csi->etx) { p=strchr(buf,csi->etx); if(p) *p=0; } *pp=copystrvar(csi,*pp,buf); csi->logic=LOGIC_TRUE; return(0); case CS_SOCKET_WRITE: lp=getintvar(csi,*(int32_t *)csi->ip); /* socket */ csi->ip+=4; pp=getstrvar(csi,*(int32_t *)csi->ip); /* buffer */ csi->ip+=4; csi->logic=LOGIC_FALSE; csi->socket_error=0; if(!lp || !pp || !(*pp)) return(0); if(sendsocket(*lp,*pp,strlen(*pp))>0) csi->logic=LOGIC_TRUE; else csi->socket_error=ERROR_VALUE; return(0); /* FTP Functions */ case CS_FTP_LOGIN: lp=getintvar(csi,*(int32_t *)csi->ip); /* socket */ csi->ip+=4; pp1=getstrvar(csi,*(int32_t *)csi->ip); /* username */ csi->ip+=4; pp2=getstrvar(csi,*(int32_t *)csi->ip); /* password */ csi->ip+=4; csi->logic=LOGIC_FALSE; csi->socket_error=0; if(!lp || !pp1 || !pp2) return(0); if(!ftp_cmd(csi,*lp,NULL,rsp)) return(0); if(atoi(rsp)!=220) return(0); sprintf(str,"USER %s",*pp1); if(!ftp_cmd(csi,*lp,str,rsp)) return(0); if(atoi(rsp)==331) { /* Password needed */ sprintf(str,"PASS %s",*pp2); if(!ftp_cmd(csi,*lp,str,rsp)) return(0); } if(atoi(rsp)==230) /* Login successful */ csi->logic=LOGIC_TRUE; return(0); case CS_FTP_LOGOUT: lp=getintvar(csi,*(int32_t *)csi->ip); /* socket */ csi->ip+=4; csi->logic=LOGIC_FALSE; csi->socket_error=0; if(!lp) return(0); if(!ftp_cmd(csi,*lp,"QUIT",rsp)) return(0); if(atoi(rsp)==221) /* Logout successful */ csi->logic=LOGIC_TRUE; return(0); case CS_FTP_PWD: lp=getintvar(csi,*(int32_t *)csi->ip); /* socket */ csi->ip+=4; csi->logic=LOGIC_FALSE; csi->socket_error=0; if(!lp) return(0); if(!ftp_cmd(csi,*lp,"PWD",rsp)) return(0); if(atoi(rsp)==257) /* pathname */ csi->logic=LOGIC_TRUE; return(0); case CS_FTP_CWD: lp=getintvar(csi,*(int32_t *)csi->ip); /* socket */ csi->ip+=4; pp=getstrvar(csi,*(int32_t *)csi->ip); /* path */ csi->ip+=4; csi->logic=LOGIC_FALSE; csi->socket_error=0; if(!lp || !pp) return(0); sprintf(str,"CWD %s",*pp); if(!ftp_cmd(csi,*lp,str,rsp)) return(0); if(atoi(rsp)==250) csi->logic=LOGIC_TRUE; return(0); case CS_FTP_DIR: lp=getintvar(csi,*(int32_t *)csi->ip); /* socket */ csi->ip+=4; pp=getstrvar(csi,*(int32_t *)csi->ip); /* path */ csi->ip+=4; csi->logic=LOGIC_FALSE; csi->socket_error=0; if(!lp || !pp) return(0); if(ftp_get(csi,*lp,*pp,NULL /* unused */, true /* DIR */)==true) csi->logic=LOGIC_TRUE; return(0); case CS_FTP_DELETE: lp=getintvar(csi,*(int32_t *)csi->ip); /* socket */ csi->ip+=4; pp=getstrvar(csi,*(int32_t *)csi->ip); /* path */ csi->ip+=4; csi->logic=LOGIC_FALSE; csi->socket_error=0; if(!lp || !pp) return(0); sprintf(str,"DELE %s",*pp); if(!ftp_cmd(csi,*lp,str,rsp)) return(0); if(atoi(rsp)==250) csi->logic=LOGIC_TRUE; return(0); case CS_FTP_GET: lp=getintvar(csi,*(int32_t *)csi->ip); /* socket */ csi->ip+=4; pp1=getstrvar(csi,*(int32_t *)csi->ip); /* src path */ csi->ip+=4; pp2=getstrvar(csi,*(int32_t *)csi->ip); /* dest path */ csi->ip+=4; csi->logic=LOGIC_FALSE; csi->socket_error=0; if(!lp || !pp1 || !pp2) return(0); if(ftp_get(csi,*lp,*pp1,*pp2)==true) csi->logic=LOGIC_TRUE; return(0); case CS_FTP_PUT: lp=getintvar(csi,*(int32_t *)csi->ip); /* socket */ csi->ip+=4; pp1=getstrvar(csi,*(int32_t *)csi->ip); /* src path */ csi->ip+=4; pp2=getstrvar(csi,*(int32_t *)csi->ip); /* dest path */ csi->ip+=4; csi->logic=LOGIC_FALSE; csi->socket_error=0; if(!lp || !pp1 || !pp2) return(0); if(ftp_put(csi,*lp,*pp1,*pp2)==true) csi->logic=LOGIC_TRUE; return(0); default: errormsg(WHERE,ERR_CHK,"net sub-instruction",*(csi->ip-1)); return(0); } } /* FTP Command/Response function */ bool sbbs_t::ftp_cmd(csi_t* csi, SOCKET sock, const char* cmdsrc, char* rsp) { char cmd[512]; int len; BOOL data_avail; time_t start; if(cmdsrc!=NULL) { sprintf(cmd,"%s\r\n",cmdsrc); if(csi->ftp_mode&CS_FTP_ECHO_CMD) bputs(cmd); len=strlen(cmd); if(sendsocket(sock,cmd,len)!=len) { csi->socket_error=ERROR_VALUE; return(FALSE); } } if(rsp!=NULL) { int rd; char ch; while(1) { rd=0; start=time(NULL); while(rd<500) { if(!online) return(FALSE); if(!socket_check(sock,&data_avail,NULL,1000)) return(FALSE); if(!data_avail) { if(time(NULL)-start>TIMEOUT_FTP_RESPONSE) { lprintf(LOG_WARNING,"!ftp_cmd: TIMEOUT_FTP_RESPONSE (%d) exceeded" ,TIMEOUT_FTP_RESPONSE); return(FALSE); } continue; } if(recv(sock, &ch, 1, 0)!=1) { csi->socket_error=ERROR_VALUE; return(FALSE); } if(ch=='\n' && rd>=1) break; rsp[rd++]=ch; } rsp[rd-1]=0; if(csi->ftp_mode&CS_FTP_ECHO_RSP) bprintf("%s\r\n",rsp); if(rsp[0]!=' ' && rsp[3]!='-') break; } } return(TRUE); } SOCKET sbbs_t::ftp_data_sock(csi_t* csi, SOCKET ctrl_sock, SOCKADDR_IN* addr) { char cmd[512]; char rsp[512]; char* p; socklen_t addr_len; SOCKET data_sock; int ip_b[4]; int port_b[2]; union { DWORD dw; BYTE b[sizeof(DWORD)]; } ip_addr; union { WORD w; BYTE b[sizeof(WORD)]; } port; if(csi->ftp_mode&CS_FTP_ASCII) strcpy(cmd,"TYPE A"); else /* BINARY */ strcpy(cmd,"TYPE I"); if(!ftp_cmd(csi,ctrl_sock,cmd,rsp) || atoi(rsp)!=200) { return(INVALID_SOCKET); } if((data_sock=open_socket(SOCK_STREAM, "ftp"))==INVALID_SOCKET) { csi->socket_error=ERROR_VALUE; return(INVALID_SOCKET); } memset(addr,0,sizeof(SOCKADDR_IN)); addr->sin_addr.s_addr = htonl(startup->telnet_interface); addr->sin_family = AF_INET; if(bind(data_sock, (struct sockaddr *)addr,sizeof(SOCKADDR_IN))!= 0) { csi->socket_error=ERROR_VALUE; close_socket(data_sock); return(INVALID_SOCKET); } if(csi->ftp_mode&CS_FTP_PASV) { if(!ftp_cmd(csi,ctrl_sock,"PASV",rsp) || atoi(rsp)!=227 /* PASV response */) { bprintf("ftp: failure, line %d",__LINE__); close_socket(data_sock); return(INVALID_SOCKET); } p=strchr(rsp,'('); if(p==NULL) { bprintf("ftp: failure, line %d",__LINE__); close_socket(data_sock); return(INVALID_SOCKET); } p++; if(sscanf(p,"%u,%u,%u,%u,%u,%u" ,&ip_b[0],&ip_b[1],&ip_b[2],&ip_b[3] ,&port_b[0],&port_b[1])!=6) { bprintf("ftp: failure, line %d",__LINE__); close_socket(data_sock); return(INVALID_SOCKET); } ip_addr.b[0]=ip_b[0]; ip_addr.b[1]=ip_b[1]; ip_addr.b[2]=ip_b[2]; ip_addr.b[3]=ip_b[3]; port.b[0]=port_b[0]; port.b[1]=port_b[1]; addr->sin_addr.s_addr=ip_addr.dw; addr->sin_port=port.w; } else { /* Normal (Active) FTP */ addr_len=sizeof(SOCKADDR_IN); if(getsockname(data_sock, (struct sockaddr *)addr,&addr_len)!=0) { csi->socket_error=ERROR_VALUE; close_socket(data_sock); return(INVALID_SOCKET); } SOCKADDR_IN ctrl_addr; addr_len=sizeof(ctrl_addr); if(getsockname(ctrl_sock, (struct sockaddr *)&ctrl_addr,&addr_len)!=0) { csi->socket_error=ERROR_VALUE; close_socket(data_sock); return(INVALID_SOCKET); } if(listen(data_sock, 1)!= 0) { csi->socket_error=ERROR_VALUE; close_socket(data_sock); return(INVALID_SOCKET); } ip_addr.dw=ntohl(ctrl_addr.sin_addr.s_addr); port.w=ntohs(addr->sin_port); sprintf(cmd,"PORT %u,%u,%u,%u,%u,%u" ,ip_addr.b[3] ,ip_addr.b[2] ,ip_addr.b[1] ,ip_addr.b[0] ,port.b[1] ,port.b[0] ); if(!ftp_cmd(csi,ctrl_sock,cmd,rsp) || atoi(rsp)!=200 /* PORT response */) { close_socket(data_sock); return(INVALID_SOCKET); } } return(data_sock); } bool sbbs_t::ftp_get(csi_t* csi, SOCKET ctrl_sock, char* src, char* dest, bool dir) { char cmd[512]; char rsp[512]; char buf[4097]; int rd; int result; BOOL data_avail; ulong total=0; SOCKET data_sock; SOCKADDR_IN addr; socklen_t addr_len; FILE* fp=NULL; struct timeval tv; fd_set socket_set; if((data_sock=ftp_data_sock(csi, ctrl_sock, &addr))==INVALID_SOCKET) return(false); if(csi->ftp_mode&CS_FTP_PASV) { #if 0 // Debug bprintf("Connecting to %s:%hd\r\n" ,inet_ntoa(addr.sin_addr) ,ntohs(addr.sin_port)); #endif if(connect(data_sock,(struct sockaddr *)&addr,sizeof(addr))!=0) { csi->socket_error=ERROR_VALUE; close_socket(data_sock); return(false); } } if(dir) sprintf(cmd,"LIST %s",src); else sprintf(cmd,"RETR %s",src); if(!ftp_cmd(csi,ctrl_sock,cmd,rsp) || atoi(rsp)!=150 /* Open data connection */) { close_socket(data_sock); return(false); } if(!(csi->ftp_mode&CS_FTP_PASV)) { /* Normal (Active) FTP */ /* Setup for select() */ tv.tv_sec=TIMEOUT_SOCK_LISTEN; tv.tv_usec=0; FD_ZERO(&socket_set); FD_SET(data_sock,&socket_set); result=select(data_sock+1,&socket_set,NULL,NULL,&tv); if(result<1) { csi->socket_error=ERROR_VALUE; closesocket(data_sock); return(false); } SOCKET accept_sock; addr_len=sizeof(addr); if((accept_sock=accept_socket(data_sock,(struct sockaddr*)&addr,&addr_len)) ==INVALID_SOCKET) { csi->socket_error=ERROR_VALUE; closesocket(data_sock); return(false); } close_socket(data_sock); data_sock=accept_sock; } if(!dir) if((fp=fopen(dest,"wb"))==NULL) { close_socket(data_sock); return(false); } while(online) { if(!socket_check(ctrl_sock,NULL,NULL,0)) break; /* Control connection lost */ if(!socket_check(data_sock,&data_avail,NULL,100)) break; /* Data connection lost */ if(!data_avail) continue; if((rd=recv(data_sock, buf, sizeof(buf)-1, 0))<1) break; if(dir) { buf[rd]=0; bputs(buf); } else fwrite(buf,1,rd,fp); total+=rd; if(!dir && csi->ftp_mode&CS_FTP_HASH) outchar('#'); } if(!dir && csi->ftp_mode&CS_FTP_HASH) { CRLF; } if(fp!=NULL) fclose(fp); close_socket(data_sock); if(!ftp_cmd(csi,ctrl_sock,NULL,rsp) || atoi(rsp)!=226 /* Download complete */) return(false); bprintf("ftp: %lu bytes received.\r\n", total); return(true); } bool sbbs_t::ftp_put(csi_t* csi, SOCKET ctrl_sock, char* src, char* dest) { char cmd[512]; char rsp[512]; char path[MAX_PATH+1]; char buf[4097]; int rd; int result; ulong total=0; SOCKET data_sock; SOCKADDR_IN addr; socklen_t addr_len; FILE* fp=NULL; bool error=false; struct timeval tv; fd_set socket_set; SAFECOPY(path,src); if(!fexistcase(path)) return(false); if((data_sock=ftp_data_sock(csi, ctrl_sock, &addr))==INVALID_SOCKET) { bprintf("ftp: failure, line %d",__LINE__); return(false); } if(csi->ftp_mode&CS_FTP_PASV) { #if 0 // Debug bprintf("Connecting to %s:%hd\r\n" ,inet_ntoa(addr.sin_addr) ,ntohs(addr.sin_port)); #endif if(connect(data_sock,(struct sockaddr *)&addr,sizeof(addr))!=0) { bprintf("ftp: failure, line %d",__LINE__); csi->socket_error=ERROR_VALUE; close_socket(data_sock); return(false); } } if((fp=fopen(path,"rb"))==NULL) { bprintf("ftp: failure, line %d",__LINE__); close_socket(data_sock); return(false); } sprintf(cmd,"STOR %s",dest); if(!ftp_cmd(csi,ctrl_sock,cmd,rsp) || atoi(rsp)!=150 /* Open data connection */) { bprintf("ftp: failure, line %d",__LINE__); close_socket(data_sock); return(false); } if(!(csi->ftp_mode&CS_FTP_PASV)) { /* Normal (Active) FTP */ /* Setup for select() */ tv.tv_sec=TIMEOUT_SOCK_LISTEN; tv.tv_usec=0; FD_ZERO(&socket_set); FD_SET(data_sock,&socket_set); result=select(data_sock+1,&socket_set,NULL,NULL,&tv); if(result<1) { csi->socket_error=ERROR_VALUE; closesocket(data_sock); return(false); } SOCKET accept_sock; addr_len=sizeof(addr); if((accept_sock=accept_socket(data_sock,(struct sockaddr*)&addr,&addr_len)) ==INVALID_SOCKET) { csi->socket_error=ERROR_VALUE; closesocket(data_sock); return(false); } close_socket(data_sock); data_sock=accept_sock; } while(online && !feof(fp)) { rd=fread(buf,sizeof(char),sizeof(buf),fp); if(rd<1) /* EOF or READ error */ break; if(!socket_check(ctrl_sock,NULL,NULL,0)) break; /* Control connection lost */ if(sendsocket(data_sock,buf,rd)<1) { error=true; break; } total+=rd; if(csi->ftp_mode&CS_FTP_HASH) outchar('#'); } if(csi->ftp_mode&CS_FTP_HASH) { CRLF; } fclose(fp); close_socket(data_sock); if(!ftp_cmd(csi,ctrl_sock,NULL,rsp) || atoi(rsp)!=226 /* Upload complete */) return(false); if(!error) bprintf("ftp: %lu bytes sent.\r\n", total); return(!error); }