/*
 * FtpSvr
 * Copyright(C) 1997-1999 Eiichiro Ito
*/
#define STRICT
#include	<windows.h>
#include	<winsock.h>
#include	<tchar.h>
#include	"tools.h"
#include	"ftpmain.h"
#include	"resource.h"

WSADATA		g_WsaData ;
HANDLE		g_hRunning = NULL ;
HWND		g_hWndStop = 0 ;
SOCKET		g_svSocket = 0 ;
TCHAR		g_szFtpMessage[ 256 ] = TEXT("") ;

extern	DWORD	AppVersion ;

LPCTSTR		g_szMonthList[] = {
	TEXT("Jan"), TEXT("Feb"), TEXT("Mar"), TEXT("Apr"), TEXT("May"), TEXT("Jun"),
	TEXT("Jul"), TEXT("Aug"), TEXT("Sep"), TEXT("Oct"), TEXT("Nov"), TEXT("Dec"),
} ;

LPCSTR	take( LPCSTR src, LPSTR dst, CHAR delim ) ;
void	strcat_ucpath( LPSTR dst, LPCTSTR src ) ;

extern "C" {
extern	int	atoi( const char * ) ;
} ;

#define		MAX_BUF		(1024)

class Session {
private:
	SOCKET		m_socket ;
	SOCKADDR_IN	m_addr ;
	LPSTR		m_pRecvBuf ;
	int			m_recvLen ;
	CHAR		m_szUser[ 128 ] ;
	CHAR		m_szPasswd[ 128 ] ;
	TCHAR		m_szCurDir[ MAX_PATH ] ;
	TCHAR		m_szUnicode[ MAX_PATH ] ;
	CHAR		m_szSjis[ MAX_PATH ] ;
	BOOL		m_fBinary ;

public:
	Session( SOCKET clSocket ) ;
	~Session() ;

	int			recv() ;
	BOOL		recv_file( LPCTSTR fn, BOOL fBinary ) ;
	BOOL		send_file( LPCTSTR fn, BOOL fBinary ) ;
	BOOL		parse() ;
	BOOL		exec() ;
	void		exec_dir( BOOL fDetail ) ;
	BOOL		exec_get_file( LPCSTR fn ) ;
	BOOL		exec_put_file( LPCSTR fn ) ;
	BOOL		exec_mkdir( LPCSTR dir ) ;
	BOOL		exec_rmdir( LPCSTR dir ) ;
	BOOL		exec_del_file( LPCSTR fn ) ;
	void		SendToClient( int mode, LPCSTR msg ) ;
	void		SendToClient( int mode, LPCTSTR msg ) ;

	SOCKET		get_socket() const { return m_socket ; }
} ;

Session::Session( SOCKET clSocket )
{
	m_socket = clSocket ;
	m_pRecvBuf = new CHAR[ MAX_BUF ] ;
	memset( m_pRecvBuf, 0, MAX_BUF ) ;
	m_recvLen = 0 ;
	m_fBinary = TRUE ;
	_tcscpy( m_szCurDir, TEXT("\\") ) ;
	m_recvLen = 0 ;
}

Session::~Session()
{
	delete[] m_pRecvBuf ;
}

int
Session::recv()
{
	int		num_recv ;

	num_recv = ::recv( m_socket, &m_pRecvBuf[ m_recvLen ], MAX_BUF - m_recvLen, 0 ) ;
	if ( !num_recv || num_recv == SOCKET_ERROR ) {
		return FALSE ;
	}
	m_recvLen += num_recv ;
	return TRUE ;
}

