/*--------------------------------------------------------------------- * * Program: AC_FTP.EXE Asynch Ftp Client (TCP) * * filename: ac_ftp.c * * copyright by Bob Quinn, 1995 * * Description: * Client application that uses "file transfer protocol" (ftp) * service as described by RFC 959. This application demonstrates * bulk data transfer techniques, and reading variable length records * from a byte stream (TCP socket). The user interface is minimized * in order to emphasize the network code. * * This software is not subject to any export provision of * the United States Department of Commerce, and may be * exported to any country or planet. * * Permission is granted to anyone to use this software for any * purpose on any computer system, and to alter it and redistribute * it freely, subject to the following restrictions: * * 1. The author is not responsible for the consequences of * use of this software, no matter how awful, even if they * arise from flaws in it. * * 2. The origin of this software must not be misrepresented, * either by explicit claim or by omission. Since few users * ever read sources, credits must appear in the documentation. * * 3. Altered versions must be plainly marked as such, and * must not be misrepresented as being the original software. * Since few users ever read sources, credits must appear in * the documentation. * * 4. This notice may not be removed or altered. * ---------------------------------------------------------------------*/ #define STRICT #include "..\wsa_xtra.h" #include #include #include /* for _fmemcpy() & _fmemset() */ #include #include "resource.h" #include #include #include /* for getcwd() */ #include "..\winsockx.h" #include "ac_ftp.h" /* Ftp commmand strings (indexed by our command macro values) */ LPSTR aszFtpCmd[] = { "", "CWD", "DELE", "PASS","PORT","RETR","STOR", "TYPE","USER","ABOR","LIST","PWD", "QUIT" }; char szAppName[] = "AC_FTP"; BOOL nAppState=NOT_CONNECTED; /* Application State */ BOOL bToNul =FALSE; /* Get to NUL device file */ BOOL bFromNul=FALSE; /* Put from NUL device file */ BOOL bIOBeep =FALSE; /* Beep on FD_READ, FD_WRITE */ BOOL bDebug =FALSE; /* Debug output to WinDebug */ BOOL bReAsync=TRUE; /* Call WSAAsyncSelect after accept() */ BOOL bLogFile=TRUE; /* Write Cmds and Replies to logfile */ SOCKET hCtrlSock=INVALID_SOCKET; /* Ftp control socket */ SOCKET hLstnSock=INVALID_SOCKET; /* Listening data socket */ SOCKET hDataSock=INVALID_SOCKET; /* Connected data socket */ char szHost[MAXHOSTNAME]={0}; /* Remote host name or address */ char szUser[MAXUSERNAME]={0}; /* User ID */ char szPWrd[MAXPASSWORD]={0}; /* User password */ SOCKADDR_IN stCLclName; /* Control socket name (local client) */ SOCKADDR_IN stCRmtName; /* (remote server) */ SOCKADDR_IN stDLclName; /* Data socket name (local client)*/ SOCKADDR_IN stDRmtName; /* (remote server) */ char achInBuf [INPUT_SIZE]; /* Network input data buffer */ char achOutBuf [INPUT_SIZE]; /* Network output buffer */ char szFtpRply [RPLY_SIZE]={0}; /* Ftp reply (input) buffer */ char szDataFile[MAXFILENAME]={0};/* Filename */ char szFtpCmd [CMD_SIZE]={0}; /* Ftp command buffer */ char achRplyBuf[BUF_SIZE]; /* Reply display buffer */ /* first one (index=0) is awaiting a reply * second (index=1) is next to be sent, etcetera */ FTPCMD astFtpCmd[MAX_CMDS]; /* Ftp command queue */ int nQLen; /* Number of entries in Ftp cmd queue */ int nFtpRplyCode; /* Ftp reply code from server */ int iNextRply; /* Index to next reply string */ int iLastRply; HFILE hDataFile=HFILE_ERROR; /* File handle for open data file */ LONG lStartTime; /* Start time for data transfer */ LONG lByteCount; char szLogFile[] = "ac_ftp.log"; /* Ftp command and reply log file */ HFILE hLogFile=HFILE_ERROR; /*-------------------------------------------------------------------- * Function: WinMain() * * Description: * initialize WinSock and open main dialog box * */ int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) { MSG msg; int nRet; lpszCmdLine = lpszCmdLine; /* avoid warning */ hPrevInstance = hPrevInstance; nCmdShow = nCmdShow; hInst = hInstance; /* save instance handle */ /*-------------initialize WinSock DLL------------*/ nRet = WSAStartup(WSA_VERSION, &stWSAData); /* WSAStartup() returns error value if failed (0 on success) */ if (nRet != 0) { WSAperror(nRet, "WSAStartup()", hInst); /* No sense continuing if we can't use WinSock */ } else { DialogBox (hInst, MAKEINTRESOURCE(AC_FTP), NULL, Dlg_Main); /*---------------release WinSock DLL--------------*/ nRet = WSACleanup(); if (nRet == SOCKET_ERROR) WSAperror(WSAGetLastError(), "WSACleanup()", hInst); } return msg.wParam; } /* end WinMain() */ /*-------------------------------------------------------------------- * Function: Dlg_Main() * * Description: Do all the message processing for the main dialog box */ BOOL CALLBACK Dlg_Main (HWND hDlg, UINT msg, UINT wParam, LPARAM lParam) { int nAddrSize = sizeof(SOCKADDR); WORD WSAEvent, WSAErr; SOCKET hSock; BOOL bOk, bRet = FALSE; int nRet; LONG lRet; switch (msg) { case WSA_ASYNC+1: /*------------------------------------------------- * Data socket async notification message handlers *-------------------------------------------------*/ hSock = (SOCKET)wParam; /* socket */ WSAEvent = WSAGETSELECTEVENT (lParam); /* extract event */ WSAErr = WSAGETSELECTERROR (lParam); /* extract error */ /* if error, display to user (listen socket should not have * an error, but some WinSocks incorrectly post it) */ if ((WSAErr) && (hSock == hDataSock)) { int i,j; for (i=0, j=WSAEvent; j; i++, j>>=1); /* convert bit to index */ WSAperror(WSAErr,aszWSAEvent[i], hInst); /* fall-through to call reenabling function for this event */ } switch (WSAEvent) { case FD_READ: if (bIOBeep) MessageBeep(0xFFFF); if (hDataSock != INVALID_SOCKET) { /* Receive file data or directory list */ RecvData(hDataSock, hDataFile, achInBuf, INPUT_SIZE); } break; case FD_ACCEPT: if (hLstnSock != INVALID_SOCKET) { /* Accept the incoming data connection request */ hDataSock = AcceptDataConn(hLstnSock, &stDRmtName); nAppState |= DATACONNECTED; /* Close the Listening Socket */ closesocket(hLstnSock); hLstnSock = INVALID_SOCKET; lStartTime = GetTickCount(); /* Data transfer should begin with FD_WRITE or FD_READ. We * fall through to jumpstart sends since FD_WRITE is not * always implemented correctly */ } case FD_WRITE: /* Send file data */ if (astFtpCmd[0].nFtpCmd == STOR) { lRet = SendData(&hDataSock, hDataFile, MTU_SIZE); } break; case FD_CLOSE: /* Data connection closed */ if (hSock == hDataSock) { /* Read any remaining data into buffer and close connection */ CloseFtpConn(&hDataSock, (astFtpCmd[0].nFtpCmd != STOR) ? achInBuf : (LPSTR)0, INPUT_SIZE, hDlg); EndData (); } break; default: break; } /* end switch(WSAEvent) */ break; case WSA_ASYNC: /*----------------------------------------------------- * Control socket async notification message handlers *-----------------------------------------------------*/ WSAEvent = WSAGETSELECTEVENT (lParam); /* extract event */ WSAErr = WSAGETSELECTERROR (lParam); /* extract error */ if (WSAErr) { /* if error, display to user */ int i,j; for (i=0, j=WSAEvent; j; i++, j>>=1); /* convert bit to index */ WSAperror(WSAErr,aszWSAEvent[i], hInst); /* fall-through to call reenabling function for this event */ } hSock = (SOCKET)wParam; switch (WSAEvent) { case FD_READ: if (!iNextRply) { /* Receive reply from server */ iLastRply = RecvFtpRply(hCtrlSock, szFtpRply, RPLY_SIZE); } if (iLastRply && (iLastRply != SOCKET_ERROR)) { /* Display the reply Message */ GetDlgItemText (hWinMain, IDC_REPLY, achRplyBuf, RPLY_SIZE-strlen(szFtpRply)); wsprintf (achTempBuf, "%s%s", szFtpRply, achRplyBuf); SetDlgItemText (hWinMain, IDC_REPLY, achTempBuf); /* Save index to next reply (if there is one) */ nRet = strlen(szFtpRply); if (iLastRply > nRet+2) { iNextRply = nRet+3; /* Adjust if reply only had LF (no CR) */ if (szFtpRply[nRet+2]) iNextRply = nRet+2; } } /* Figure out what to do with reply based on last command */ ProcessFtpRply (szFtpRply, RPLY_SIZE); break; case FD_WRITE: /* Send command to server */ if (astFtpCmd[1].nFtpCmd) SendFtpCmd(); break; case FD_CONNECT: /* Control connected at TCP level */ nAppState = CTRLCONNECTED; wsprintf(achTempBuf, "Server: %s", szHost); SetDlgItemText (hDlg, IDC_SERVER, achTempBuf); SetDlgItemText (hDlg, IDC_STATUS, "Status: connected"); break; case FD_CLOSE: if (nAppState & CTRLCONNECTED) { nAppState = NOT_CONNECTED; /* Reset app state */ AbortFtpCmd(); if (hCtrlSock != INVALID_SOCKET) /* Close control socket */ CloseFtpConn(&hCtrlSock, (PSTR)0, 0, hDlg); SetDlgItemText (hDlg, IDC_SERVER, "Server: none"); SetDlgItemText (hDlg, IDC_STATUS, "Status: not connected"); } break; default: break; } /* end switch(WSAEvent) */ break; case WM_COMMAND: switch (wParam) { case IDC_CONNECT: /* If we already have a socket open, tell user to close it */ if (nAppState & CTRLCONNECTED) { MessageBox (hDlg,"Close the active connection first", "Can't Connect", MB_OK | MB_ICONASTERISK); } else { /* Prompt user for server and login user information */ bOk = DialogBox (hInst, MAKEINTRESOURCE(IDD_SERVER), hDlg, Dlg_Login); if (bOk) { /* Check the destination address and resolve if necessary */ stCRmtName.sin_addr.s_addr = GetAddr(szHost); if (stCRmtName.sin_addr.s_addr == INADDR_ANY) { /* Tell user to enter a host */ wsprintf(achTempBuf, "Sorry, server %s is invalid. Try again", szHost); MessageBox (hDlg, achTempBuf, "Can't connect!", MB_OK | MB_ICONASTERISK); } else { /* Initiate connect attempt to server */ hCtrlSock = InitCtrlConn(&stCRmtName, hDlg, WSA_ASYNC); } } } break; case IDC_CLOSE: if (nAppState & CTRLCONNECTED) { /* Set application state so nothing else is processed */ nAppState = NOT_CONNECTED; /* If we're listening, stop now */ if (hLstnSock != INVALID_SOCKET) { closesocket(hLstnSock); hLstnSock = INVALID_SOCKET; } /* If there is a data connection, then abort it */ if (hDataSock != INVALID_SOCKET) QueueFtpCmd (ABOR, 0); /* Quit the control connection */ if (hCtrlSock != INVALID_SOCKET) QueueFtpCmd (QUIT, 0); SetDlgItemText (hDlg, IDC_SERVER, "Server: none"); SetDlgItemText (hDlg, IDC_STATUS, "Status: not connected"); } break; case IDC_RETR: /* Prompt for name of remote file to get */ if (nAppState & CTRLCONNECTED) { bOk = DialogBoxParam (hInst, MAKEINTRESOURCE(IDD_FILENAME), hDlg, Dlg_File, (LONG)(LPSTR)szDataFile); if (bOk && szDataFile[0]) { if (!bToNul) { /* If user provided a filename open same name here for write. * Truncate the filename to 8 chars plus 3, if necessary */ hDataFile = CreateLclFile (szDataFile); } if (hDataFile != HFILE_ERROR || bToNul) { /* Tell the Server where to connect back to us */ hLstnSock = InitDataConn(&stDLclName, hDlg, WSA_ASYNC+1); if (hLstnSock != INVALID_SOCKET) { /* Queue PORT, TYPE and RETR cmds */ if (QueueFtpCmd(PORT, 0)) { if (QueueFtpCmd (TYPE, "I")) QueueFtpCmd(RETR, szDataFile); } } } } } else not_connected(); break; case IDC_STOR: /* Prompt for name of local file to send */ if (nAppState & CTRLCONNECTED) { bOk = DialogBoxParam (hInst, MAKEINTRESOURCE(IDD_FILENAME), hDlg, Dlg_File, (LONG)(LPSTR)szDataFile); if (bOk && szDataFile[0]) { if (!bFromNul) { /* If user provided filename, try to open same name locally */ hDataFile = _lopen(szDataFile, 0); if (hDataFile == HFILE_ERROR) { wsprintf(achTempBuf, "Unable to open file: %s", szDataFile); MessageBox (hWinMain, (LPSTR)achTempBuf,"File Error", MB_OK | MB_ICONASTERISK); } } if (hDataFile != HFILE_ERROR || bFromNul) { /* Tell the Server where to connect back to us */ hLstnSock = InitDataConn(&stDLclName, hDlg, WSA_ASYNC+1); if (hLstnSock != INVALID_SOCKET) { /* Queue PORT, TYPE and STOR cmds */ if (QueueFtpCmd (PORT, 0)) { if (QueueFtpCmd (TYPE, "I")) QueueFtpCmd (STOR, szDataFile); } } } } } else not_connected(); break; case IDC_ABOR: if (hCtrlSock != INVALID_SOCKET) /* Abort the pending ftp command */ QueueFtpCmd (ABOR, 0); break; case IDC_LCWD: /* Prompt for directory name, and move to it on local system. */ bOk = DialogBoxParam (hInst, MAKEINTRESOURCE(IDD_FILENAME), hDlg, Dlg_File, (LONG)(LPSTR)szDataFile); if (bOk && szDataFile[0]) { if (!(_chdir (szDataFile))) { getcwd (szDataFile, MAXFILENAME-1); SetDlgItemText (hDlg, IDC_LPWD, szDataFile); } } break; case IDC_LDEL: /* Prompt for filename, and delete it from local system */ bOk = DialogBoxParam (hInst, MAKEINTRESOURCE(IDD_FILENAME), hDlg, Dlg_File, (LONG)(LPSTR)szDataFile); if (bOk && szDataFile[0]) { /* If user provided filename, then delete it */ remove (szDataFile); } break; case IDC_LDIR: /* Get local file directory, and display in notepad */ if (GetLclDir(szTempFile)) { wsprintf (achTempBuf, "notepad %s", szTempFile); WinExec (achTempBuf, SW_SHOW); } break; case IDC_RCWD: /* Prompt for directory name, and move to it on remote system. */ if (nAppState & CTRLCONNECTED) { szDataFile[0] = 0; bOk = DialogBoxParam (hInst, MAKEINTRESOURCE(IDD_FILENAME), hDlg, Dlg_File, (LONG)(LPSTR)szDataFile); if (bOk && szDataFile[0]) { QueueFtpCmd (CWD, szDataFile); } } else not_connected(); break; case IDC_RDEL: /* Prompt for filename, and delete it from remote system */ if (nAppState & CTRLCONNECTED) { szDataFile[0] = 0; bOk = DialogBoxParam (hInst, MAKEINTRESOURCE(IDD_FILENAME), hDlg, Dlg_File, (LONG)(LPSTR)szDataFile); if (bOk && szDataFile[0]) { /* If user provided a filename, send command to delete it */ QueueFtpCmd (DELE, szDataFile); } } else not_connected(); break; case IDC_RDIR: /* Get remote file directory, and display in notepad file */ if (nAppState & CTRLCONNECTED) { hDataFile = CreateLclFile (szTempFile); if (hDataFile != HFILE_ERROR) { /* Prepare to receive the incoming connection from server */ hLstnSock = InitDataConn(&stDLclName, hDlg, WSA_ASYNC+1); if (hLstnSock != INVALID_SOCKET) { if (QueueFtpCmd (PORT, 0)) /* Queue PORT, TYPE and LIST cmds */ if (QueueFtpCmd (TYPE, "A")) QueueFtpCmd (LIST, 0); } } } else not_connected(); break; case IDC_OPTIONS: DialogBox (hInst, MAKEINTRESOURCE(IDD_OPTIONS), hDlg, Dlg_Options); break; case IDABOUT: DialogBox (hInst, MAKEINTRESOURCE(IDD_ABOUT), hDlg, Dlg_About); break; case WM_DESTROY: case IDC_EXIT: SendMessage (hDlg, WM_COMMAND, IDC_CLOSE, 0L); if (hLogFile != HFILE_ERROR) _lclose(hLogFile); EndDialog(hDlg, msg); bRet = TRUE; break; default: break; } /* end case WM_COMMAND: */ break; case WM_INITDIALOG: hWinMain = hDlg; /* save our main window handle */ /* Assign an icon to dialog box */ #ifdef WIN32 SetClassLong (hDlg, GCL_HICON, (LONG) LoadIcon((HINSTANCE) GetWindowLong(hDlg, GWL_HINSTANCE), __TEXT("AC_FTP"))); #else SetClassWord(hDlg,GCW_HICON,(WORD)LoadIcon(hInst,MAKEINTRESOURCE(AC_FTP))); #endif /* Initialize FTP command structure array */ memset (astFtpCmd, 0, (sizeof(struct stFtpCmd))*MAX_CMDS); /* Display current working directory */ getcwd (szDataFile, MAXFILENAME-1); SetDlgItemText (hDlg, IDC_LPWD, szDataFile); /* Open logfile, if logging enabled */ if (bLogFile) { hLogFile = _lcreat (szLogFile, 0); if (hLogFile == HFILE_ERROR) { MessageBox (hWinMain, "Unable to open logfile", "File Error", MB_OK | MB_ICONASTERISK); bLogFile = FALSE; } } /* Center dialog box */ CenterWnd (hDlg, NULL, TRUE); break; default: break; } /* end switch (msg) */ return (bRet); } /* end Dlg_Main() */ /*--------------------------------------------------------------------- * Function: Dlg_Login * * Description: Prompt for destination host, user name, and password */ BOOL CALLBACK Dlg_Login ( HWND hDlg, UINT msg, UINT wParam, LPARAM lParam) { BOOL bRet = FALSE; lParam = lParam; /* avoid warning */ switch (msg) { case WM_INITDIALOG: SetDlgItemText (hDlg, IDC_SERVER, szHost); SetDlgItemText (hDlg, IDC_USERNAME, szUser); SetDlgItemText (hDlg, IDC_PASSWORD, szPWrd); CenterWnd (hDlg, hWinMain, TRUE); break; case WM_COMMAND: switch (wParam) { case IDOK: GetDlgItemText (hDlg, IDC_SERVER, szHost, MAXHOSTNAME); GetDlgItemText (hDlg, IDC_USERNAME, szUser, MAXUSERNAME); GetDlgItemText (hDlg, IDC_PASSWORD, szPWrd, MAXPASSWORD); EndDialog (hDlg, TRUE); bRet = TRUE; break; case IDCANCEL: EndDialog (hDlg, FALSE); bRet = FALSE; break; default: break; } } return(bRet); } /* end Dlg_Login */ /*--------------------------------------------------------------------- * Function: Dlg_Options() * * Description: Allow user to change a number of run-time parameters * that affect the operation, to allow experimentation and debugging. */ BOOL CALLBACK Dlg_Options ( HWND hDlg, UINT msg, UINT wParam, LPARAM lParam) { BOOL bRet = FALSE; lParam = lParam; /* avoid warning */ switch (msg) { case WM_INITDIALOG: CheckDlgButton (hDlg, IDC_TO_NUL, bToNul); CheckDlgButton (hDlg, IDC_FROM_NUL,bFromNul); CheckDlgButton (hDlg, IDC_LOGFILE, bLogFile); CheckDlgButton (hDlg, IDC_IOBEEP, bIOBeep); CheckDlgButton (hDlg, IDC_REASYNC, bReAsync); CenterWnd (hDlg, hWinMain, TRUE); break; case WM_COMMAND: switch (wParam) { case IDC_TO_NUL: bToNul = !bToNul; break; case IDC_FROM_NUL: bFromNul = !bFromNul; break; case IDC_LOGFILE: bLogFile = !bLogFile; break; case IDC_IOBEEP: bIOBeep = !bIOBeep; break; case IDC_REASYNC: bReAsync = !bReAsync; break; case IDOK: if (bLogFile && hLogFile == HFILE_ERROR) { bLogFile = FALSE; } else if (!bLogFile && hLogFile != HFILE_ERROR) { _lclose(hLogFile); hLogFile = HFILE_ERROR; } else if (bLogFile && hLogFile == HFILE_ERROR) { hLogFile = _lcreat (szLogFile, 0); if (hLogFile == HFILE_ERROR) { MessageBox (hWinMain, "Unable to open logfile", "File Error", MB_OK | MB_ICONASTERISK); bLogFile = FALSE; } } EndDialog (hDlg, TRUE); bRet = TRUE; break; } } return(bRet); } /* end Dlg_Options() */ /*--------------------------------------------------------------- * Function: not_connected() * * Description: tell the user that the client needs a connection. */ void not_connected(void) { MessageBox (hWinMain, "You need to connect to an FTP Server", "Not Connected", MB_OK | MB_ICONASTERISK); } /*-------------------------------------------------------------- * Function: CloseFtpConn() * * Description: Close the connection gracefully and robustly, * reading all remaining data into buffer before closure. */ int CloseFtpConn(SOCKET *hSock, LPSTR achInBuf, int len, HWND hWnd) { char achDiscard[BUF_SIZE]; int nRet=0; if (*hSock != INVALID_SOCKET) { /* disable asynchronous notification if window handle provided */ if (hWnd) { nRet = WSAAsyncSelect(*hSock, hWnd, 0, 0); if (nRet == SOCKET_ERROR) WSAperror (WSAGetLastError(), "CloseFtpConn() WSAAsyncSelect()", hInst); } /* Half-close the connection to close neatly */ nRet = shutdown (*hSock, 1); if (nRet == SOCKET_ERROR) { WSAperror (WSAGetLastError(), "shutdown()", hInst); } else { /* Read and discard remaining data (until EOF or error) */ nRet = 1; while (nRet && (nRet != SOCKET_ERROR)) { if (achInBuf) nRet = RecvData(*hSock, hDataFile, achInBuf, len); else nRet = recv (*hSock, (LPSTR)achDiscard, BUF_SIZE, 0); } /* close the socket, and ignore any error * (since we can't do much about them anyway */ closesocket (*hSock); } *hSock = INVALID_SOCKET; /* we always invalidate socket */ } return (nRet); } /* end CloseFtpConn() */