diff --git a/src/sbbs3/baja.c b/src/sbbs3/baja.c
index c902ea7f0e9e1636f7d64a8496f6f7caa5f02af6..b95dfac0a9db34cdccc208ff37438da8bfca9562 100644
--- a/src/sbbs3/baja.c
+++ b/src/sbbs3/baja.c
@@ -440,7 +440,7 @@ void expdefs(uchar *line)
 
 void compile(char *src)
 {
-	uchar *str,*save,*p,*sp,*tp,*arg,*arg2,*arg3,*ar,ch;
+	uchar *str,*save,*p,*sp,*tp,*arg,*arg2,*arg3,*arg4,*ar,ch;
     ushort i,j;
 	long l,savline;
 	FILE *in;
@@ -478,7 +478,7 @@ void compile(char *src)
 		if(display)
 			printf("%s\n",p);
 		sp=strchr(p,SP);
-		arg=arg2=arg3="";
+		arg=arg2=arg3=arg4="";
 		if(sp) {
 			*sp=0;
 			arg=sp+1;
@@ -490,7 +490,11 @@ void compile(char *src)
 				sp=strchr(arg2,SP);
 				if(sp) {
 					arg3=sp+1;
-					while(*arg3 && *arg3<=SP) arg3++; } } }
+					while(*arg3 && *arg3<=SP) arg3++; 
+					sp=strchr(arg3,SP);
+					if(sp) {
+						arg4=sp+1;
+						while(*arg4 && *arg4<=SP) arg4++; } } } }
 
 		if(!stricmp(p,"!INCLUDE")) {
 			savline=line;
@@ -622,6 +626,19 @@ void compile(char *src)
 			fprintf(out,"%c%c",CS_ONE_MORE_BYTE,CS_EXIT);
 			continue; }
 