BOOL
Session::send_file( LPCTSTR fn, BOOL fBinary )
{
	HANDLE			hFile ;
	int				status ;
	SOCKET			dtSocket ;
	DWORD			size, nRead ;

	hFile = CreateFile( fn, GENERIC_READ, FILE_SHARE_READ, NULL,
						OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0 ) ;
	if ( hFile == INVALID_HANDLE_VALUE ) {
		wsprintf( m_szUnicode, TEXT("%s: can't find the file"), fn ) ;
		SendToClient( 550, m_szUnicode ) ;
		return FALSE ;
	}
	size = GetFileSize( hFile, NULL ) ;
	dtSocket = socket( AF_INET, SOCK_STREAM, 0 ) ;
	if ( dtSocket == INVALID_SOCKET ) {
		SendToClient( 500, "can't open socket" ) ;
		CloseHandle( hFile ) ;
		return FALSE ;
	}
	status = connect( dtSocket, (LPSOCKADDR) &m_addr, sizeof m_addr ) ;
	if ( status == SOCKET_ERROR ) {
		closesocket( dtSocket ) ;
		SendToClient( 500, "can't connect socket" ) ;
		CloseHandle( hFile ) ;
		return FALSE ;
	}
	wsprintf( m_szUnicode,
			  TEXT("Opening %s mode data connection for %s(%d bytes)."),
			  fBinary ? TEXT("BINARY") : TEXT("ASCII"),
			  fn, size ) ;
	SendToClient( 150, m_szUnicode ) ;
	do {
		if ( !ReadFile( hFile, m_szSjis, sizeof m_szSjis, &nRead, 0 ) ) {
			break ;
		}
		send( dtSocket, m_szSjis, nRead, 0 ) ;
	} while ( nRead == sizeof m_szSjis ) ;
	CloseHandle( hFile ) ;
	shutdown( dtSocket, 0 ) ;
	closesocket( dtSocket ) ;
	SendToClient( 226, "Transfer complete." ) ;
	return TRUE ;
}

BOOL
Session::recv_file( LPCTSTR fn, BOOL fBinary )
{
	HANDLE			hFile ;
	DWORD			nWrite ;
	SOCKET			dtSocket ;
	int				status, num_recv ;

	hFile = CreateFile( fn, GENERIC_WRITE, FILE_SHARE_READ, NULL,
						CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0 ) ;
	if ( hFile == INVALID_HANDLE_VALUE ) {
		wsprintf( m_szUnicode, TEXT("%s: can't open the file"), fn ) ;
		SendToClient( 550, m_szUnicode ) ;
		return FALSE ;
	}
	dtSocket = socket( AF_INET, SOCK_STREAM, 0 ) ;
	if ( dtSocket == INVALID_SOCKET ) {
		SendToClient( 500, "can't open socket" ) ;
		CloseHandle( hFile ) ;
		return FALSE ;
	}
	status = connect( dtSocket, (LPSOCKADDR) &m_addr, sizeof m_addr ) ;
	if ( status == SOCKET_ERROR ) {
		closesocket( dtSocket ) ;
		SendToClient( 500, "can't connect socket" ) ;
		CloseHandle( hFile ) ;
		return FALSE ;
	}
	wsprintf( m_szUnicode,
			  TEXT("Opening %s mode data connection for %s."),
			  fBinary ? TEXT("BINARY") : TEXT("ASCII"),
			  fn ) ;
	SendToClient( 150, m_szUnicode ) ;
	while ( 1 ) {
		num_recv = ::recv( dtSocket, m_szSjis, sizeof m_szSjis, 0 ) ;
		if ( !num_recv || num_recv == SOCKET_ERROR ) {
			break ;
		}
		WriteFile( hFile, m_szSjis, num_recv, &nWrite, 0 ) ;
	}
	CloseHandle( hFile ) ;
	closesocket( dtSocket ) ;
	SendToClient( 226, "Transfer complete." ) ;
	return TRUE ;
}

BOOL
Session::parse()
{
	int			i, delsize = 0 ;
	LPSTR		ptr, nexttop = 0 ;

	for ( i = 0, ptr = m_pRecvBuf ; i < m_recvLen ; i ++, ptr ++ ) {
		if ( *ptr == '\r' ) {
			*ptr = 0 ;
			if ( i + 1 < m_recvLen && ptr[1] == '\n' ) {
				nexttop = ptr + 2 ;
				delsize = i + 2 ;
			} else {
				nexttop = ptr + 1 ;
				delsize = i + 1 ;
			}
			break ;
		} else if ( *ptr == '\n' ) {
			*ptr = 0 ;
			nexttop = ptr + 1 ;
			delsize = i + 1 ;
			break ;
		}
	}
	if ( i >= m_recvLen ) {
		return TRUE ;
	}
	if ( !exec() ) {
		return FALSE ;
	}
	if ( m_recvLen > delsize ) {
		memcpy( m_pRecvBuf, nexttop, m_recvLen - delsize ) ;
		m_recvLen -= delsize ;
	} else {
		m_recvLen = 0 ;
	}
	return TRUE ;
}

