{\rtf0\ansi{\fonttbl\f0\fswiss Helvetica;\f1\fmodern Courier;\f2\ftech Symbol;}
\paperw11440
\paperh9000
\margl120
\margr120
{\colortbl;\red0\green0\blue0;}
\pard\tx520\tx1060\tx1600\tx2120\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f0\b0\i\ulnone\fs24\fc0\cf0 /*\
 * ftplib.c : Low-level routines for opening and using ftp connections.\
 *\
 * George Ferguson, ferguson@cs.rochester.edu, 29 Nov 1992.\
 *\
 * Based on ftplib by Amos Shapira (amoss@cs.huji.ac.il) and code\
 * from Alan Emtage (bajan@bunyip.com) and RFC 959.\
 *\
	This is an Object category that provides methods for most of the functions\
	defined in ftplib.c.  The methods are based on, or are direct translations of the\
	routines in ftplib.c.\
	Scott Stark, stark@superc.che.udel.edu, Sat Jan 30 22:58:50 EST 1993\
*/\

\b\i0 #import <appkit/appkit.h>\
#import "FTPObject.h"\
#import "Text_Console.h"\
\
#import <ctype.h>\
#import <fcntl.h>\
#import <errno.h>\
#import <libc.h>\
#import <netdb.h>\
#import <netinet/in.h>\
#import <sys/socket.h>\
#import <signal.h>\
#ifndef TRUE\
#	define TRUE 1\
#	define FALSE 0\
#endif\
\
@implementation Object(FTPDaemon)\
\
- (FTPInfoPtr) ftpOpen:(const char *) hostname textConsole: ftpConsole\
\{\

\pard\tx180\tx360\tx540\tx720\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f1\b0\i\fc1\cf1 /*
{{\NeXTHelpMarker909 \markername ftpOpen:;}
¬}\pard\tx180\tx360\tx540\tx720\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f1\b0\i\ulnone\fs24\fc1\cf1  --- 
\ul MethodDescription
\ulnone \
	
\i0\ul ReturnValue:
\i\ulnone  A new FTPInfo pointer if we connect, NULL otherwise;\
	
\i0\ul Description:
\i\ulnone  This method finds the in_addr for hostname and the\
		port number of the ftp service.  It then calls our 
\b ftpOpenAddress:port:
\b0 \
		method to establish a connection and allocate the FTPInfo struct.;\
	
\i0\ul Args:
\i\ulnone \
		hostname: The internet name ("superc.che.udel.edu") or dotted-decimal\
			address ("128.175.17.35");\
*/\

\pard\tx520\tx1060\tx1600\tx2120\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f0\b\i0\fc0\cf0 struct servent *serv;	//
{{\NeXTHelpLink1344 \markername ;\linkFilename CodeHelp.rtf;\linkMarkername servent;}
¬}\pard\tx520\tx1060\tx1600\tx2120\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f0\b\i0\ulnone\fs24\fc0\cf0 \
struct hostent *host;	//
{{\NeXTHelpLink1370 \markername ;\linkFilename CodeHelp.rtf;\linkMarkername hostent;}
¬}\pard\tx520\tx1060\tx1600\tx2120\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f0\b\i0\ulnone\fs24\fc0\cf0 \
unsigned short port;\
struct in_addr *hostaddr;\
FTPInfoPtr info;\
\

\f1\b0\fs20 [self debug: MAX_DEBUG method:_cmd, "trying \\"%s\\"", hostname];
\f0\b\fs24 \
	
\b0\i\fc1\cf1 /* Get the port number for the ftp service */
\b\i0\fc0\cf0 \
	serv = getservbyname("ftp","tcp");\
	if (serv == NULL )\
	\{\
	int response;\
		response = [self askYesNo:\
					"Cannot find service 'ftp/tbufPtr'\\nTry to open with default info?"\
					cancel: NO];\
		if( response != NX_ALERTDEFAULT )\
			return NULL;\
		port = htons((unsigned short) IPPORT_FTP);\
	\}\
	else\
		port = (unsigned short) serv->s_port;\

\f1\b0\fs20\fc1\cf1 [self debug: LOW_DEBUG method:_cmd, "ftp service port is %d", port];
\f0\b\fs24\fc0\cf0 \
\

\f1\b0\fs20\fc1\cf1 [self debug: MAX_DEBUG method:_cmd, "Getting address for host \\"%s\\"", hostname];
\f0\b\fs24\fc0\cf0 \
	
\b0\i\fc1\cf1 /* Get host address.  We first assume hostname is its internet name\
		and then try it as a dotted-decimal address */
\b\i0\fc0\cf0 \
	if ( (host = gethostbyname(hostname)) == NULL )\
	\{	
\b0\i /* Hope it is a dotted-decimal address */
\b\i0 \
		hostaddr = (struct in_addr *) inet_addr(hostname);\
		if( hostaddr == (struct in_addr *)-1 )\
			[self alert: "FTP Open" msg: "Cannot find address of host \\"%s\\""\
				btn1: "Ok" btn2: NULL btn3: NULL, hostname];\
		return NULL;\
		info = [self ftpOpenAddress: hostaddr port: port textConsole: ftpConsole];\
		return info;\
	\}\
\
	
\b0\i\fc1\cf1 /* If gethostbyname succeeeds, it fills in a list of addresses.\
		We'll try them until we get one that works, if any. */
\b\i0\fc0\cf0 \
	while( (hostaddr = (struct in_addr *)*host->h_addr_list) != NULL )\
	\{\
		info = [self ftpOpenAddress: hostaddr port: port textConsole: ftpConsole];\
		if( info != NULL )\
			return info;\
		host->h_addr_list ++;\
	\}\
\
	return NULL;\
\} 
\b0\i\fc1\cf1 // End ftpOpen:
\b\i0\fc0\cf0 \
\
static void ConnectAlarm(int sigNum)\
\{\
	return;\
\}\
\
- (FTPInfoPtr) ftpOpenAddress:(struct in_addr *) address port:(unsigned short) port\
	textConsole: ftpConsole\
\{\

\pard\tx180\tx360\tx540\tx720\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f1\b0\i\fc1\cf1 /*
{{\NeXTHelpMarker3107 \markername ftpOpenAddress:port:;}
¬}\pard\tx180\tx360\tx540\tx720\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f1\b0\i\ulnone\fs24\fc1\cf1  --- 
\ul MethodDescription
\ulnone \
	
\i0\ul ReturnValue:
\i\ulnone  A new FTPInfo pointer if we connect, NULL otherwise;\
	
\i0\ul Description:
\i\ulnone  This method performs the necessary to establish\
		the control connection to the remote ftpd specified by the\
		address and port arg, socket() & connect(). It also allocates\
		and initializes a FTPInfo struct.;\
	
\i0\ul Args:
\i\ulnone \
		address: The network address of the ftp host to connect to;\
		port: The port number of the ftp service;\
*/\

\pard\tx520\tx1060\tx1600\tx2120\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f0\b\i0\fc0\cf0 struct sockaddr_in addr;	//
{{\NeXTHelpLink3565 \markername ;\linkFilename CodeHelp.rtf;\linkMarkername sockaddr_in;}
¬}\pard\tx520\tx1060\tx1600\tx2120\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f0\b\i0\ulnone\fs24\fc0\cf0 \
int addrlen, ftpCode;\
int fd_flags,  rtn_val, (*signal_handler)();\
FTPInfoPtr info;\
\

\f1\b0\fs20 [self debug: MAX_DEBUG method:_cmd, "ftpOpenAddress: address %d.%d.%d.%d, port %d",\
	((unsigned char *)address)[0],((unsigned char *)address)[1],\
	((unsigned char *)address)[2],((unsigned char *)address)[3],\
	(unsigned int)port];
\f0\b\fs24 \
	
\b0\i\fc1\cf1 /* Allocate the connection structure */
\b\i0\fc0\cf0 \
	info = [self malloc: sizeof(FTPInfo)];\
	info->ftpConsole = ftpConsole;\
\
	
\b0\i\fc1\cf1 /* Open a socket specifying we want to use the Internet TCP protocol 
{{\NeXTHelpLink4066 \markername ;\linkFilename CodeHelp.rtf;\linkMarkername socket;}
¬}\pard\tx520\tx1060\tx1600\tx2120\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f0\b0\i\ulnone\fs24\fc1\cf1 */
\b\i0\fc0\cf0 \
	info->socket_fd = socket(AF_INET, SOCK_STREAM, 0);\
	if ( info->socket_fd < 0)\
	\{\
		[self systemErr: "Failed to allocate FTP socket" method:_cmd];\
		[self free: info];\
		return NULL;\
	\}\

\f1\b0\fs20 [self debug: MAX_DEBUG method:_cmd, "socket is %d", info->socket_fd];
\f0\b\fs24 \
\
	
\b0\i\fc1\cf1 /* Fill in the socket address infomation */
\b\i0\fc0\cf0 \
	bzero((char *)&addr,sizeof(struct sockaddr_in));\
	addr.sin_family = AF_INET;	// Internet protocol\
	addr.sin_port = port;			// The port for the connection\
	addr.sin_addr = *address;		// The address of the server host\
\
	
\b0\i\fc1\cf1 /* Establish a connection with the remote ftp server using our socket 
{{\NeXTHelpLink4661 \markername ;\linkFilename CodeHelp.rtf;\linkMarkername connect;}
¬}\pard\tx520\tx1060\tx1600\tx2120\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f0\b0\i\ulnone\fs24\fc1\cf1 */
\b\i0\fc0\cf0 \
	info->connectTimedOut = NO;\
	signal_handler =  signal(SIGALRM, ConnectAlarm);\
	alarm(30);\
	rtn_val = connect(info->socket_fd, (struct sockaddr *)&addr,\
		sizeof(struct sockaddr_in));\
	alarm(0);\
	signal(SIGALRM, signal_handler);\
	if( rtn_val < 0 )\
	\{\
		if( info->connectTimedOut == YES )\
			errno = ETIMEDOUT;	
\b0\i\fc1\cf1 // Fake a ETIMEDOUT error
\b\i0\fc0\cf0 \
		[self systemErr: "Failed to connect to remote ftp server" method:_cmd];\
		close(info->socket_fd);\
		[self free: info];\
		return NULL;\
	\}\

\f1\b0\fs20\fc1\cf1 [self debug: MAX_DEBUG method:_cmd, "ftpOpenAddress: connect() returned ok"];
\f0\b\fs24\fc0\cf0 \
\
	
\b0\i\fc1\cf1 /* Get the local address and process information for the socket */
\b\i0\fc0\cf0 \
	addrlen = sizeof(struct sockaddr_in);\
	rtn_val = getsockname(info->socket_fd,(struct sockaddr *)&(info->addr),\
		&addrlen);\
	if( rtn_val < 0 )\
	\{\
		[self systemErr: "Failed to get address of ftp connection" method:_cmd];\
		close(info->socket_fd);\
		[self free: info];\
		return NULL;\
	\}\

\f1\b0\fs20\fc1\cf1 [self debug: MAX_DEBUG method:_cmd, "getsockname() returned addr %d.%d.%d.%d",\
	    ((unsigned char *)&(info->addr.sin_addr))[0],\
	    ((unsigned char *)&(info->addr.sin_addr))[1],\
	    ((unsigned char *)&(info->addr.sin_addr))[2],\
	    ((unsigned char *)&(info->addr.sin_addr))[3]];
\f0\b\fs24\fc0\cf0 \
\
	
\b0\i\fc1\cf1 /* Turn on the non-blocking flag of the socket descriptor.  I do\
		this because I had problems with blocking on result output\
		from the remote ftpd's in ftpGetReply.  I don't think I\
		need to do this anymore, but it does not hurt anything. */
\b\i0\fc0\cf0 \
	fd_flags = fcntl(info->socket_fd, F_GETFL, 0);\
	if( fd_flags < 0 )\
	\{\
		[self systemErr: "Failed to get socket flags" method:_cmd];\
		close(info->socket_fd);\
		[self free: info];\
		return NULL;\
	\}\
	fd_flags |= O_NDELAY;\
	rtn_val = fcntl(info->socket_fd, F_SETFL, fd_flags);\
	if( rtn_val < 0 )\
	\{\
		[self systemErr: "Failed to set socket flags" method:_cmd];\
		close(info->socket_fd);\
		[self free: info];\
		return NULL;\
	\}\
\
	
\b0\i\fc1\cf1 /* Open a stdio stream for output commands on the \
		control connection. I do this for output because it\
		simplifies sending commands. */
\b\i0\fc0\cf0 \
	info->ftpOut = fdopen(info->socket_fd, "w");\
	if ( info->ftpOut == NULL )\
	\{\
		[self systemErr: "Failed to open stdio stream on socket_fd" method:_cmd];\
		close(info->socket_fd);\
		[self free: info];\
		return NULL;\
	\}\
	
\b0\i\fc1\cf1 /* Turn on line oriented buffering */
\b\i0\fc0\cf0 \
	setvbuf(info->ftpOut, NULL, _IOLBF, 128);\
\
	
\b0\i\fc1\cf1 /* Read the startup message from the server, fail if it's not an INFO\
		of SUCCESS message */
\b\i0\fc0\cf0 \
	ftpCode = [self ftpReplyCode: info];\
	if( FTP_REPLY_ERR(ftpCode) || ftpCode == 0 )\
	\{\
		[self alertOk: "Failed to get initial reply" quit: NO];\
		fclose(info->ftpOut);\
		close(info->socket_fd);\
		[self free: info];\
		return NULL;\
	\}\
\
	
\b0\i\fc1\cf1 /* Finish other initializations */
\b\i0\fc0\cf0 \
	info->code = 0;\
	info->port_socket = -1;\
	info->data_fd = -1;\
	info->transferType = TYPE_I;\
	info->removeCR = 1;\
	info->connectTimedOut = NO;\
\

\f1\b0\fs20 [self debug: MAX_DEBUG method:_cmd, "Created FTPInfoPtr %p", info];
\f0\b\fs24 \
	return info;\
\} 
\b0\i\fc1\cf1 // End ftpOpenAddress: port:
\b\i0\fc0\cf0 \
\
- ftpLogin:(FTPInfoPtr) info user:(char *) user passwd:(char *) passwd\
\{\

\pard\tx180\tx360\tx540\tx720\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f1\b0\i\fc1\cf1 /*
{{\NeXTHelpMarker7671 \markername ftpLogin:user:passwd:;}
¬}\pard\tx180\tx360\tx540\tx720\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f1\b0\i\ulnone\fs24\fc1\cf1  --- 
\ul MethodDescription
\ulnone \
	
\i0\ul ReturnValue:
\i\ulnone  self we login, nil otherwise;\
	
\i0\ul Description:
\i\ulnone  This method issues the USER and PASS commands\
		to the remote ftpd to initiate a session;\
	
\i0\ul Args:
\i\ulnone \
		info: The connection info for the ftp host;\
		user: The user name to supply with the USER command;\
		passwd: The password to supply with the PASS command;\
*/\

\pard\tx520\tx1060\tx1600\tx2120\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f0\b\i0\fc0\cf0 int ftpCode;\
\

\f1\b0\fs20 [self debug: MAX_DEBUG method:_cmd, "logging in as \\"%s\\", passwd \\"%s\\"", user, passwd];
\f0\b\fs24 \
\
	
\b0\i\fc1\cf1 /* Send the USER command */
\b\i0\fc0\cf0 \
	ftpCode = [self ftpSendCmd: info cmd: "US
\fc1\cf1 ER" 
\pard\tx180\tx360\tx540\tx720\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\fc1\cf1 arg
\pard\tx520\tx1060\tx1600\tx2120\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\fc1\cf1 : user
\fc0\cf0 ];\
	switch( ftpCode )\
	\{\
		case FTP_LOGIN_OK:				
\b0\i\fc1\cf1 // Logged in without passwd
\b\i0\fc0\cf0 \
			return self;\
		case FTP_LOGIN_NEED_PASSWD:	
\b0\i\fc1\cf1 // Must send passwd
\b\i0\fc0\cf0 \
			break;\
		default:							
\b0\i\fc1\cf1 // Error
\b\i0\fc0\cf0 \
			[self ftpError: info];\
			return nil;\
	\}\
\
	
\b0\i\fc1\cf1 /* If we get here, we need to send passwd */
\b\i0\fc0\cf0 \
	ftpCode = [self ftpSendCmd: info cmd: "PA
\fc1\cf1 SS" 
\pard\tx180\tx360\tx540\tx720\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\fc1\cf1 arg
\pard\tx520\tx1060\tx1600\tx2120\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\fc1\cf1 : passw
\fc0\cf0 d];\
	switch( ftpCode )\
	\{\
		case FTP_LOGIN_OK:		
\b0\i\fc1\cf1 // Logged in
\b\i0\fc0\cf0 \
			return self;\
		default:					
\b0\i\fc1\cf1 // Error
\b\i0\fc0\cf0 \
			[self ftpError: info];\
			return nil;\
	\}\
\
	return nil;\
\} 
\b0\i // End ftpLogin: user: passwd:
\b\i0 \
\
- (int) ftpSendCmd:(FTPInfoPtr) info cmd:(char *) com
\fc1\cf1 mand 
\pard\tx180\tx360\tx540\tx720\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\fc1\cf1 arg
\pard\tx520\tx1060\tx1600\tx2120\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\fc1\cf1 :(char *) 
\pard\tx180\tx360\tx540\tx720\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\fc1\cf1 arg
\pard\tx520\tx1060\tx1600\tx2120\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\fc0\cf0 \
\{\

\pard\tx180\tx360\tx540\tx720\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f1\b0\i\fc1\cf1 /*
{{\NeXTHelpMarker8797 \markername ftpSendCmd:cmd:arg:;}
¬}\pard\tx180\tx360\tx540\tx720\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f1\b0\i\ulnone\fs24\fc1\cf1  --- 
\ul MethodDescription
\ulnone \
	
\i0\ul ReturnValue:
\i\ulnone  The ftp code sent by the remote server,\
		or -1 if command or info are NULL;\
	
\i0\ul Description:
\i\ulnone  Send the string in command to the server over\
		the control connection and return the result of 
\b ftpReplyCode:
\b0 ;\
	
\i0\ul Args:
\i\ulnone \
		info: The connection info for the ftp host;\
		command: The ftp command to send;\
		arg: A single, possible NULL argument to command;\
*/\

\pard\tx520\tx1060\tx1600\tx2120\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f0\b\i0\fc0\cf0 char *format = "%s %s\\r\\n";\
int code;\
\
	if ( info == NULL || command == NULL || ! *command )\
		return -1;\
\
	
\fc1\cf1 if( 
\pard\tx180\tx360\tx540\tx720\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\fc1\cf1 arg
\pard\tx520\tx1060\tx1600\tx2120\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\fc1\cf1  == NULL )\
		format = "%s\\r\\n";\
	fprintf(info->ftpOut, format, command, 
\pard\tx180\tx360\tx540\tx720\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\fc1\cf1 arg
\pard\tx520\tx1060\tx1600\tx2120\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\fc1\cf1 );\
	code = [self ftpReplyC
\fc0\cf0 ode: info];\
\
	return code;\
\} 
\b0\i // End ftpSendCmd cmd: 
\pard\tx180\tx360\tx540\tx720\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f1\fc1\cf1 arg
\pard\tx520\tx1060\tx1600\tx2120\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f0\fc0\cf0 :
\b\i0 \
\
- (int) ftpPort:(FTPInfoPtr) info\
\{\

\pard\tx180\tx360\tx540\tx720\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f1\b0\i\fc1\cf1 /*
{{\NeXTHelpMarker9493 \markername ftpPort:;}
¬}\pard\tx180\tx360\tx540\tx720\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f1\b0\i\ulnone\fs24\fc1\cf1  --- 
\ul MethodDescription
\ulnone \
	
\i0\ul ReturnValue:
\i\ulnone  The ftp code sent by the remote server,\
		or -1 if info is NULL, or we fail on some system command;\
	
\i0\ul Description:
\i\ulnone  Performs the PORT command. The new port is saved\
		in info->
\pard\tx520\tx1060\tx1600\tx2120\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\fc1\cf1 port_socket
\pard\tx180\tx360\tx540\tx720\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\fc1\cf1 ;\
	
\i0\ul Args:
\i\ulnone \
		info: The connection info for the ftp host;\
*/\

\pard\tx520\tx1060\tx1600\tx2120\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f0\b\i0\fc0\cf0 int port_socket;\
struct sockaddr_in addr;\
int addrlen, ftpCode;\
\
	if ( info == NULL )\
		return -1;\
	if ( info->port_socket >= 0 )\
		return FTP_COMMAND_OK;
\b0\i\fc1\cf1 	// A port is already assigned
\b\i0\fc0\cf0 \
\
	
\b0\i /* Open a socket for the reply */
\b\i0 \
	port_socket = socket(AF_INET, SOCK_STREAM, 0);\
	if ( port_socket < 0)\
	\{\
		[self systemErr: "Failed to open socket for PORT" method:_cmd];\
		return 
\f2\b0 -
\f0\b 1;\
	\}\

\f1\b0\fs20 [self debug: MAX_DEBUG method:_cmd, "socket() returned %d", port_socket];
\f0\b\fs24 \
\
	
\b0\i\fc1\cf1 /* Bind the socket to our local address and process */
\b\i0\fc0\cf0 \
	addr = info->addr;\
	addr.sin_port = 0;\
	addrlen = sizeof(struct sockaddr_in);\
	if( bind(port_socket,(struct sockaddr *)&addr, addrlen) < 0 )\
	\{\
		[self systemErr: "Failed to bind socket for PORT" method:_cmd];\
		return 
\f2\b0 -
\f0\b 1;\
	\}\
\
	
\b0\i\fc1\cf1 /* Indicate we are willing to accept up to one connection on\
		port we just bound */
\b\i0\fc0\cf0 \
	if( listen(port_socket, 1) < 0 )\
	\{\
		[self systemErr: "Failed to bind socket for PORT" method:_cmd];\
		return 
\f2\b0 -
\f0\b 1;\
	\}\
\
	
\b0\i\fc1\cf1 /* Get the local address and process for the socket */
\b\i0\fc0\cf0 \
	if(getsockname(port_socket, (struct sockaddr *)&addr, &addrlen) < 0 )\
	\{\
		[self systemErr: "Failed to bind socket for PORT" method:_cmd];\
		close(port_socket);\
		return 
\f2\b0 -
\f0\b 1;\
	\}\

\f1\b0\fs20 [self debug: MAX_DEBUG method:_cmd, "PORT %d,%d,%d,%d,%d,%d",\
		(int)((unsigned char *)&addr.sin_addr.s_addr)[0],\
		(int)((unsigned char *)&addr.sin_addr.s_addr)[1],\
		(int)((unsigned char *)&addr.sin_addr.s_addr)[2],\
		(int)((unsigned char *)&addr.sin_addr.s_addr)[3],\
		(int)((unsigned char *)&addr.sin_port)[0],\
		(int)((unsigned char *)&addr.sin_port)[1]];
\f0\b\fs24 \
\
	
\b0\i\fc1\cf1 /* Send the PORT command */
\b\i0\fc0\cf0 \
	fprintf(info->ftpOut, "PORT %d,%d,%d,%d,%d,%d\\r\\n",\
		(int)((unsigned char *)&addr.sin_addr.s_addr)[0],\
		(int)((unsigned char *)&addr.sin_addr.s_addr)[1],\
		(int)((unsigned char *)&addr.sin_addr.s_addr)[2],\
		(int)((unsigned char *)&addr.sin_addr.s_addr)[3],\
		(int)((unsigned char *)&addr.sin_port)[0],\
		(int)((unsigned char *)&addr.sin_port)[1]);\
\
	
\b0\i\fc1\cf1 /* Check the ftpd server response */ 
\b\i0\fc0\cf0 \
	ftpCode = [self ftpReplyCode: info];\
	if( !FTP_REPLY_COMPLETE(ftpCode) )\
	\{	\
		[self ftpError: info];\
		close(port_socket);\
		return 
\f2\b0 -
\f0\b 1;\
	\}\
\
	
\b0\i\fc1\cf1 /* Success, save the socket */
\b\i0\fc0\cf0 \
	info->port_socket = port_socket;\
\
	return info->code;\
\} 
\b0\i // End ftpPort:
\b\i0 \
\
- (int) ftpOpenDataConn:(FTPInfoPtr) info cmd:(char *) command arg:(char *) arg\
\{\

\pard\tx180\tx360\tx540\tx720\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f1\b0\i\fc1\cf1 /*
{{\NeXTHelpMarker12065 \markername ftpOpenDataConn:cmd:arg:;}
¬}\pard\tx180\tx360\tx540\tx720\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f1\b0\i\ulnone\fs24\fc1\cf1  --- 
\ul MethodDescription
\ulnone \
	
\i0\ul ReturnValue:
\i\ulnone  The ftp code sent by the remote server,\
		or -1 if info is NULL, or we fail on some system command;\
	
\i0\ul Description:
\i\ulnone  Open data connection for operation CMD on file\
		FILENAME. Returns the fd of the data connection\
		(also stored in info->data_fd) or -1 if an error occurs;\
	
\i0\ul Args:
\i\ulnone \
		info: The connection info for the ftp host;\
		command: The command requiring the data connection;\
		arg: A single or NULL arg to command;\
*/\

\pard\tx520\tx1060\tx1600\tx2120\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f0\b\i0\fc0\cf0 struct sockaddr_in addr;\
int data_fd, addrlen, ftpCode;\
\

\f1\b0\fs20 [self debug: MAX_DEBUG method:_cmd, "opening data connection for \\"%s %s\\"", command, arg];
\f0\b\fs24 \
\
	
\b0\i /* Get a PORT for the data connection */
\b\i0 \
	ftpCode = [self ftpPort: info];\
	if( !FTP_REPLY_COMPLETE(ftpCode) )\
		return -1;\
\
	
\b0\i\fc1\cf1 /* Send the command */
\b\i0\fc0\cf0 \
	if( arg != 0 && *arg != 0 )\
		fprintf(info->ftpOut,"%s %s\\r\\n", command, arg);\
	else if( command != NULL )\
		fprintf(info->ftpOut,"%s\\r\\n", command);\
	else\
		fprintf(info->ftpOut, "NOOP\\r\\n");\
\
	
\b0\i\fc1\cf1 /* Check the reply code */
\b\i0\fc0\cf0 \
	ftpCode = [self ftpReplyCode: info];\
	if( !FTP_REPLY_OK(ftpCode) )\
	\{\
		[self ftpError: info];\
		return -1;\
	\}\
\
	
\b0\i\fc1\cf1 /* Wait for the ftpd server connect to our data port */
\b\i0\fc0\cf0 \
	addrlen = sizeof(struct sockaddr_in);\
	data_fd = accept(info->port_socket, (struct sockaddr *)&addr, &addrlen);\
	if( data_fd < 0 )\
		[self systemErr: "Failed to accept server connection" method:_cmd];\
\
	close(info->port_socket);\
	info->port_socket = -1;\
	info->data_fd = data_fd;\
	if ( data_fd < 0 )\
		return -1;\
\

\f1\b0\fs20 [self debug: MAX_DEBUG method:_cmd, "returning data connection fd = %d", 
\fc1\cf1 data_fd
\fc0\cf0 ];
\f0\b\fs24 \
	return data_fd;\
\} 
\b0\i\fc1\cf1 // End ftpOpenDataConn: cmd: arg:
\b\i0\fc0\cf0 \
\
- (int) ftpCloseDataConn:(FTPInfoPtr) info\
\{\

\pard\tx180\tx360\tx540\tx720\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f1\b0\i\fc1\cf1 /*
{{\NeXTHelpMarker13714 \markername ftpCloseDataConn:;}
¬}\pard\tx180\tx360\tx540\tx720\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f1\b0\i\ulnone\fs24\fc1\cf1  --- 
\ul MethodDescription
\ulnone \
	
\i0\ul ReturnValue:
\i\ulnone  0 if successful, -1 on error;\
	
\i0\ul Description:
\i\ulnone  Close data connection info->data_fd;\
	
\i0\ul Args:
\i\ulnone \
		info: The connection info for the ftp host;\
*/\

\pard\tx520\tx1060\tx1600\tx2120\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f0\b\i0\fc0\cf0 int ftpCode;\
\

\f1\b0\fs20 [self debug: MAX_DEBUG method:_cmd, "closing fd = %d", info->data_fd];
\f0\b\fs24 \
	close(info->data_fd);\
	info->data_fd = -1;\
	ftpCode = [self ftpReplyCode: info];\

\f1\b0\fs20\fc1\cf1 [self debug: MAX_DEBUG method:_cmd, "final reply was %d", 
\f0\b\fs24\fc0\cf0 ftpCode
\f1\b0\fs20\fc1\cf1 ];
\f0\b\fs24\fc0\cf0 \
\
	if ( FTP_REPLY_OK(ftpCode) && ftpCode != 0 )\
		return 0;\
\
	[self ftpError: info];\
\
	return -1;\
\} 
\b0\i // End ftpCloseDataConn:
\b\i0 \
\
- (int) ftpReadDataConn:(FTPInfoPtr) info buffer:(char *) buffer length:(int) length\
\{\

\pard\tx180\tx360\tx540\tx720\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f1\b0\i\fc1\cf1 /*
{{\NeXTHelpMarker14338 \markername ftpReadDataConn:buffer:length:;}
¬}\pard\tx180\tx360\tx540\tx720\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f1\b0\i\ulnone\fs24\fc1\cf1  --- 
\ul MethodDescription
\ulnone \
	
\i0\ul ReturnValue:
\i\ulnone  The value from the 
\b read()
\b0  system call;\
	
\i0\ul Description:
\i\ulnone  Read from the data connection of f into buffer,\
		and return the number of bytes actually read(), not including\
		striped '\\r's;\
	
\i0\ul Args:
\i\ulnone \
		info: The connection info for the ftp host;\
		buffer: The char buffer we read into;\
		length: The maximum number of bytes to read;\
*/
\pard\tx520\tx1060\tx1600\tx2120\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f0\b\i0\fc0\cf0 \
int bytesRead;\
char *raw_buf, *stripped_buf;\
\
	errno = 0;\
	bytesRead = read(info->data_fd, buffer, length);\

\f1\b0\fs20 [self debug: SUPER_DEBUG method:_cmd, "nread = %d, errno = %d", bytesRead, errno];
\f0\b\fs24 \
\
	
\b0\i /* If the read fails, or we're in binary mode, or we shouldn't strip\
		CR's, then return, leaving errno alone */
\b\i0 \
	if( bytesRead <= 0 || info->transferType != TYPE_A || !info->removeCR )\
		return bytesRead;\
\
	
\b0\i\fc1\cf1 /* Otherwise run along the new data, copy everything but CR's */
\b\i0\fc0\cf0 \
	raw_buf = stripped_buf = buffer;\
	while( bytesRead -- > 0 )\
	\{\
		if ( *raw_buf != '\\r' )\
			*stripped_buf ++ = *raw_buf;\
		raw_buf ++;\
	\}\
\
	return (stripped_buf - buffer);\
\} 
\b0\i // End ftpReadDataConn: buffer: length:
\b\i0 \
\
- ftpQuit:(FTPInfoPtr) info\
\{\

\pard\tx180\tx360\tx540\tx720\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f1\b0\i\fc1\cf1 /*
{{\NeXTHelpMarker15415 \markername ftpQuit:;}
¬}\pard\tx180\tx360\tx540\tx720\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f1\b0\i\ulnone\fs24\fc1\cf1  --- 
\ul MethodDescription
\ulnone \
	
\i0\ul ReturnValue:
\i\ulnone  self;\
	
\i0\ul Description:
\i\ulnone  Send the QUIT command and invoke 
\b ftpClose:
\b0 ; \
	
\i0\ul Args:
\i\ulnone \
		info: The connection info for the ftp host;\
*/\

\pard\tx520\tx1060\tx1600\tx2120\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f0\b\i0\fc0\cf0 	if( ferror(info->ftpOut) == 0 )\
		fprintf(info->ftpOut, "QUIT\\r\\n");\
	[self ftpClose: info];\
\
	return self;\
\} 
\b0\i // End ftpQuit:
\b\i0 \
\
- ftpClose:(FTPInfoPtr) info\
\{\

\pard\tx180\tx360\tx540\tx720\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f1\b0\i\fc1\cf1 /*
{{\NeXTHelpMarker15735 \markername ftpClose:;}
¬}\pard\tx180\tx360\tx540\tx720\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f1\b0\i\ulnone\fs24\fc1\cf1  --- 
\ul MethodDescription
\ulnone \
	
\i0\ul ReturnValue:
\i\ulnone  self;\
	
\i0\ul Description:
\i\ulnone  Close every open thing in site and free\
		the FTPInfoPtr arg; \
	
\i0\ul Args:
\i\ulnone \
		info: The connection info for the ftp host;\
*/\

\pard\tx520\tx1060\tx1600\tx2120\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f0\b\i0\fc0\cf0 	if ( info == NULL )\
		return self;\
	if ( info->data_fd > 0)\
		close(info->data_fd);\
	fclose(info->ftpOut);\
	close(info->socket_fd);\
	free(info);\
\
	return self;\
\} 
\b0\i // End ftpClose:
\b\i0 \
\
- (int) ftpAbort:(FTPInfoPtr) info\
\{\

\pard\tx180\tx360\tx540\tx720\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f1\b0\i\fc1\cf1 /*
{{\NeXTHelpMarker16132 \markername ftpAbort:;}
¬}\pard\tx180\tx360\tx540\tx720\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f1\b0\i\ulnone\fs24\fc1\cf1  --- 
\ul MethodDescription
\ulnone \
	
\i0\ul ReturnValue:
\i\ulnone  The code from 
\b ftpReplyCode:
\b0 , or -1 on error;\
	
\i0\ul Description:
\i\ulnone  Send ABOR sequence and process response. This\
		routine will read and return the first reply, presumably a 426.\
		It is presumed that the routine that closes the data connection\
		will read the 226 that should follow. \
	
\i0\ul Args:
\i\ulnone \
		info: The connection info for the ftp host;\
*/\

\pard\tx520\tx1060\tx1600\tx2120\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f0\b\i0\fc0\cf0 char buf[3];\
int ftpCode, sendCount;\
\
	if ( info == NULL || info->data_fd < 0 )\
		return -1;\
\
	// Get the remote server's attention \
	buf[0] = IAC;\
	buf[1] = IP;\
	buf[2] = IAC;\
	sendCount = send(info->socket_fd, buf, sizeof(buf), MSG_OOB);\

\f1\b0\fs20\fc1\cf1 	if( sendCount == -1 )\
		[self debug: LOW_DEBUG method:_cmd, "send(MSG_OOB) failed, %s", strerror(errno)];\

\fc0\cf0 [self debug: MAX_DEBUG method:_cmd, "ABOR"];
\f0\b\fs24 \
	// Abort the current command\
	fprintf(info->ftpOut, "%cABOR\\r\\n", DM);\
	fflush(info->ftpOut);\
\
	ftpCode = [self ftpReplyCode: info];\
	if(  ftpCode == 552)	// NIC style of reply\
		ftpCode = [self ftpReplyCode: info];\
\
	return ftpCode;\
\} 
\b0\i // End ftpAbort:
\b\i0 \
\
- (int) ftpCwd:(FTPInfoPtr) info newDir:(char *) directory\
\{\

\pard\tx180\tx360\tx540\tx720\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f1\b0\i\fc1\cf1 /*
{{\NeXTHelpMarker17214 \markername ftpCwd:newDir:;}
¬}\pard\tx180\tx360\tx540\tx720\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f1\b0\i\ulnone\fs24\fc1\cf1  --- 
\ul MethodDescription
\ulnone \
	
\i0\ul ReturnValue:
\i\ulnone  0 if sucessful, -1 otherwise;\
	
\i0\ul Description:
\i\ulnone  Change remote working directory to directory.\
		Does one component of directory at a time. We currently only\
		understand UNIX pathnames (ie. components separated by '/'s).;\
	
\i0\ul Args:
\i\ulnone \
		info: The connection info for the ftp host;\
		directory: The directory to cd to. Can be a full or\
			relative pathname;\
*/\

\pard\tx520\tx1060\tx1600\tx2120\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f0\b\i0\fc0\cf0 char command[256];\
char *start,*end,*cp;\
int ftpCode;\
\

\f1\b0\fs20 [self debug: MAX_DEBUG method:_cmd, "CWD: %s", directory];
\f0\b\fs24 \
\
	if( directory == NULL || *directory == '\\0' )\
		return 0;		
\b0\i // That was easy
\b\i0 \
\
	start = end = directory;\
	while( *start )\
	\{\
		strcpy(command, "CWD ");\
		cp = command + 4;\
		do \{\
			*cp++ = *end++;\
		\} while( *end && *end != '/' );\
		*cp ++ = '\\0';\

\f1\b0\fs20 [self debug: MAX_DEBUG method:_cmd, "
\fc1\cf1 sending %s", command]
\fc0\cf0 ;
\f0\b\fs24 \
		ftpCode = [self ftpSendCmd: info cmd: command arg: NULL];\
		if ( !FTP_REPLY_OK(ftpCode) )\
		\{	/* Only report the error if it is not a file not found, 550 */\
			if( ftpCode != 550 )\
				[self ftpError: info];\
			return -1;\
		\}\
\
		if( *end )\
			end += 1;\
		start = end;\
	\}\
\
	return 0;\
\} 
\b0\i // End ftpCwd: newDir:
\b\i0 \
\
- (const char *) ftpPwd:(FTPInfoPtr) info\
\{\

\pard\tx180\tx360\tx540\tx720\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f1\b0\i\fc1\cf1 /* --- 
\ul MethodDescription
\ulnone \
	
\i0\ul ReturnValue:
\i\ulnone  The current directory on the remote ftp host, NULL on error;\
	
\i0\ul Description:
\i\ulnone  This method invokes the FTP PWD command and returns\
		a pointer to the return value;\
	
\i0\ul Args:
\i\ulnone \
			info: The connection info for the ftp host;\
*/\

\pard\tx520\tx1060\tx1600\tx2120\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f0\b\i0\fc0\cf0 static char pwd[MAXPATHLEN+1];\
char *tmpPtr;\
int ftpCode;\
\
	ftpCode = [self ftpSendCmd: info cmd: "PWD" arg: NULL];\
	if( ftpCode == -1 )\
		return NULL;\
	
\b0\i /* Get a pointer to the "/current/dir" string */
\b\i0 \
	tmpPtr = strchr(info->replyBuffer, '"');\
	if( tmpPtr == NULL )\
		return NULL;\
	tmpPtr ++;\
	strcpy(pwd, tmpPtr);\
	tmpPtr = strchr(pwd, '"');\
	if( tmpPtr == NULL )\
		return NULL;\
	*tmpPtr = '\\0';\
\
	return pwd;\
\} 
\b0\i // End ftpPwd:
\b\i0 \
\
- (int) ftpType:(FTPInfoPtr) info type:(char *) type\
\{\

\pard\tx180\tx360\tx540\tx720\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f1\b0\i\fc1\cf1 /*
{{\NeXTHelpMarker19122 \markername ftpType:type:;}
¬}\pard\tx180\tx360\tx540\tx720\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f1\b0\i\ulnone\fs24\fc1\cf1  --- 
\ul MethodDescription
\ulnone \
	
\i0\ul ReturnValue:
\i\ulnone  The ftp code sent by the remote server;\
	
\i0\ul Description:
\i\ulnone  Change the TYPE of the data connection\
		to that specified by the type string.  We only care\
		about "ascii" and "binary". Actually any string other\
		than "ascii" is sent as "binary".;\
	
\i0\ul Args:
\i\ulnone \
		info: The connection info for the ftp host;\
		type: Either the string "ascii" or "binary";\
*/\

\pard\tx520\tx1060\tx1600\tx2120\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f0\b\i0\fc0\cf0 int ftpCode;\
\
	if( strcasecmp(type,"ascii") == 0 )\
	\{\
		info->transferType = TYPE_A;\
		fprintf(info->ftpOut, "TYPE A\\r\\n");\
	\}\
	else		
\b0\i // default to binary
\b\i0 \
	\{\
		info->transferType = TYPE_I;\
		fprintf(info->ftpOut, "TYPE I\\r\\n");\
	\}\
	ftpCode = [self ftpReplyCode: info];\
\
	return ftpCode;\
\} 
\b0\i // End ftpType: type:
\b\i0 \
\

\b0\i /* Main command connection parser.\
 * Reads and parses reply messages from the server, handles TELNET commands\
 * and translate codes in the reply prefix into integers. Returns the reply\
 * code (also stored info->code) and sends the text of the reply to info->ftpConsole.\
 */\

\b\i0 #define REPLY_CODE		1\
#define REPLY_CONT		2\
#define REPLY_LAST		3\
#define REPLY_MORE	4\
#define REPLY_CHCK		5\
\
- (int) ftpReplyCode:(FTPInfoPtr) info\
\{\

\pard\tx180\tx360\tx540\tx720\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f1\b0\i\fc1\cf1 /*
{{\NeXTHelpMarker20245 \markername ftpReplyCode:;}
¬}\pard\tx180\tx360\tx540\tx720\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f1\b0\i\ulnone\fs24\fc1\cf1  --- 
\ul MethodDescription
\ulnone \
	
\i0\ul ReturnValue:
\i\ulnone  The ftp code sent by the remote server,\
		0 if we can not find one, and -1 if info is NULL;\
	
\i0\ul Description:
\i\ulnone  This method parses the input side of the control\
		connection for next line beginning with a 3 digit code.\
		If a code is found, it is returned. A value of 0 is returned\
		is we fail to locate such a line.;\
	
\i0\ul Args:
\i\ulnone \
		info: The connection info for the ftp host;\
*/\

\pard\tx520\tx1060\tx1600\tx2120\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f0\b\i0\fc0\cf0 int tmpcode = 0, status, i_val, nread;\
static char c, buffer[BUFSIZ], *bufPtr;\
static int recursive = 0;\
\
	if( info == NULL )\
		return -1;\
\
	
\b0\i /* If we have not invoked ourselves, initialize things */
\b\i0 \
	if( recursive == 0 )\
	\{\
		info->code = 0;\
		bufPtr = buffer;\
		*bufPtr = '\\0';\
		info->replyBuffer = buffer;\
	\}\
\
	
\b0\i /* We begin looking for a 3 digit reply code */
\b\i0 \
	status = REPLY_CODE;\
	while( 1 )\
	\{\
		errno = 0;\
		nread = read(info->socket_fd, &c, 1);\
		i_val = c;\
		if ( nread <= 0 )\
		\{	
\b0\i /* read() failed, check for blocking error */
\b\i0 \
			if( errno == EWOULDBLOCK )\
				continue;\
			else\
				info->code = FTP_SERVICE_UNAVAILABLE;\

\f1\b0\fs20\fc1\cf1 [self debug: MAX_DEBUG method:_cmd, "returning %d, errno = %d", info->code, errno];
\f0\b\fs24\fc0\cf0 \
			return info->code;\
		\}\
		else if( i_val == IAC)\
		\{	
\b0\i /* Telnet IAC, Check the telnet rfc for what this stuff is */
\b\i0 \
			read(info->socket_fd, &c, 1);\
			read(info->socket_fd, &c, 1);\
			i_val = c;\
			switch ( i_val )\
			\{\
				case WILL:\
				case WONT:\
					c = DONT;\
					break;\
				case DO:\
				case DONT:\
					c = WONT;\
					break;\
				default:\
					continue;\
			\}\
			putc(IAC, info->ftpOut);\
			putc(c, info->ftpOut);\
			read(info->socket_fd, &c, 1);\
			putc(c, info->ftpOut);\
			fflush(info->ftpOut);\
			continue;\
		\} 
\b0\i // End else if( i_val == IAC )
\b\i0 \
\
		
\b0\i /* Otherwise we read a "regular" character */
\b\i0 \
		[info->ftpConsole bufferText: c];\
		switch ( status ) \
		\{\
			case REPLY_CODE:\

\f1\b0\fs20\fc1\cf1 [self debug: SUPER_DEBUG method:_cmd, "CODE: c: '%c' (%d)", c, i_val];
\f0\b\fs24\fc0\cf0 \
		if ( !isdigit(c) )\
		\{	
\b0\i /* Try to find a line with a reply code. I added this\
				for responses like one gets when you issue a LIST\
				command with a non-existent file, e.g., if issue 'LIST no_file'\
					"no_file not found\
					226 Transfer complete" \
				*/
\b\i0 \
		int rtn_val, last_char;\
			errno = 0;\
			*bufPtr ++ = last_char = c;\
			if( (bufPtr - buffer) >= BUFSIZ-1 )\
			\{	
\b0\i /* Wrap the buffer */
\b\i0 \
				bufPtr = buffer;\
				*bufPtr = '\\0';\
			\}\
			while( read(info->socket_fd, &c, 1) > 0 )\
			\{	
\b0\i /* Read until we have seen a newline followed\
					by a digit.  We then recursively call ourselves to\
					read the code */
\b\i0 \
				[info->ftpConsole bufferText: c];\
				if( last_char == '\\n' && isdigit(c) )\
				\{	
\b0\i /* Recursive call to parse the code */
\b\i0 \
					recursive ++;\
					info->code = info->code * 10 + c - '0';\
					rtn_val = [self ftpReplyCode: info];\
					recursive --;\
					return rtn_val;\
				\}\
				last_char = c;\
			\}\
			return -1;\
		\} 
\b0\i\fc1\cf1 // End if( !isdigit(c) )
\b\i0\fc0\cf0 \
\
		
\b0\i\fc1\cf1 /* We have a digit. If we have read three digits then go to\
			the next step */
\b\i0\fc0\cf0 \
		info->code = info->code * 10 + c - '0';\
		if (info->code >= 100)\
			status = REPLY_CONT;\
		continue;\
\
		
\b0\i\fc1\cf1 /* we reach here after we finished reading the code or when we\
			struck a line beginning with at least three digits, check if\
			this is the last line of the reply */
\b\i0\fc0\cf0 \
		case REPLY_CONT:\
			if (c == '-')\
				status = REPLY_MORE;\
			else\
				status = REPLY_LAST;\
			continue;\
\
		case REPLY_LAST:\
			if ( c == '\\n' )\
			\{	
\b0\i\fc1\cf1 /* Have read though the last line, return the parsed code */
\b\i0\fc0\cf0 \
				*bufPtr = '\\0';\

\f1\b0\fs20 [self debug: MAX_DEBUG method:_cmd, "LAST: returning %d", info->code];
\f0\b\fs24 \
				return info->code;\
			\}\
			*bufPtr ++ = c;\
			if( (bufPtr - buffer) >= BUFSIZ-1 )\
			\{	
\b0\i /* Wrap the buffer */
\b\i0 \
				bufPtr = buffer;\
				*bufPtr = '\\0';\
			\}\
			continue;\
\
		case REPLY_MORE:\
			if ( c == '\\n' )\
			\{	
\b0\i\fc1\cf1 /* Have read though a line, check the next line for the end code */
\b\i0\fc0\cf0 \
				tmpcode = 0;\
				status = REPLY_CHCK;\
			\}\
			continue;\
\
		case REPLY_CHCK:\
			if ( !isdigit(c) )\
			\{	
\b0\i\fc1\cf1 /* No code here, read the rest of the line */
\b\i0\fc0\cf0 \
				status = REPLY_MORE;\
				continue;\
			\}\
			
\b0\i\fc1\cf1 /* See if we can read a matching code */
\b\i0\fc0\cf0 \
			tmpcode = tmpcode * 10 + c - '0';\
			if (tmpcode < 100)\
				continue;				
\b0\i // Keep reading
\b\i0 \
			
\b0\i /* See if the code matches that which preceeded the \
				continuation line.  If it does not we must keep searching */
\b\i0 \
			if ( tmpcode != info->code )\
				status = REPLY_MORE;	
\b0\i // Start the search over
\b\i0 \
			else\
				status = REPLY_CONT;	
\b0\i // Read the rest of the line prior to return
\b\i0 \
			continue;\
\
		\} 
\b0\i // End switch ( status ) 
\b\i0 \
	\} 
\b0\i // End while( 1 )
\b\i0 \
\
\} 
\b0\i // End ftpReplyCode:
\b\i0 \
\
- ftpError:(FTPInfoPtr) info\
\{\

\pard\tx180\tx360\tx540\tx720\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f1\b0\i\fc1\cf1 /*
{{\NeXTHelpMarker24764 \markername ftpError:;}
¬}\pard\tx180\tx360\tx540\tx720\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f1\b0\i\ulnone\fs24\fc1\cf1  --- 
\ul MethodDescription
\ulnone \
	
\i0\ul ReturnValue:
\i\ulnone  self;\
	
\i0\ul Description:
\i\ulnone  This method displays error msgs for codes\
		>= to 400.  Each error message refers the user to the\
		console log for additional information.;\
	
\i0\ul Args:
\i\ulnone \
		info: The connection info for the ftp host;\
*/\

\pard\tx520\tx1060\tx1600\tx2120\tx2660\tx3200\tx3720\tx4260\tx4800\tx5320\f0\b\i0\fc0\cf0 char *errMsg;\
\
	if ( !FTP_REPLY_ERR(info->code) )\
		return self;\
\
	switch (info->code)\
	\{\
		case FTP_SYNTAX_ERROR:\
			errMsg = "Syntax error, command unrecognized.";\
			break;\
		case FTP_PARM_SYNTAX_ERROR:\
			errMsg = "Syntax error in parameters or arguments.";\
			break;\
		case FTP_CMD_NOT_IMPL:\
			errMsg = "Command not implemented.";\
			break;\
		case FTP_BAD_CMD_SEQ:\
			errMsg = "Bad sequence of commands.";\
			break;\
		case FTP_CMD_PARM_NOT_IMPL:\
			errMsg = "Command not implemented for that parameter.";\
			break;\
		case FTP_SERVICE_UNAVAILABLE:\
			errMsg = "Service not available.";\
			break;\
		case FTP_DATA_FAILED:\
			errMsg = "Cannot open data connection.";\
			break;\
		case FTP_DATA_CLOSE_ABORT:\
			errMsg = "Connection closed; transfer aborted.";\
			break;\
		case FTP_LOGIN_ERR:\
			errMsg = "Not logged in.";\
			break;\
		case FTP_FILE_UNAVAILABLE:\
			errMsg = "Requested file action not taken. File unavailable (e.g., file busy).";\
			break;\
		case FTP_ACTION_NOT_TAKEN:\
			errMsg = "Requested action not taken. File unavailable (e.g., file not found, no access).";\
			break;\
		case FTP_ABORTED_LOCAL_ERR:\
			errMsg = "Requested action aborted. Local error in processing.";\
			break;\
		case FTP_ABORTED_PAGE_TYPE:\
			errMsg = "Requested action aborted. Page type unknown.";\
			break;\
		case FTP_INSUFFICIENT_SPACE:\
			errMsg = "Requested action not taken. Insufficient storage space.";\
			break;\
		case FTP_EXCEEDED_ALLOCATION:\
			errMsg = "Requested file action aborted. Exceeded storage allocation.";\
			break;\
		case FTP_BAD_FILENAME:\
			errMsg = "Requested action not taken. File name not allowed.";\
			break;\
		default:\
			errMsg = "Unknown error.";\
	\}\
	[self alert: "FTP Error" msg: "%s\\n%s" btn1: "Ok" btn2: NULL btn3: NULL,\
		errMsg, "The console log may more information."];\
\
	return self;\
\} 
\b0\i // End ftpError:
\b\i0 \
\
@end\

\b0\i\fc1\cf1 /* RCS Information:\
	$Author: me $;\
	$Date: 94/01/08 14:40:51 $;\
	$Source: /usr1/me/NeXTSrc/Archie/RCS/Object_FTPDaemon.m,v $;\
	$Revision: 1.1 $;\
	$Log:	Object_FTPDaemon.m,v $Revision 1.1  94/01/08  14:40:51  meCheck point of 2.09a version.Revision 1.3  93/03/29  02:01:39  meAdded ftpPwd method to return the current ftp directory.\
Revision 1.2  93/02/24  11:39:56  meFixed ftpError: message to actually show the errMsg string.\
Revision 1.1  93/02/23  02:11:06  meVersion 2.01a of the project.;\
*/\

}