+		if(!stricmp(p,"LOOP") || !stricmp(p,"LOOP_BEGIN")) {
+			fprintf(out,"%c%c",CS_ONE_MORE_BYTE,CS_LOOP_BEGIN);
+			continue; }
+		if(!stricmp(p,"CONTINUE") || !stricmp(p,"CONTINUE_LOOP")) {
+			fprintf(out,"%c%c",CS_ONE_MORE_BYTE,CS_CONTINUE_LOOP);
+			continue; }
+		if(!stricmp(p,"BREAK") || !stricmp(p,"BREAK_LOOP")) {
+			fprintf(out,"%c%c",CS_ONE_MORE_BYTE,CS_BREAK_LOOP);
+			continue; }
+		if(!stricmp(p,"END_LOOP")) {
+			fprintf(out,"%c%c",CS_ONE_MORE_BYTE,CS_END_LOOP);
+			continue; }
+
 		if(!stricmp(p,"USER_EVENT")) {
 			if(!(*arg))
 				break;
@@ -646,8 +663,8 @@ void compile(char *src)
 		if(!stricmp(p,"ASYNC")) {
 			fprintf(out,"%c",CS_ASYNC);
 			continue; }
-		if(!stricmp(p,"RIOSYNC")) {
-			fprintf(out,"%c",CS_RIOSYNC);
+		if(!stricmp(p,"RIOSYNC")) {		/* deprecated */
+			fprintf(out,"%c",CS_SYNC);
 			continue; }
 		if(!stricmp(p,"GETTIMELEFT")) {
 			fprintf(out,"%c",CS_GETTIMELEFT);
@@ -942,13 +959,20 @@ void compile(char *src)
 			writecrc(src,arg);
 			writecrc(src,arg2);
 			continue; }
+		if(!stricmp(p,"COPY_CHAR") || !stricmp(p,"COPY_KEY")) {
+			if(!(*arg)) break;
+			fprintf(out,"%c%c",CS_VAR_INSTRUCTION,COPY_CHAR);
+			writecrc(src,arg);
+			continue; }
 		if(!stricmp(p,"COPY_FIRST_CHAR")) {
+			if(!(*arg) || !(*arg2)) break;
 			fputc(CS_VAR_INSTRUCTION,out);
 			fputc(COPY_FIRST_CHAR,out);
 			writecrc(src,arg);
 			writecrc(src,arg2);
 			continue; }
 		if(!stricmp(p,"COMPARE_FIRST_CHAR")) {
+			if(!(*arg) || !(*arg2)) break;
 			fputc(CS_VAR_INSTRUCTION,out);
 			fputc(COMPARE_FIRST_CHAR,out);
 			writecrc(src,arg);
@@ -1407,6 +1431,24 @@ void compile(char *src)
 			writecrc(src,arg2);
 			continue; }
 
+		if(!stricmp(p,"COMPARE_ANY_BITS") || !stricmp(p,"COMPARE_ALL_BITS")) {
+			if(!(*arg) || !(*arg2))
+				break;
+			if((l=isvar(arg2))!=0) {
+				fputc(CS_USE_INT_VAR,out);
+				fwrite(&l,4,1,out); // variable
+				fputc(6,out);		// int offset
+				fputc(4,out);		// int length
+				l=0; }				// place holder
+			else
+				l=val(src,arg2);
+			fprintf(out,"%c%c",CS_VAR_INSTRUCTION
+				,!stricmp(p,"COMPARE_ANY_BITS") ? COMPARE_ANY_BITS : COMPARE_ALL_BITS);
+			writecrc(src,arg);
+			fwrite(&l,sizeof(l),1,out);
+			continue;
+		}
+
 		if(!stricmp(p,"OR_INT_VAR")
 			|| (!stricmp(p,"OR")
 				&& (isdigit(*arg2) || atol(arg2) || *arg2=='\'' || *arg2=='.'))) {
@@ -1972,6 +2014,175 @@ void compile(char *src)
 			writecrc(src,arg);			/* int var */
 			continue; }
 
+		/* NET_FUNCTIONS */
+
+		if(!stricmp(p,"SOCKET_OPEN")) {
+			if(!(*arg)) break;
+			fprintf(out,"%c%c",CS_NET_FUNCTION,CS_SOCKET_OPEN);
+			writecrc(src,arg);			/* int var (socket) */
+			continue;
+		}
+		if(!stricmp(p,"SOCKET_CLOSE")) {
+			if(!(*arg)) break;
+			fprintf(out,"%c%c",CS_NET_FUNCTION,CS_SOCKET_CLOSE);
+			writecrc(src,arg);			/* int var (socket) */
+			continue;
+		}
+		if(!stricmp(p,"SOCKET_CHECK")) {
+			if(!(*arg)) break;
+			fprintf(out,"%c%c",CS_NET_FUNCTION,CS_SOCKET_CHECK);
+			writecrc(src,arg);			/* int var (socket) */
+			continue;
+		}
+		if(!stricmp(p,"SOCKET_CONNECT")) {
+			if(!(*arg) || !(*arg2) || !(*arg3)) break;
+
+			/* TCP port */
+			if((l=isvar(arg3))!=0) {
+				fputc(CS_USE_INT_VAR,out);
+				fwrite(&l,4,1,out); // variable
+				fputc(10,out);		// int offset
+				fputc(2,out);		// int length
+				i=0; }				// place holder
+			else
+				i=val(src,arg3);
+
+			fprintf(out,"%c%c",CS_NET_FUNCTION,CS_SOCKET_CONNECT);
+			writecrc(src,arg);			/* int var (socket) */
+			writecrc(src,arg2);			/* str var (address) */
+			fwrite(&i,2,1,out);
+			continue;
+		}
+		if(!stricmp(p,"SOCKET_ACCEPT")) {
+			if(!(*arg)) break;
+			fprintf(out,"%c%c",CS_NET_FUNCTION,CS_SOCKET_ACCEPT);
+			writecrc(src,arg);			/* int var (socket) */
+			continue;
+		}
+		if(!stricmp(p,"SOCKET_NREAD")) {
+			if(!(*arg) || !(*arg2)) break;
+			fprintf(out,"%c%c",CS_NET_FUNCTION,CS_SOCKET_NREAD);
+			writecrc(src,arg);			/* int var (socket) */
+			writecrc(src,arg2);			/* int var (nbytes) */
+			continue;
+		}
+		if(!stricmp(p,"SOCKET_PEEK")) {
+			if(!(*arg) || !(*arg2)) break;
+
+			/* length */
+			if(!(*arg3))
+				i=0;
+			else if((l=isvar(arg3))!=0) {
+				fputc(CS_USE_INT_VAR,out);
+				fwrite(&l,4,1,out); // variable
+				fputc(10,out);		// int offset
+				fputc(2,out);		// int length
+				i=0; }				// place holder
+			else
+				i=val(src,arg3);
+
+			fprintf(out,"%c%c",CS_NET_FUNCTION,CS_SOCKET_PEEK);
+			writecrc(src,arg);			/* int var (socket) */
+			writecrc(src,arg2);			/* str var (buffer) */
+			fwrite(&i,sizeof(i),1,out);	/* word (length) */
+			continue;
+		}
+		if(!stricmp(p,"SOCKET_READ")) {
+			if(!(*arg) || !(*arg2)) break;
+
+			/* length */
+			if(!(*arg3))
+				i=0;
+			else if((l=isvar(arg3))!=0) {
+				fputc(CS_USE_INT_VAR,out);
+				fwrite(&l,4,1,out); // variable
+				fputc(10,out);		// int offset
+				fputc(2,out);		// int length
+				i=0; }				// place holder
+			else 
+				i=val(src,arg3);
+
+			fprintf(out,"%c%c",CS_NET_FUNCTION,CS_SOCKET_READ);
+			writecrc(src,arg);			/* int var (socket) */
+			writecrc(src,arg2);			/* str var (buffer) */
+			fwrite(&i,sizeof(i),1,out);	/* word (length) */
+			continue;
+		}
+		if(!stricmp(p,"SOCKET_WRITE")) {
+			if(!(*arg) || !(*arg2)) break;
+			fprintf(out,"%c%c",CS_NET_FUNCTION,CS_SOCKET_WRITE);
+			writecrc(src,arg);			/* int var (socket) */
+			writecrc(src,arg2);			/* str var (buffer) */
+			continue;
+		}
+
+		/* FTP functions */
+		if(!stricmp(p,"FTP_LOGIN")) {
+			if(!(*arg) || !(*arg2) || !(*arg3)) break;
+			fprintf(out,"%c%c",CS_NET_FUNCTION,CS_FTP_LOGIN);
+			writecrc(src,arg);			/* int var (socket) */
+			writecrc(src,arg2);			/* int var (user) */
+			writecrc(src,arg3);			/* int var (password) */
+			continue;
+		}
+		if(!stricmp(p,"FTP_LOGOUT")) {
+			if(!(*arg)) break;
+			fprintf(out,"%c%c",CS_NET_FUNCTION,CS_FTP_LOGOUT);
+			writecrc(src,arg);			/* int var (socket) */
+			continue;
+		}
+		if(!stricmp(p,"FTP_PWD")) {
+			if(!(*arg)) break;
+			fprintf(out,"%c%c",CS_NET_FUNCTION,CS_FTP_PWD);
+			writecrc(src,arg);			/* int var (socket) */
+			continue;
+		}
+		if(!stricmp(p,"FTP_CWD")) {
+			if(!(*arg) || !(*arg2)) break;
+			fprintf(out,"%c%c",CS_NET_FUNCTION,CS_FTP_CWD);
+			writecrc(src,arg);			/* int var (socket) */
+			writecrc(src,arg2);			/* str var (path)	*/
+			continue;
+		}
+		if(!stricmp(p,"FTP_DIR")) {
+			if(!(*arg) || !(*arg2)) break;
+			fprintf(out,"%c%c",CS_NET_FUNCTION,CS_FTP_DIR);
+			writecrc(src,arg);			/* int var (socket) */
+			writecrc(src,arg2);			/* str var (path)	*/
+			continue;
+		}
+		if(!stricmp(p,"FTP_GET")) {
+			if(!(*arg) || !(*arg2) || !(*arg3)) break;
+			fprintf(out,"%c%c",CS_NET_FUNCTION,CS_FTP_GET);
+			writecrc(src,arg);			/* int var (socket) */
+			writecrc(src,arg2);			/* str var (src path) */
+			writecrc(src,arg3);			/* str var (dest path) */
+			continue;
+		}
+		if(!stricmp(p,"FTP_PUT")) {
+			if(!(*arg) || !(*arg2) || !(*arg3)) break;
+			fprintf(out,"%c%c",CS_NET_FUNCTION,CS_FTP_PUT);
+			writecrc(src,arg);			/* int var (socket) */
+			writecrc(src,arg2);			/* str var (src path) */
+			writecrc(src,arg3);			/* str var (dest path) */
+			continue;
+		}
+		if(!stricmp(p,"FTP_DELETE")) {
+			if(!(*arg) || !(*arg2)) break;
+			fprintf(out,"%c%c",CS_NET_FUNCTION,CS_FTP_DELETE);
+			writecrc(src,arg);			/* int var (socket) */
+			writecrc(src,arg2);			/* str var (path)	*/
+			continue;
+		}
+		if(!stricmp(p,"FTP_RENAME")) {
+			if(!(*arg) || !(*arg2) || !(*arg3)) break;
+			fprintf(out,"%c%c",CS_NET_FUNCTION,CS_FTP_RENAME);
+			writecrc(src,arg);			/* int var (socket) */
+			writecrc(src,arg2);			/* str var (org name) */
+			writecrc(src,arg3);			/* str var (new name) */
+			continue;
+		}
+
 		if(!stricmp(p,"NODE_ACTION")) {
 			if(!(*arg)) break;
 			if((l=isvar(arg))!=0) {
@@ -2438,7 +2649,7 @@ void compile(char *src)
 			if(!(*arg)) break;
 			p=strchr(arg,SP);
 			if(p) *p=0;
-			if(!(*arg) || isdigit(*arg))
+			if(isdigit(*arg))
 				fprintf(out,"%c%c",CS_SHIFT_STR,atoi(arg));
 			else {
 				if((l=isvar(arg2))!=0) {
@@ -2455,6 +2666,23 @@ void compile(char *src)
 				if(!i) i=128;
 				fwrite(&i,1,1,out); }
 			continue; }
+		if(!stricmp(p,"SHIFT_TO_FIRST_CHAR") || !stricmp(p,"SHIFT_TO_LAST_CHAR")) {
+			if(!(*arg) || !(*arg2)) break;
+			if((l=isvar(arg2))!=0) {
+				fputc(CS_USE_INT_VAR,out);
+				fwrite(&l,4,1,out); // variable
+				fputc(6,out);		// int offset
+				fputc(1,out);		// int length
+				ch=0; }				// place holder
+			else
+				ch=val(src,arg2);
+
+			fprintf(out,"%c%c",CS_VAR_INSTRUCTION
+				,!stricmp(p,"SHIFT_TO_FIRST_CHAR") ? SHIFT_TO_FIRST_CHAR : SHIFT_TO_LAST_CHAR);
+			writecrc(src,arg);
+			fwrite(&ch,sizeof(ch),1,out);
+			continue;
+		}
 		if(!stricmp(p,"TRUNCSP")) {
 			fprintf(out,"%c%c",CS_VAR_INSTRUCTION,TRUNCSP_STR_VAR);
 			writecrc(src,arg);
@@ -2746,6 +2974,9 @@ void compile(char *src)
 		if(!stricmp(p,"INKEY")) {
 			fprintf(out,"%c",CS_INKEY);
 			continue; }
+		if(!stricmp(p,"INCHAR")) {
+			fprintf(out,"%c",CS_INCHAR);
+			continue; }
 		if(!stricmp(p,"GETKEY")) {
 			fprintf(out,"%c",CS_GETKEY);
 			continue; }
@@ -3119,8 +3350,8 @@ void compile(char *src)
 }
 
 char *banner=	"\n"
-				"BAJA v2.20 - Synchronet Shell/Module Compiler - "
-				"Copyright 2000 Rob Swindell\n";
+				"BAJA v2.30 - Synchronet Shell/Module Compiler - "
+				"Copyright 2001 Rob Swindell\n";
 
 char *usage=	"\n"
 				"usage: baja [/opts] file[.src]\n"
diff --git a/src/sbbs3/cmdshell.h b/src/sbbs3/cmdshell.h
index 8f6090171e56020aef8ee68043a19dc3016c6552..3a1988d1ae3ca88a1596ee63ffbc1ed84dbdb823 100644
--- a/src/sbbs3/cmdshell.h
+++ b/src/sbbs3/cmdshell.h
@@ -81,7 +81,7 @@ enum {
 	,CS_SELECT_EDITOR
 	,CS_SET_EDITOR			// 0x20
 	,CS_INKEY
-	,CS_RIOSYNC
+	,CS_INCHAR				// Was RIOSYNC (now deprecated) 02/18/01
 	,CS_GETTIMELEFT
 	,CS_SAVELINE
 	,CS_RESTORELINE
@@ -175,6 +175,10 @@ enum {
 	,CS_CASE
 	,CS_USE_INT_VAR 		// 0x7a
 
+/* Network (TCP/IP) Functions */
+
+	,CS_NET_FUNCTION=0x7d
+
 /* File I/O Functions */
 
 	,CS_FIO_FUNCTION=0x7e
@@ -403,8 +407,8 @@ enum {
 	,VAR_RESERVED_L1
 	,AND_INT_VAR					// Bit-wise AND int variable (static)
 	,AND_INT_VARS		// 0x50 	// Bit-wise AND int variable (dynamic)
-	,VAR_RESERVED_M4
-	,VAR_RESERVED_M3
+	,COMPARE_ANY_BITS
+	,COMPARE_ALL_BITS
 	,VAR_RESERVED_M2
 	,VAR_RESERVED_M1
 	,OR_INT_VAR 					// Bit-wise OR int variable (static)
@@ -450,11 +454,14 @@ enum {
 	,SEND_FILE_VIA_VAR				// Send file (dynamic) via protocol
 	,FTIME_TO_INT					// Put time of str var file into int
 	,RECEIVE_FILE_VIA				// Receive file (static) via protocol
-	,RECEIVE_FILE_VIA_VAR			// Receive file (dynamic) via protocol
+	,RECEIVE_FILE_VIA_VAR // 0x80	// Receive file (dynamic) via protocol
 	,TELNET_GATE_STR				// Run telnet gateway to static address with mode
 	,TELNET_GATE_VAR				// Run telnet gateway to variable address with mode
 	,COPY_FIRST_CHAR				// Copy first char of str var to int var
 	,COMPARE_FIRST_CHAR				// Compare first char of str var to static char
+	,COPY_CHAR						// Copy cmdkey to int var or str var
+	,SHIFT_TO_FIRST_CHAR			// Shift str var to first occurance of static char
+	,SHIFT_TO_LAST_CHAR				// Shift str var to last occurance of static char
 	};
 
 /* Preceeded by CS_STR_FUNCTION */
@@ -472,6 +479,10 @@ enum {								// More single byte instructions
 	,CS_LOGON						// Logon procedure
 	,CS_LOGOUT						// Logout procedure
 	,CS_EXIT						// Exit current module immediately
+	,CS_LOOP_BEGIN					// Looping anchor
+	,CS_CONTINUE_LOOP				// "next" loop
+	,CS_BREAK_LOOP					// Stop executing loop
+	,CS_END_LOOP					// End of looping code block
 	};
 
 /* Preceeded by CS_TWO_MORE_BYTES */
@@ -479,6 +490,30 @@ enum {								// More two byte instructions
 	 CS_USER_EVENT					// External user event
 	};
 
+/* Preceeded by CS_NET_FUNCTION */
+
+enum {
+	 CS_SOCKET_OPEN					// Open a socket
+	,CS_SOCKET_CLOSE				// Close a socket
+	,CS_SOCKET_CONNECT				// Outbound connection
+	,CS_SOCKET_ACCEPT				// Accept an incomming connection
+	,CS_SOCKET_NREAD				// Get number of bytes in input buffer
+	,CS_SOCKET_PEEK					// Peek at input buffer
+	,CS_SOCKET_READ					// Read input buffer
+	,CS_SOCKET_WRITE				// Write to socket
+	,CS_SOCKET_CHECK				// Check connection
+
+	,CS_FTP_LOGIN					// socket, username, password
+	,CS_FTP_LOGOUT
+	,CS_FTP_PWD						// print working dir
+	,CS_FTP_CWD						// change working dir
+	,CS_FTP_DIR						// path
+	,CS_FTP_PUT						// path
+	,CS_FTP_GET						// path, offset
+	,CS_FTP_RENAME					
+	,CS_FTP_DELETE
+	};
+
 /* Preceeded by CS_FIO_FUNCTION */
 enum {
 	 FIO_OPEN						// Open file (static filename)
@@ -562,12 +597,21 @@ enum {
 #define CS_IN_SWITCH	(1L<<0) 	/* Inside active switch statement */
 #define CS_OFFLINE_EXEC (1L<<1) 	/* Offline execution */
 
+									/* Bits for csi_t.ftp_mode */
+#define CS_FTP_ECHO_CMD	(1L<<0)		/* Echo command lines to user (for debug) */
+#define CS_FTP_ECHO_RSP	(1L<<1)		/* Echo response lines to user */
+#define CS_FTP_PASV		(1L<<2)		/* Use PASV mode transfers */
+#define CS_FTP_ASCII	(1L<<3)		/* Use ASCII file transfers */
+#define CS_FTP_HASH		(1L<<4)		/* Hash marks printing during xfers */
+
 #define MAX_RETS		50	/* maximum nested call depth */
 #define MAX_CMDRETS		50	/* maximum nested cmd depth */
+#define MAX_LOOPDEPTH	50	/* maximum nested loop depth */
 #define MAX_STRVARS		26
 #define MAX_INTVARS		26
 #define MAX_STRLEN		81
 #define MAX_FOPENS		10	/* maximum open files */
+#define MAX_SOCKETS		10	/* maximum open sockets */
 #define MAX_SYSVARS		16	/* maximum system variable saves */
 
 #define LOGIC_LESS		-1
@@ -586,20 +630,26 @@ typedef struct {					/* Command shell image */
 			cmd,					/* Current command key */
 			etx,					/* End-of-text character */
 			*ret[MAX_RETS], 		/* Return address stack */
-			rets,					/* Returns on stack */
 			*cmdret[MAX_CMDRETS],	/* Command return address stack */
-			cmdrets;				/* Command returns on stack */
+			*loop_home[MAX_LOOPDEPTH];	/* Loop home address stack */
 
 	int 	logic;					/* Current logic */
 	
 	FILE	*file[MAX_FOPENS];		/* Each file ptr */
+	int		socket[MAX_SOCKETS];	/* Open socket descriptors */
+	long	socket_error;			/* Last socket error */
 
 	uint	str_vars,				/* Total number of string variables */
 			int_vars,				/* Total number of integer variables */
-			files;					/* Open files */
+			files,					/* Open files */
+			sockets,				/* Open sockets */
+			rets,					/* Returns on stack */
+			loops,					/* Nested loop depth (loops on stack) */
+			cmdrets;				/* Command returns on stack */
 
 	long	retval, 				/* Return value */
 			misc,					/* Misc bits */
+			ftp_mode,				/* FTP operation mode */
 			switch_val, 			/* Current switch value */
 			*int_var,				/* Integer variables */
 			*str_var_name,			/* String variable names (CRC-32) */
diff --git a/src/sbbs3/exec.cpp b/src/sbbs3/exec.cpp
index 497dba81d34af2ca5fc24dd3d08673f4fb92d7a0..ca5bac4402f54c5ec87cdc8c3abc25d620f6a532 100644
--- a/src/sbbs3/exec.cpp
+++ b/src/sbbs3/exec.cpp
@@ -427,6 +427,19 @@ long * sbbs_t::getintvar(csi_t *bin, long name)
 		case 0x430178ec:
 			return((long *)&cfg.uq);
 
+		case 0x455CB929:
+			return(&bin->ftp_mode);
+
+		case 0x2105D2B9:
+			return(&bin->socket_error);
+
+		case 0xA0023A2E:
+			return((long *)&cfg.startup->options);
+
+		case 0x16E2585F:
+			sysvar_l[sysvar_li]=client_socket;
+			break;
+
 		default:
 			if(bin->int_var && bin->int_var_name)
 				for(i=0;i<bin->int_vars;i++)
@@ -450,6 +463,8 @@ void sbbs_t::clearvars(csi_t *bin)
 	bin->int_var=NULL;
 	bin->int_var_name=NULL;
 	bin->files=0;
+	bin->loops=0;
+	bin->sockets=0;
 	bin->retval=0;
 }
 
@@ -475,6 +490,12 @@ void sbbs_t::freevars(csi_t *bin)
 			bin->file[i]=0; 
 		}
 	}
+	for(i=0;i<bin->sockets;i++) {
+		if(bin->socket[i]) {
+			close_socket((SOCKET)bin->socket[i]);
+			bin->socket[i]=0; 
+		}
+	}
 }
 
 /****************************************************************************/
@@ -616,6 +637,7 @@ void sbbs_t::skipto(csi_t *csi, uchar inst)
 						case STRLWR_VAR:
 						case TRUNCSP_STR_VAR:
 						case CHKFILE_VAR:
+						case COPY_CHAR:
 						case STRIP_CTRL_STR_VAR:
 							csi->ip+=4; /* Skip variable name */
 							continue;
@@ -627,6 +649,8 @@ void sbbs_t::skipto(csi_t *csi, uchar inst)
 						case SEND_FILE_VIA_VAR:
 						case RECEIVE_FILE_VIA_VAR:
 						case COMPARE_FIRST_CHAR:
+						case SHIFT_TO_FIRST_CHAR:
+						case SHIFT_TO_LAST_CHAR:
 							csi->ip+=4; /* Skip variable name */
 							csi->ip++;	/* Skip char */
 							continue;
@@ -721,6 +745,48 @@ void sbbs_t::skipto(csi_t *csi, uchar inst)
 							csi->ip+=4;             /* Variable */
 							continue; }
 
+				case CS_NET_FUNCTION:
+					csi->ip++;
+					switch(*(csi->ip++)) {
+						case CS_SOCKET_CONNECT:
+							csi->ip+=4;				/* socket */
+							csi->ip+=4;				/* address */
+							csi->ip+=2;				/* port */
+							continue;
+						case CS_SOCKET_NREAD:
+							csi->ip+=4;				/* socket */
+							csi->ip+=4;				/* intvar */
+							continue;
+						case CS_SOCKET_READ:
+						case CS_SOCKET_PEEK:
+							csi->ip+=4;				/* socket */
+							csi->ip+=4;				/* buffer */
+							csi->ip+=2;				/* length */
+							continue;
+						case CS_SOCKET_WRITE:
+							csi->ip+=4;				/* socket */
+							csi->ip+=4;				/* strvar */
+							continue;
+
+						case CS_FTP_LOGIN:
+						case CS_FTP_GET:
+						case CS_FTP_PUT:
+						case CS_FTP_RENAME:
+							csi->ip+=4;				/* socket */
+							csi->ip+=4;				/* username/path */
+							csi->ip+=4;				/* password/path */
+							continue;
+						case CS_FTP_DIR:
+						case CS_FTP_CWD:
+						case CS_FTP_DELETE:
+							csi->ip+=4;				/* socket */
+							csi->ip+=4;				/* path */
+							continue;
+
+						default:
+							csi->ip+=4;				/* socket */
+							continue; }
+
 				case CS_COMPARE_ARS:
 					csi->ip++;
 					csi->ip+=(*csi->ip);
@@ -746,8 +812,17 @@ void sbbs_t::skipto(csi_t *csi, uchar inst)
 			continue; }
 
 		if(*csi->ip==CS_ONE_MORE_BYTE) {
+			if(inst==CS_END_LOOP && *(csi->ip+1)==CS_END_LOOP)
+				break;
+
 			csi->ip++;				/* skip extension */
 			csi->ip++;				/* skip instruction */
+
+			if(*(csi->ip-1)==CS_LOOP_BEGIN) {	/* nested loop */
+				skipto(csi,CS_END_LOOP);
+				csi->ip+=2;
+			}
+
 			continue; }
 
 		if(*csi->ip==CS_TWO_MORE_BYTES) {
@@ -1000,8 +1075,10 @@ int sbbs_t::exec(csi_t *csi)
 				csi->ip=csi->cs+*((ushort *)(csi->ip));
 				return(0);
 			case CS_CALL:
-				csi->ret[csi->rets++]=csi->ip+2;
-				csi->ip=csi->cs+*((ushort *)(csi->ip));
+				if(csi->rets<MAX_RETS) {
+					csi->ret[csi->rets++]=csi->ip+2;
+					csi->ip=csi->cs+*((ushort *)(csi->ip));
+				}
 				return(0);
 			case CS_MSWAIT:
 				mswait(*(ushort *)csi->ip);
@@ -1179,10 +1256,12 @@ int sbbs_t::exec(csi_t *csi)
 					csi->str[0]=0;
 				return(0);
 			case CS_SHIFT_STR:
-				if(strlen(csi->str)>=*csi->ip)
-					memmove(csi->str,csi->str+(*csi->ip)
-						,strlen(csi->str)+1);
-				csi->ip++;
+				i=*(csi->ip++);
+				j=strlen(csi->str);
+				if(i>j) 
+					i=j;
+				if(i) 
+					memmove(csi->str,csi->str+i,j+1);
 				return(0);
 			case CS_COMPARE_KEY:
 				if( ((*csi->ip)==CS_DIGIT && isdigit(csi->cmd))
@@ -1353,6 +1432,22 @@ int sbbs_t::exec(csi_t *csi)
 					return(0);
 				case CS_EXIT:
 					return(1);
+				case CS_LOOP_BEGIN:
+					if(csi->loops<MAX_LOOPDEPTH)
+						csi->loop_home[csi->loops++]=(csi->ip-1);
+					return(0);
+				case CS_BREAK_LOOP:
+					if(csi->loops) {
+						skipto(csi,CS_END_LOOP);
+						csi->ip+=2;
+						csi->loops--;
+					}
+					return(0);
+				case CS_END_LOOP:
+				case CS_CONTINUE_LOOP:
+					if(csi->loops)
+						csi->ip=csi->loop_home[csi->loops-1];
+					return(0);
 				default:
 					errormsg(WHERE,ERR_CHK,"one byte extended function"
 						,*(csi->ip-1));
@@ -1397,9 +1492,11 @@ int sbbs_t::exec(csi_t *csi)
 		case CS_ASYNC:
 			ASYNC;
 			return(0);
+#if 0 /* Removed 02/18/01 - never used, officially deprecated for INCHAR */
 		case CS_RIOSYNC:
 			RIOSYNC(0);
 			return(0);
+#endif
 		case CS_GETTIMELEFT:
 			gettimeleft();
 			return(0);
@@ -1412,7 +1509,7 @@ int sbbs_t::exec(csi_t *csi)
 			csi->cmd=getkey(K_UPPER);
 			return(0);
 		case CS_GETCHAR:
-			csi->cmd=csi->str[0]=getkey(0);
+			csi->cmd=getkey(0);
 			return(0);
 		case CS_INKEY:
 			csi->cmd=toupper(inkey(K_GETSTR));
@@ -1421,6 +1518,13 @@ int sbbs_t::exec(csi_t *csi)
 			else
 				csi->logic=LOGIC_FALSE;
 			return(0);
+		case CS_INCHAR:
+			csi->cmd=inkey(K_GETSTR);
+			if(csi->cmd)
+				csi->logic=LOGIC_TRUE;
+			else
+				csi->logic=LOGIC_FALSE;
+			return(0);
 		case CS_GETKEYE:
 			csi->cmd=getkey(K_UPPER);
 			if(csi->cmd=='/') {
@@ -1514,15 +1618,12 @@ int sbbs_t::exec(csi_t *csi)
 			putmsg(csi->str,P_SAVEATR|P_NOABORT);
 			return(0);
 		case CS_CMD_HOME:
-			csi->cmdret[csi->cmdrets++]=(csi->ip-1);
+			if(csi->cmdrets<MAX_CMDRETS)
+				csi->cmdret[csi->cmdrets++]=(csi->ip-1);
 			return(0);
 		case CS_END_CMD:
 			if(csi->cmdrets)
 				csi->ip=csi->cmdret[--csi->cmdrets];
-	/*													Removed 06/07/95
-			else
-				errormsg(WHERE,ERR_CHK,"misplaced end_cmd",(csi->ip-csi->cs)-1);
-	*/
 			return(0);
 		case CS_CMD_POP:
 			if(csi->cmdrets)
diff --git a/src/sbbs3/execmisc.cpp b/src/sbbs3/execmisc.cpp
index 9def1b031f0657575f5a02322b7620b351dcf5ef..79a954607b373b3ac279b89fe8048108702c03ae 100644
--- a/src/sbbs3/execmisc.cpp
+++ b/src/sbbs3/execmisc.cpp
@@ -38,9 +38,33 @@
 #include "sbbs.h"
 #include "cmdshell.h"
 
-int sbbs_t::exec_misc(csi_t *csi, char *path)
+/* Return true if connected */
+bool socket_check(SOCKET sock)
 {
-	char	str[512],tmp[512],buf[1025],ch,*p,**pp,**pp1,**pp2;
+	char	ch;
+	int		i;
+	fd_set	socket_set;
+	struct	timeval tv;
+
+	FD_ZERO(&socket_set);
+	FD_SET(sock,&socket_set);
+
+	tv.tv_sec=0;
+	tv.tv_usec=0;
+
+	i=select(sock+1,&socket_set,NULL,NULL,&tv);
+	if(i==SOCKET_ERROR)
+		return(false);
+
+	if(i==0 || recv(sock,&ch,1,MSG_PEEK)==1) 
+		return(true);
+
+	return(false);
+}
+
+int sbbs_t::exec_misc(csi_t* csi, char *path)
+{
+	char	str[512],tmp[512],rsp[512],buf[1025],ch,*p,**pp,**pp1,**pp2;
 	ushort	w;
 	uint 	i,j;
 	long	l,*lp,*lp1,*lp2;
@@ -609,6 +633,25 @@ int sbbs_t::exec_misc(csi_t *csi, char *path)
 							*lp^=l;
 							break; }
 					return(0);
+				case COMPARE_ANY_BITS: 
+				case COMPARE_ALL_BITS:
+					i=*(csi->ip-1);
+					lp=getintvar(csi,*(long *)csi->ip);
+					csi->ip+=4;
+					l=*(long *)csi->ip;
+					csi->ip+=4;
+					csi->logic=LOGIC_FALSE;
+					if(!lp)
+						return(0);
+
+					if(i==COMPARE_ANY_BITS) {
+						if(((*lp)&l)!=0)
+							csi->logic=LOGIC_TRUE;
+					} else {
+						if(((*lp)&l)==l)
+							csi->logic=LOGIC_TRUE;
+					}
+					return(0);
 				case ADD_INT_VARS:
 				case SUB_INT_VARS:
 				case MUL_INT_VARS:
@@ -832,6 +875,27 @@ int sbbs_t::exec_misc(csi_t *csi, char *path)
 						memmove(*pp,*pp+i,strlen(*pp)+1);
 					return(0);
 
+				case SHIFT_TO_FIRST_CHAR:
+				case SHIFT_TO_LAST_CHAR:
+					i=*(csi->ip-1);
+					pp=getstrvar(csi,*(long *)csi->ip);
+					csi->ip+=4;
+					ch=*(csi->ip++);
+					csi->logic=LOGIC_FALSE;
+					if(!pp || !*pp)
+						return(0);
+					if(i==SHIFT_TO_FIRST_CHAR)
+						p=strchr(*pp,ch);
+					else	/* _TO_LAST_CHAR */
+						p=strrchr(*pp,ch);
+					if(p==NULL)
+						return(0);
+					csi->logic=LOGIC_TRUE;
+					i=p-*pp;
+					if(i>0)
+						memmove(*pp,*pp+i,strlen(p)+1);
+					return(0);
+
 				case CHKFILE_VAR:
 					pp=getstrvar(csi,*(long *)csi->ip);
 					csi->ip+=4;
@@ -873,6 +937,19 @@ int sbbs_t::exec_misc(csi_t *csi, char *path)
 					while(*(csi->ip++));	/* Find NULL */
 					telnet_gate(str,l);
 					return(0);
+				case COPY_CHAR:
+					pp=getstrvar(csi,*(long *)csi->ip);
+					if(pp==NULL)
+						lp=getintvar(csi,*(long *)csi->ip);
+					csi->ip+=4;
+
+					if(pp==NULL && lp!=NULL)
+						*lp=csi->cmd;
+					else if(pp!=NULL) {
+						sprintf(tmp,"%c",csi->cmd);
+						*pp=copystrvar(csi,*pp,tmp);
+					}
+					return(0);
 				case COPY_FIRST_CHAR:
 					lp=getintvar(csi,*(long *)csi->ip);
 					csi->ip+=4;
@@ -984,7 +1061,9 @@ int sbbs_t::exec_misc(csi_t *csi, char *path)
 						csi->logic=fclose((FILE *)*lp);
 						for(i=0;i<csi->files;i++)
 							if(csi->file[i]==(FILE *)*lp)
-								csi->file[i]=0; }
+								csi->file[i]=0; 
+						*lp=0;
+					}
 					else
 						csi->logic=LOGIC_FALSE;
 					return(0);
@@ -1014,8 +1093,8 @@ int sbbs_t::exec_misc(csi_t *csi, char *path)
 						if(!vp)
 							return(0);
 						i=*(short *)vp; }
-					if(i>1024)
-						i=1024;
+					if(i>sizeof(buf)-1)
+						i=sizeof(buf)-1;
 					if(!lp1 || !(*lp1) || (!pp && !lp2))
 						return(0);
 					if(pp) {
@@ -1048,7 +1127,7 @@ int sbbs_t::exec_misc(csi_t *csi, char *path)
 					if(!lp1 || !(*lp1) || feof((FILE *)*lp1) || (!pp && !lp2))
 						return(0);
 					csi->logic=LOGIC_TRUE;
-					for(i=0;i<1024 /* && !eof(*lp1) removed 1/23/96 */;i++) {
+					for(i=0;i<sizeof(buf)-1;i++) {
 						if(!fread(buf+i,1,1,(FILE *)*lp1))
 							break;
 						if(*(buf+i)==LF) {
@@ -1081,8 +1160,8 @@ int sbbs_t::exec_misc(csi_t *csi, char *path)
 						if(!vp)
 							return(0);
 						i=*(short *)vp; }
-					if(i>1024)
-						i=1024;
+					if(i>sizeof(buf)-1)
+						i=sizeof(buf)-1;
 					if(!lp1 || !(*lp1) || (!pp && !lp2) || (pp && !*pp))
 						return(0);
 					if(pp) {
@@ -1315,7 +1394,7 @@ int sbbs_t::exec_misc(csi_t *csi, char *path)
 					else
 						csi->logic=LOGIC_FALSE;
 					return(0);
-	#if 1	/* posix dirent */
+
 				case OPEN_DIR:
 					lp=getintvar(csi,*(long *)csi->ip);
 					csi->ip+=4;
@@ -1356,11 +1435,308 @@ int sbbs_t::exec_misc(csi_t *csi, char *path)
 					else
 						csi->logic=LOGIC_FALSE;
 					return(0);
-	#endif
+
 				default:
 					errormsg(WHERE,ERR_CHK,"fio sub-instruction",*(csi->ip-1));
 					return(0); }
 
+		case CS_NET_FUNCTION:
+			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_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);
+
+				default:
+					errormsg(WHERE,ERR_CHK,"net sub-instruction",*(csi->ip-1));
+					return(0); }
 
 		case CS_SWITCH:
 			lp=getintvar(csi,*(long *)csi->ip);
@@ -1479,3 +1855,259 @@ int sbbs_t::exec_misc(csi_t *csi, char *path)
 			errormsg(WHERE,ERR_CHK,"shell instruction",*(csi->ip-1));
 			return(0); }
 }
+
+/* FTP Command/Response function */
+bool sbbs_t::ftp_cmd(csi_t* csi, SOCKET sock, char* cmdsrc, char* rsp)
+{
+	char	cmd[512];
+	int		len;
+
+	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;
+
+			while(rd<500) {
+
+				if(!online)
+					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((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);
+}
+
+/* FTP Command/Response function */
+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);
+}