BOOL
Session::exec()
{
	LPCSTR	ptr ;
	u_short	port ;
	LPCSTR	buf = m_pRecvBuf ;
	SOCKET	clSocket = m_socket ;
	TCHAR	unicode2[ MAX_PATH ] ;

	if ( !strcmp( buf, "QUIT" ) ) {
		SendToClient( 221, "Goodbye" ) ;
		*m_szUser = 0 ;
		return FALSE ;
	} else if ( !strncmp( buf, "USER ", 5 ) ) {
		strcpy( m_szUser, &buf[ 5 ] ) ;
		strcpy( m_szSjis, "Password required for " ) ;
		strcat( m_szSjis, m_szUser ) ;
		strcat( m_szSjis, "." ) ;
		SendToClient( 331, m_szSjis ) ;
	} else if ( !strncmp( buf, "PASS ", 5 ) ) {
		strcpy( m_szPasswd, &buf[ 5 ] ) ;
		strcpy( m_szSjis, "User " ) ;
		strcat( m_szSjis, m_szUser ) ;
		strcat( m_szSjis, " logged in." ) ;
		SendToClient( 230, m_szSjis ) ;
	} else if ( !strncmp( buf, "PORT ", 5 ) ) {
		memset( &m_addr, 0, sizeof m_addr ) ;
		m_addr.sin_family = AF_INET ;
		ptr = &buf[ 5 ] ;
		ptr = take( ptr, m_szSjis, ',' ) ;
		m_addr.sin_addr.S_un.S_un_b.s_b1 = atoi( m_szSjis ) ;
		ptr = take( ptr, m_szSjis, ',' ) ;
		m_addr.sin_addr.S_un.S_un_b.s_b2 = atoi( m_szSjis ) ;
		ptr = take( ptr, m_szSjis, ',' ) ;
		m_addr.sin_addr.S_un.S_un_b.s_b3 = atoi( m_szSjis ) ;
		ptr = take( ptr, m_szSjis, ',' ) ;
		m_addr.sin_addr.S_un.S_un_b.s_b4 = atoi( m_szSjis ) ;
		ptr = take( ptr, m_szSjis, ',' ) ;
		port = (u_short) atoi( m_szSjis ) ;
		port = port * 256 + (u_short) atoi( ptr ) ;
		m_addr.sin_port = htons( port ) ;
		SendToClient( 200, "PORT command successful." ) ;
	} else if ( !strcmp( buf, "PWD" ) || !strcmp( buf, "XPWD" ) ) {
		strcpy( m_szSjis, "\042" ) ;
		strcat_ucpath( m_szSjis, m_szCurDir ) ;
		strcat( m_szSjis, "\042 is current directory." ) ;
		SendToClient( 257, m_szSjis ) ;
	} else if ( !strncmp( buf, "CWD ", 4 ) ) {
		strcpy( m_szSjis, &buf[ 4 ] ) ;
		mbstowcs( m_szUnicode, m_szSjis, strlen( m_szSjis ) + 1 ) ;
		rel2absdir( m_szCurDir, unicode2, sizeof unicode2 / sizeof (TCHAR), m_szUnicode ) ;
		if ( is_dir( unicode2 ) ) {
			_tcscpy( m_szCurDir, unicode2 ) ;
			SendToClient( 250, "CWD command successful." ) ;
		} else {
			strcat( m_szSjis, ": can't find the directory." ) ;
			SendToClient( 550, m_szSjis ) ;
		}
	} else if ( !strncmp( buf, "LIST", 4 ) ) {
		exec_dir( TRUE ) ;
	} else if ( !strncmp( buf, "NLST", 4 ) ) {
		exec_dir( FALSE ) ;
	} else if ( !strncmp( buf, "RETR ", 5 ) ) {
		exec_get_file( &buf[ 5 ] ) ;
	} else if ( !strncmp( buf, "STOR ", 5 ) ) {
		exec_put_file( &buf[ 5 ] ) ;
	} else if ( !strcmp( buf, "TYPE I" ) ) {
		m_fBinary = TRUE ;
		SendToClient( 200, "Type set to I" ) ;
	} else if ( !strcmp( buf, "TYPE A" ) ) {
		m_fBinary = FALSE ;
		SendToClient( 200, "Type set to A" ) ;
	} else if ( !strncmp( buf, "XMKD ", 5 ) ) {
		if ( exec_mkdir( &buf[ 5 ] ) ) {
			SendToClient( 257, "MKD command successful." ) ;
		} else {
			strcpy( m_szSjis, &buf[ 5 ] ) ;
			strcat( m_szSjis, ": can't create a file when that file already exists." ) ;
			SendToClient( 550, m_szSjis ) ;
		}
	} else if ( !strncmp( buf, "XRMD ", 5 ) ) {
		if ( exec_rmdir( &buf[ 5 ] ) ) {
			SendToClient( 250, "RMD command successful." ) ;
		} else {
			strcpy( m_szSjis, &buf[ 5 ] ) ;
			strcat( m_szSjis, ": can't find the file." ) ;
			SendToClient( 550, m_szSjis ) ;
		}
	} else if ( !strncmp( buf, "DELE ", 5 ) ) {
		if ( exec_del_file( &buf[ 5 ] ) ) {
			SendToClient( 250, "DELE command successful." ) ;
		} else {
			strcpy( m_szSjis, &buf[ 5 ] ) ;
			strcat( m_szSjis, ": can't find the file." ) ;
			SendToClient( 550, m_szSjis ) ;
		}
	} else {
		strcpy( m_szSjis, "'" ) ;
		strcat( m_szSjis, buf ) ;
		strcat( m_szSjis, "': command not understood" ) ;
		SendToClient( 500, m_szSjis ) ;
	}
	return TRUE ;
}

