diff --git a/src/sbbs3/depends.mak b/src/sbbs3/depends.mak
index d6360de010c509b7aef3ecdaa40c1f626ae7a6b6..84723f8da17ae900963bb1fd9cf40029a6179528 100644
--- a/src/sbbs3/depends.mak
+++ b/src/sbbs3/depends.mak
@@ -25,6 +25,7 @@ $(LIBODIR)$(SLASH)exec.$(OFILE):		$(HEADERS) cmdshell.h
 $(LIBODIR)$(SLASH)execfile.$(OFILE):	$(HEADERS) cmdshell.h
 $(LIBODIR)$(SLASH)execfunc.$(OFILE):	$(HEADERS) cmdshell.h
 $(LIBODIR)$(SLASH)execmisc.$(OFILE):	$(HEADERS) cmdshell.h
+$(LIBODIR)$(SLASH)execnet.$(OFILE):	$(HEADERS) cmdshell.h
 $(LIBODIR)$(SLASH)execmsg.$(OFILE):		$(HEADERS) cmdshell.h
 $(LIBODIR)$(SLASH)fido.$(OFILE):		$(HEADERS)
 $(LIBODIR)$(SLASH)file.$(OFILE):      	$(HEADERS)
diff --git a/src/sbbs3/execnet.cpp b/src/sbbs3/execnet.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..d2fbfcd10c6e0b3164487d3b3f46aa29d2d3e103
--- /dev/null
+++ b/src/sbbs3/execnet.cpp
@@ -0,0 +1,761 @@
+/* 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 2001 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"
+
+int sbbs_t::exec_net(csi_t* csi)
+{
+	char	str[512],rsp[512],buf[1025],*p,**pp,**pp1,**pp2;
+	ushort	w;
+	uint 	i;
+	long	*lp,*lp1,*lp2;
+
+	switch(*(csi->ip++)) {	/* sub-op-code stored as next byte */
+		case CS_SOCKET_OPEN:
+			lp=getintvar(csi,*(long *)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);
+				if(sock!=INVALID_SOCKET) {
+
+					SOCKADDR_IN	addr;
+
+					memset(&addr,0,sizeof(addr));
+					addr.sin_addr.s_addr = htonl(cfg.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,*(long *)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]==*lp)
+						csi->socket[i]=0; 
+				*lp=0;
+			}
+			return(0);
+		case CS_SOCKET_CHECK:
+			lp=getintvar(csi,*(long *)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)==true)
+				csi->logic=LOGIC_TRUE;
+			else
+				csi->socket_error=ERROR_VALUE;
+				
+			return(0);
+		case CS_SOCKET_CONNECT:
+			lp=getintvar(csi,*(long *)csi->ip);		/* socket */
+			csi->ip+=4;
+
+			pp=getstrvar(csi,*(long *)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))==0)
+				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,*(long *)csi->ip);		/* socket */
+			csi->ip+=4;
+			csi->socket_error=0;
+			/* TODO */
+			return(0);
+		case CS_SOCKET_NREAD:
+			lp1=getintvar(csi,*(long *)csi->ip);		/* socket */
+			csi->ip+=4;
+			lp2=getintvar(csi,*(long *)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,*(long *)csi->ip);			/* socket */
+			csi->ip+=4;
+			pp=getstrvar(csi,*(long *)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_WRITE:	
+			lp=getintvar(csi,*(long *)csi->ip);			/* socket */
+			csi->ip+=4;
+			pp=getstrvar(csi,*(long *)csi->ip);			/* buffer */
+			csi->ip+=4;
+
+			csi->logic=LOGIC_FALSE;
+			csi->socket_error=0;
+
+			if(!lp || !pp || !(*pp))
+				return(0);
+
+			if(send(*lp,*pp,strlen(*pp),0)>0)
+				csi->logic=LOGIC_TRUE;
+			else
+				csi->socket_error=ERROR_VALUE;
+			return(0);
+
+		case CS_FTP_LOGIN:
+			lp=getintvar(csi,*(long *)csi->ip);			/* socket */
+			csi->ip+=4;
+			pp1=getstrvar(csi,*(long *)csi->ip);		/* username */
+			csi->ip+=4;
+			pp2=getstrvar(csi,*(long *)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\r\n",*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,*(long *)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,*(long *)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,*(long *)csi->ip);			/* socket */
+			csi->ip+=4;
+			pp=getstrvar(csi,*(long *)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,*(long *)csi->ip);			/* socket */
+			csi->ip+=4;
+			pp=getstrvar(csi,*(long *)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,*(long *)csi->ip);			/* socket */
+			csi->ip+=4;
+			pp=getstrvar(csi,*(long *)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,*(long *)csi->ip);			/* socket */
+			csi->ip+=4;
+			pp1=getstrvar(csi,*(long *)csi->ip);		/* src path */
+			csi->ip+=4;
+			pp2=getstrvar(csi,*(long *)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,*(long *)csi->ip);			/* socket */
+			csi->ip+=4;
+			pp1=getstrvar(csi,*(long *)csi->ip);		/* src path */
+			csi->ip+=4;
+			pp2=getstrvar(csi,*(long *)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); 
+	}
+}
+
+#define SOCK_READLINE_TIMEOUT	5	/* seconds */
+
+/* FTP Command/Response function */
+bool sbbs_t::ftp_cmd(csi_t* csi, SOCKET sock, char* cmdsrc, char* rsp)
+{
+	char	cmd[512];
+	int		len;
+	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(send(sock,cmd,len,0)!=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(time(NULL)-start>SOCK_READLINE_TIMEOUT) {
+					lprintf("!ftp_cmd: SOCK_READLINE_TIMEOUT exceeded");
+					return(FALSE);
+				}
+
+				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;
+	int		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))==INVALID_SOCKET) {
+		csi->socket_error=ERROR_VALUE;
+		return(INVALID_SOCKET);
+	}
+
+	memset(addr,0,sizeof(addr));
+	addr->sin_addr.s_addr = htonl(cfg.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 */) {
+			close_socket(data_sock);
+			return(INVALID_SOCKET);
+		}
+
+		p=strchr(rsp,'(');
+		if(p==NULL) {
+			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) {
+			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);
+		} 
+
+		if(listen(data_sock, 1)!= 0) {
+			csi->socket_error=ERROR_VALUE;
+			close_socket(data_sock);
+			return(INVALID_SOCKET);
+		}
+
+		ip_addr.dw=ntohl(addr->sin_addr.s_addr);
+		port.w=ntohs(addr->sin_port);
+		sprintf(cmd,"PORT %u,%u,%u,%u,%hu,%hu"
+			,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		addr_len;
+	ulong	total=0;
+	SOCKET	data_sock;
+	SOCKADDR_IN	addr;
+	FILE*	fp=NULL;
+
+	if((data_sock=ftp_data_sock(csi, ctrl_sock, &addr))==INVALID_SOCKET)
+		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) {
+
+#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);
+		}
+
+	} else {	/* Normal (Active) FTP */
+
+		SOCKET accept_sock;
+
+		addr_len=sizeof(addr);
+		if((accept_sock=accept(data_sock,(struct sockaddr*)&addr,&addr_len))
+			==INVALID_SOCKET) {
+			csi->socket_error=ERROR_VALUE;
+			closesocket(data_sock);
+			return(false);
+		}
+
+		if(cfg.startup->socket_open!=NULL)
+			cfg.startup->socket_open(TRUE);
+
+		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))
+			break; /* Control connection lost */
+
+		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	buf[4097];
+	int		rd;
+	int		addr_len;
+	ulong	total=0;
+	SOCKET	data_sock;
+	SOCKADDR_IN	addr;
+	FILE*	fp=NULL;
+	bool	error=false;
+
+	if((data_sock=ftp_data_sock(csi, ctrl_sock, &addr))==INVALID_SOCKET)
+		return(false);
+
+	sprintf(cmd,"STOR %s",dest);
+
+	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) {
+
+#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);
+		}
+
+	} else {	/* Normal (Active) FTP */
+
+		SOCKET accept_sock;
+
+		addr_len=sizeof(addr);
+		if((accept_sock=accept(data_sock,(struct sockaddr*)&addr,&addr_len))
+			==INVALID_SOCKET) {
+			csi->socket_error=ERROR_VALUE;
+			closesocket(data_sock);
+			return(false);
+		}
+
+		if(cfg.startup->socket_open!=NULL)
+			cfg.startup->socket_open(TRUE);
+
+		close_socket(data_sock);
+		data_sock=accept_sock;
+	}
+
+
+	if((fp=fopen(src,"rb"))==NULL) {
+		close_socket(data_sock);
+		return(false);
+	}
+
+	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))
+			break; /* Control connection lost */
+
+		if(send(data_sock,buf,rd,0)<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);
+}