void
Session::exec_dir( BOOL fDetail )
{
	SYSTEMTIME		st, now ;
	WIN32_FIND_DATA	find ;
	DWORD			len, nWrite ;
	HANDLE			hFile, hFind ;
	TCHAR			szTmpPath[ MAX_PATH ], buf[ 20 ], attr[ 12 ] ;

	GetLocalTime( &now ) ;
	wsprintf( szTmpPath, TEXT("\\%u.FTP"), this ) ;
	hFile = CreateFile( szTmpPath, GENERIC_WRITE, FILE_SHARE_READ, NULL,
						CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0 ) ;
	if ( hFile == INVALID_HANDLE_VALUE ) {
		return ;
	}
	_tcscpy( m_szUnicode, m_szCurDir ) ;
	catdir( m_szUnicode, TEXT("*.*"), NULL ) ;
	hFind = FindFirstFile( m_szUnicode, &find ) ;
	if ( hFind != INVALID_HANDLE_VALUE ) {
		do {
			if ( !_tcscmp( find.cFileName, TEXT(".") ) ) {
				continue ;
			} else if ( !_tcscmp( find.cFileName, TEXT("..") ) ) {
				continue ;
			}
			if ( fDetail ) {
				FileTimeToSystemTime( &find.ftLastWriteTime, &st ) ;
				if ( st.wYear != now.wYear ) {
					wsprintf( buf, TEXT(" %04d"), st.wYear ) ;
				} else {
					wsprintf( buf, TEXT("%2d:%02d"), st.wHour, st.wMinute ) ;
				}
				len = _tcslen( find.cFileName ) ;
				if ( find.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ) {
					_tcscpy( attr, TEXT("drwxrwxrwx") ) ;
				} else if ( len > 4 && !_tcsicmp( &find.cFileName[ len - 4 ], TEXT(".EXE") ) ) {
					_tcscpy( attr, TEXT("-rwxrwxrwx") ) ;
				} else {
					_tcscpy( attr, TEXT("-rw-rw-rw-") ) ;
				}
				if ( find.dwFileAttributes & FILE_ATTRIBUTE_READONLY ) {
					attr[2] = attr[5] = attr[8] = TEXT('-') ;
				}
				wsprintf( m_szUnicode,
						  TEXT("%s   1 %8s %8s   %-10d %s %2d %s %s"),
						  attr,
						  TEXT("owner   "), TEXT("group   "),
						  find.nFileSizeLow,
						  g_szMonthList[ st.wMonth - 1 ], st.wDay,
						  buf,
						  find.cFileName ) ;
			} else {
				_tcscpy( m_szUnicode, find.cFileName ) ;
			}
			m_szSjis[0] = 0 ;
			wcstombs( m_szSjis, m_szUnicode, sizeof m_szSjis ) ;
			if ( m_szSjis[0] ) {
				len = strlen( m_szSjis ) ;
				m_szSjis[ len ++ ] = TEXT('\r') ;
				m_szSjis[ len ++ ] = TEXT('\n') ;
				WriteFile( hFile, m_szSjis, len, &nWrite, 0 ) ;
			}
		} while ( FindNextFile( hFind, &find ) ) ;
		FindClose( hFind ) ;
	}
	CloseHandle( hFile ) ;
	send_file( szTmpPath, FALSE ) ;
	DeleteFile( szTmpPath ) ;
}

BOOL
Session::exec_get_file( LPCSTR fn )
{
	TCHAR	unicode2[ MAX_PATH ] ;

	mbstowcs( m_szUnicode, fn, strlen( fn ) + 1 ) ;
	rel2absdir( m_szCurDir, unicode2, sizeof unicode2 / sizeof (TCHAR), m_szUnicode ) ;
	return send_file( unicode2, m_fBinary ) ;
}

BOOL
Session::exec_put_file( LPCSTR fn )
{
	TCHAR	unicode2[ MAX_PATH ] ;

	mbstowcs( m_szUnicode, fn, strlen( fn ) + 1 ) ;
	rel2absdir( m_szCurDir, unicode2, sizeof unicode2 / sizeof (TCHAR), m_szUnicode ) ;
	return recv_file( unicode2, m_fBinary ) ;
}

BOOL
Session::exec_mkdir( LPCSTR dir )
{
	TCHAR	unicode2[ MAX_PATH ] ;

	mbstowcs( m_szUnicode, dir, strlen( dir ) + 1 ) ;
	rel2absdir( m_szCurDir, unicode2, sizeof unicode2 / sizeof (TCHAR), m_szUnicode ) ;
	return CreateDirectory( unicode2, NULL ) ;
}

BOOL
Session::exec_rmdir( LPCSTR dir )
{
	TCHAR	unicode2[ MAX_PATH ] ;

	mbstowcs( m_szUnicode, dir, strlen( dir ) + 1 ) ;
	rel2absdir( m_szCurDir, unicode2, sizeof unicode2 / sizeof (TCHAR), m_szUnicode ) ;
	return RemoveDirectory( unicode2 ) ;
}

BOOL
Session::exec_del_file( LPCSTR fn )
{
	TCHAR	unicode2[ MAX_PATH ] ;

	mbstowcs( m_szUnicode, fn, strlen( fn ) + 1 ) ;
	rel2absdir( m_szCurDir, unicode2, sizeof unicode2 / sizeof (TCHAR), m_szUnicode ) ;
	return DeleteFile( unicode2 ) ;
}

void
Session::SendToClient( int mode, LPCSTR msg )
{
	CHAR	buf[ 256 ] ;

	buf[0] = (mode / 100) + '0' ;
	buf[1] = ((mode / 10) % 10) + '0' ;
	buf[2] = (mode % 10) + '0' ;
	buf[3] = ' ' ;
	strcpy( &buf[4], msg ) ;
	strcat( buf, "\r\n" ) ;
	send( m_socket, buf, strlen( buf ), 0 ) ;
}

void
Session::SendToClient( int mode, LPCTSTR msg )
{
	CHAR	buf[ 256 ] ;

	buf[0] = (mode / 100) + '0' ;
	buf[1] = ((mode / 10) % 10) + '0' ;
	buf[2] = (mode % 10) + '0' ;
	buf[3] = ' ' ;
	buf[4] = 0 ;
	wcstombs( &buf[4], msg, sizeof buf - 4 ) ;
	strcat( buf, "\r\n" ) ;
	send( m_socket, buf, strlen( buf ), 0 ) ;
}

void	NotifyMessage( LPCTSTR str, int errcode ) ;
BOOL	ServerLoop( Session *sess ) ;
void	ServerMain( void ) ;
BOOL	StartServer( int portNo ) ;

void
NotifyMessage( LPCTSTR str, int errcode )
{
	wsprintf( g_szFtpMessage, TEXT("%s(%d)"), str, errcode ) ;
}

BOOL
ServerLoop( Session *sess )
{
	struct timeval	tv ;
	fd_set			readfds ;
	TCHAR			unicode[ 30 ] ;
	SOCKET			clSocket = sess->get_socket() ;

	wsprintf( unicode, TEXT("FtpSvr (Version %d.%02d)."), AppVersion / 100, AppVersion % 100 ) ;
	sess->SendToClient( 220, unicode ) ;
	while ( g_hRunning && (WaitForSingleObject( g_hRunning, 0 ) != WAIT_OBJECT_0) ) {
		tv.tv_sec = 1 ;
		tv.tv_usec = 0 ;
		FD_ZERO( &readfds ) ;
		FD_SET( clSocket, &readfds ) ;
		if ( !select( 0, &readfds, NULL, NULL, &tv ) ) {
			continue ;
		}
		if ( !sess->recv() ) {
			break ;
		}
		if ( !sess->parse() ) {
			break ;
		}
	}
	closesocket( clSocket ) ;
	delete sess ;
	return TRUE ;
}

LPCSTR
take( LPCSTR src, LPSTR dst, CHAR delim )
{
	CHAR	c ;

	while ( c = *src ++ ) {
		if ( c == delim ) {
			break ;
		}
		*dst++ = c ;
	}
	*dst = 0 ;
	return src ;
}

void
strcat_ucpath( LPSTR dst, LPCTSTR src )
{
	TCHAR	c ;

	while ( *dst ) {
		dst ++ ;
	}
	while ( c = *src++ ) {
		if ( c == TEXT('\\') ) {
			c = TEXT('/') ;
		}
		*dst++ = (CHAR) c ;
	}
	*dst = 0 ;
}

void
ServerMain( void )
{
	struct timeval	tv ;
	fd_set			readfds ;
	HANDLE			hThread ;
	DWORD			threadID ;
	SOCKET			clSocket ;
	int				clAddrLen ;
	SOCKADDR_IN		clSockAddr ;

	g_hWndStop = 0 ;
	NotifyMessage( TEXT("selecting"), 0 ) ;
	while ( g_hRunning && (WaitForSingleObject( g_hRunning, 0 ) != WAIT_OBJECT_0) ) {
		tv.tv_sec = 1 ;
		tv.tv_usec = 0 ;
		FD_ZERO( &readfds ) ;
		FD_SET( g_svSocket, &readfds ) ;
		if ( !select( 0, &readfds, NULL, NULL, &tv ) ) {
			continue ;
		}
		NotifyMessage( TEXT("accepting"), 0 ) ;
		clAddrLen = sizeof (clSockAddr) ;
		clSocket = accept( g_svSocket, (LPSOCKADDR) &clSockAddr, &clAddrLen ) ;
		if ( clSocket == INVALID_SOCKET ) {
			NotifyMessage( TEXT("accept failed(%d)"), WSAGetLastError() ) ;
			break ;
		}
		Session		*sess = new Session( clSocket ) ;
		if ( !sess ) {
			closesocket( clSocket ) ;
			continue ;
		}
		hThread = CreateThread( NULL,
						0,
						(LPTHREAD_START_ROUTINE) ServerLoop,
						(void*) sess,
						0,
						&threadID ) ;
		if ( hThread == NULL ) {
			NotifyMessage( TEXT("CreateThread fail(%d)"), GetLastError() ) ;
			closesocket( clSocket ) ;
			continue ;
		}
		CloseHandle( hThread ) ;
	}
	closesocket( g_svSocket ) ;
	g_svSocket = 0 ;
	if ( g_hWndStop ) {
		PostMessage( g_hWndStop, WM_COMMAND, IDM_STOP, 0 ) ;
		g_hWndStop = 0 ;
	}
}

BOOL
StartServer( int portNo )
{
	int				status ;
	HANDLE			hThread ;
	DWORD			threadID ;
	SOCKADDR_IN		svSockAddr ;

	/* イベントをリセットする */
	ResetEvent( g_hRunning ) ;
	/* ソケットをオープンする */
	g_svSocket = socket( PF_INET, SOCK_STREAM, 0 ) ;
	if ( g_svSocket == INVALID_SOCKET ) {
		NotifyMessage( TEXT("socket failed(%d)"), GetLastError() ) ;
		g_svSocket = 0 ;
		return FALSE ;
	}
	memset( &svSockAddr, 0, sizeof (svSockAddr) ) ;
	svSockAddr.sin_port = htons(portNo) ;
	svSockAddr.sin_family = AF_INET ;
	svSockAddr.sin_addr.s_addr = htonl(INADDR_ANY) ;
	/* bind を行う */
	status = bind( g_svSocket, (LPSOCKADDR) &svSockAddr, sizeof (svSockAddr) ) ;
	if ( status == SOCKET_ERROR ) {
		NotifyMessage( TEXT("bind failed(%d)"), GetLastError() ) ;
		closesocket( g_svSocket ) ;
		g_svSocket = 0 ;
		return FALSE ;
	}
	NotifyMessage( TEXT("bind ok"), status ) ;
	/* listen を行う */
	status = listen( g_svSocket, 1 ) ;
	if ( status == SOCKET_ERROR ) {
		NotifyMessage( TEXT("listen failed(%d)"), GetLastError() ) ;
		closesocket( g_svSocket ) ;
		g_svSocket = 0 ;
		return FALSE ;
	}
	NotifyMessage( TEXT("listen ok"), status ) ;
	hThread = CreateThread( NULL,
					0,
					(LPTHREAD_START_ROUTINE) ServerMain,
					(void*) 0,
					0,
					&threadID ) ;
	if ( hThread == NULL ) {
		NotifyMessage( TEXT("CreateThread fail(%d)"), GetLastError() ) ;
		closesocket( g_svSocket ) ;
		g_svSocket = 0 ;
		return FALSE ;
	}
	CloseHandle( hThread ) ;
	return TRUE ;
}

BOOL
ftp_init( void )
{
	WSAStartup( MAKEWORD(1, 1), &g_WsaData ) ;
	/* 受信許可イベントの作成 */
	g_hRunning = CreateEvent( NULL, TRUE, FALSE, NULL ) ;
	if ( g_hRunning == NULL ) {
		return FALSE ;
	}
	NotifyMessage( TEXT("ftp initialized"), 0 ) ;
	return TRUE ;
}

void
ftp_release( void )
{
	CloseHandle( g_hRunning ) ;
	g_hRunning = NULL ;
}

BOOL
ftp_start( int portNo )
{
	BOOL	ret ;

	ret = StartServer( portNo ) ;
	NotifyMessage( TEXT("ftp started"), ret ) ;
	return ret ;
}

void
ftp_stop( HWND hWnd )
{
	SetEvent( g_hRunning ) ;
	g_hWndStop = hWnd ;
}

BOOL
ftp_running( void )
{
	if ( g_hRunning == NULL ) {
		return FALSE ;
	}
	return WaitForSingleObject( g_hRunning, 0 ) != WAIT_OBJECT_0 ;
}
