/*++ Copyright (c) 1994 Microsoft Corporation Module Name: cmds.c Abstract: FTP commands Author: Richard L Firth (rfirth) 03-Nov-1995 Revision History: 03-Nov-1995 rfirth Created --*/ #include "ftpcatp.h" // // manifests // #define MAX_ARGV 20 #define COMMAND_WHITESPACE TEXT(" ,\r\n") // // external functions // extern BOOL Prompt( IN LPCTSTR pszPrompt, OUT LPTSTR* ppszCommand ); // // prototypes // BOOL dbgbreak(HINTERNET, int, PTCHAR *); BOOL chdir(HINTERNET, int, PTCHAR *); BOOL del(HINTERNET, int, PTCHAR *); BOOL dir(HINTERNET, int, PTCHAR *); BOOL get(HINTERNET, int, PTCHAR *); BOOL help(HINTERNET, int, PTCHAR *); BOOL lcd(HINTERNET, int, char**); BOOL mkdir(HINTERNET, int, PTCHAR *); BOOL put(HINTERNET, int, PTCHAR *); BOOL pwd(HINTERNET, int, PTCHAR *); BOOL quit(HINTERNET, int, PTCHAR *); BOOL rb(HINTERNET, int, char**); BOOL rename_file(HINTERNET, int, PTCHAR *); BOOL rmdir(HINTERNET, int, PTCHAR *); BOOL wb(HINTERNET, int, char**); BOOL toggle_verbose(HINTERNET, int, PTCHAR *); BOOL set_type(HINTERNET, int, PTCHAR *); BOOL open_file(HINTERNET, int, PTCHAR *); BOOL close_file(HINTERNET, int, PTCHAR *); BOOL read_file(HINTERNET, int, PTCHAR *); BOOL write_file(HINTERNET, int, PTCHAR *); BOOL DispatchCommand(LPTSTR, HINTERNET); #if DBG BOOL CheckHandles(HINTERNET, int, PTCHAR*); #endif // // external data // extern DWORD Verbose; extern INTERNET_STATUS_CALLBACK PreviousCallback; extern HINTERNET hCancel; extern BOOL AsyncMode; extern HANDLE AsyncEvent; extern DWORD AsyncResult; extern DWORD AsyncError; extern BOOL UseQueryData; // // data // typedef struct { LPCSTR pszCommand; LPCSTR HelpText; BOOL (*fn)(HINTERNET, int, PTCHAR []); } COMMAND_ENTRY; COMMAND_ENTRY Commands[] = { #if DBG {"b", "Break into debugger", dbgbreak}, #endif {"!", "Shell escape", NULL}, {"?", "This list", help}, {"cd", "Change to a remote directory", chdir}, {"close", "Close an open file handle", close_file}, {"dir", "List a directory", dir}, {"del", "Delete a remote file", del}, {"get", "Copy a file from the server", get}, #if DBG {"hndl", "Get current handle count", CheckHandles}, #endif {"lcd", "Change local directory", lcd}, {"md", "Create a remote directory", mkdir}, {"open", "Open a file for read or write", open_file}, {"put", "Copy a file to the server", put}, {"pwd", "Display the current directory", pwd}, {"quit", "Terminate this program", quit}, {"rb", "Get/set Read buffer size", rb}, {"rd", "Remove a remote directory", rmdir}, {"read", "Read data from a file", read_file}, {"ren", "Rename a remote file", rename_file}, {"type", "Set transfer type", set_type}, {"verbose", "Toggle verbose mode", toggle_verbose}, {"wb", "Get/set Write buffer size", wb}, {"write", "Write data to a file", write_file}, {NULL, NULL, NULL} }; BOOL fQuit = FALSE; DWORD CacheFlags = 0; HINTERNET FileHandle = NULL; // // functions // void get_response(HINTERNET hFtp) { DWORD buflen; char buffer[2048]; DWORD category; buflen = sizeof(buffer); if (InternetGetLastResponseInfo(&category, buffer, &buflen)) { if (hFtp && (Verbose >= 2)) { DWORD len = sizeof(DWORD); DWORD dwFlags; if (InternetQueryOption(hFtp, INTERNET_OPTION_REQUEST_FLAGS, &dwFlags, &len)) { if (dwFlags & INTERNET_REQFLAG_FROM_CACHE) { fprintf(stderr, "****** Got from the cache ***** \n"); } else { fprintf(stderr, "****** From the wire ***** \n"); } } } if (buflen || (Verbose >= 2)) { print_response(buffer, buflen, TRUE); } } else { DWORD error; error = GetLastError(); if (Verbose || (error != ERROR_INSUFFICIENT_BUFFER)) { LPSTR errString; errString = (error == ERROR_INSUFFICIENT_BUFFER) ? "InternetGetLastResponseInfo() returns error %d (buflen = %d)\n" : "InternetGetLastResponseInfo() returns error %d\n" ; printf(errString, error, buflen); } if (error = ERROR_INSUFFICIENT_BUFFER) { LPSTR errbuf; if ((errbuf = malloc(buflen)) == NULL) { printf("error: get_response: malloc(%d) failed\n", buflen); return; } if (InternetGetLastResponseInfo(&category, errbuf, &buflen)) { if (buflen || (Verbose >= 2)) { print_response(errbuf, buflen, TRUE); } } else { printf("error: get_response: InternetGetLastResponseInfo() returns error %d (buflen = %d)\n", GetLastError(), buflen ); } free(errbuf); } } } BOOL quit( IN HINTERNET hFtpSession, IN int argc, IN PTCHAR argv[] ) { fQuit = TRUE; return TRUE; } BOOL get( IN HINTERNET hFtpSession, IN int argc, IN PTCHAR argv[] ) { LPTSTR pszFilename; LPTSTR pszLocalfile; BOOL ok; if (argc < 2) { if (!Prompt(TEXT("remote-name: "), &pszFilename)) { return FALSE; } } else { pszFilename = argv[1]; } if (argc >= 3) { pszLocalfile = argv[2]; } else { pszLocalfile = pszFilename; } ok = FtpGetFile(hFtpSession, pszFilename, pszLocalfile, FALSE, FILE_ATTRIBUTE_NORMAL, CacheFlags | FTP_TRANSFER_TYPE_BINARY, FTPCAT_GET_CONTEXT ); if (AsyncMode && !ok) { if (GetLastError() != ERROR_IO_PENDING) { print_error("get", "FtpGetFile()"); } else { if (Verbose) { printf("waiting for async FtpGetFile()...\n"); } WaitForSingleObject(AsyncEvent, INFINITE); ok = (BOOL)AsyncResult; } } if (!ok) { if (AsyncMode) { SetLastError(AsyncError); } print_error("get", "%sFtpGetFile()", AsyncMode ? "async " : ""); } else { get_response(hFtpSession); } return ok; } BOOL put( IN HINTERNET hFtpSession, IN int argc, IN PTCHAR argv[] ) { LPTSTR pszFilename; LPTSTR pszLocalfile; BOOL ok; if (argc < 2) { if (!Prompt(TEXT("remote-name: "), &pszFilename)) { return FALSE; } } else { pszFilename = argv[1]; } if (argc >= 3) { pszLocalfile = argv[2]; } else { pszLocalfile = pszFilename; } ok = FtpPutFile(hFtpSession, pszLocalfile, pszFilename, FTP_TRANSFER_TYPE_BINARY, FTPCAT_PUT_CONTEXT ); if (AsyncMode && !ok) { if (GetLastError() != ERROR_IO_PENDING) { print_error("put", "FtpPutFile()"); } else { if (Verbose) { printf("waiting for async FtpPutFile()...\n"); } WaitForSingleObject(AsyncEvent, INFINITE); ok = (BOOL)AsyncResult; } } if (!ok) { if (AsyncMode) { SetLastError(AsyncError); } print_error("put", "%sFtpPutFile()", AsyncMode ? "async " : ""); } else { get_response(hFtpSession); } return ok; } BOOL rename_file( IN HINTERNET hFtpSession, IN int argc, IN PTCHAR argv[] ) { LPTSTR pszTemp; LPTSTR pszOldFilename; LPTSTR pszNewFilename; BOOL ok; if (argc < 2) { if (!Prompt(TEXT("Old name: "), &pszTemp)) { return FALSE; } pszOldFilename = lstrdup(pszTemp); if (pszOldFilename == NULL) { return FALSE; } } else { pszOldFilename = argv[1]; } if (argc < 3) { if (!Prompt(TEXT("New name: "), &pszNewFilename)) { return FALSE; } } else { pszNewFilename = argv[2]; } ok = FtpRenameFile(hFtpSession, pszOldFilename, pszNewFilename ); if (AsyncMode && !ok) { if (GetLastError() != ERROR_IO_PENDING) { print_error("rename_file", "FtpRenameFile()"); } else { if (Verbose) { printf("waiting for async FtpRenameFile()...\n"); } WaitForSingleObject(AsyncEvent, INFINITE); ok = (BOOL)AsyncResult; } } if (!ok) { if (AsyncMode) { SetLastError(AsyncError); } print_error("rename_file", "%sFtpRenameFile()", AsyncMode ? "async " : ""); } else { get_response(hFtpSession); } if (argc < 2) { LocalFree(pszOldFilename); } return ok; } BOOL del( IN HINTERNET hFtpSession, IN int argc, IN PTCHAR argv[] ) { LPTSTR pszFilename; BOOL ok; if (argc < 2) { if (!Prompt(TEXT("File name: "), &pszFilename)) { return FALSE; } } else { pszFilename = argv[1]; } ok = FtpDeleteFile(hFtpSession, pszFilename); if (AsyncMode && !ok) { if (GetLastError() != ERROR_IO_PENDING) { print_error("del", "FtpDeleteFile()"); } else { if (Verbose) { printf("waiting for async FtpDeleteFile()...\n"); } WaitForSingleObject(AsyncEvent, INFINITE); ok = (BOOL)AsyncResult; } } if (!ok) { if (AsyncMode) { SetLastError(AsyncError); } print_error("del", "%sFtpDeleteFile()", AsyncMode ? "async " : "" ); } else { get_response(hFtpSession); } return ok; } BOOL mkdir( IN HINTERNET hFtpSession, IN int argc, IN PTCHAR argv[] ) { LPTSTR pszDirname; BOOL ok; if (argc < 2) { if (!Prompt(TEXT("Directory name: "), &pszDirname)) { return FALSE; } } else { pszDirname = argv[1]; } ok = FtpCreateDirectory(hFtpSession, pszDirname ); if (AsyncMode && !ok) { if (GetLastError() != ERROR_IO_PENDING) { print_error("mkdir", "FtpCreateDirectory()"); } else { if (Verbose) { printf("waiting for async FtpCreateDirectory()...\n"); } WaitForSingleObject(AsyncEvent, INFINITE); ok = (BOOL)AsyncResult; } } if (!ok) { if (AsyncMode) { SetLastError(AsyncError); } print_error("mkdir", "%sFtpCreateDirectory()", AsyncMode ? "async " : ""); } else { get_response(hFtpSession); } return ok; } BOOL chdir( IN HINTERNET hFtpSession, IN int argc, IN PTCHAR argv[] ) { LPTSTR pszDirname; BOOL ok; if (argc < 2) { if (!Prompt(TEXT("Directory name: "), &pszDirname)) { return FALSE; } } else { pszDirname = argv[1]; } ok = FtpSetCurrentDirectory(hFtpSession, pszDirname ); if (AsyncMode && !ok) { if (GetLastError() != ERROR_IO_PENDING) { print_error("chdir", "FtpSetCurrentDirectory()"); } else { if (Verbose) { printf("waiting for async FtpSetCurrentDirectory()...\n"); } WaitForSingleObject(AsyncEvent, INFINITE); ok = (BOOL)AsyncResult; } } if (!ok) { if (AsyncMode) { SetLastError(AsyncError); } print_error("chdir", "%sFtpSetCurrentDirectory()", AsyncMode ? "async " : ""); } else { get_response(hFtpSession); } return ok; } BOOL rmdir( IN HINTERNET hFtpSession, IN int argc, IN PTCHAR argv[] ) { LPTSTR pszDirname; BOOL ok; if (argc < 2) { if (!Prompt(TEXT("Directory name: "), &pszDirname)) { return FALSE; } } else { pszDirname = argv[1]; } ok = FtpRemoveDirectory(hFtpSession, pszDirname ); if (AsyncMode && !ok) { if (GetLastError() != ERROR_IO_PENDING) { print_error("rmdir", "FtpRemoveDirectory()"); } else { if (Verbose) { printf("waiting for async FtpRemoveDirectory()...\n"); } WaitForSingleObject(AsyncEvent, INFINITE); ok = (BOOL)AsyncResult; } } if (!ok) { if (AsyncMode) { SetLastError(AsyncError); } print_error("rmdir", "%sFtpRemoveDirectory()", AsyncMode ? "async " : ""); } else { get_response(hFtpSession); } return ok; } BOOL dir( IN HINTERNET hFtpSession, IN int argc, IN PTCHAR argv[] ) { BOOL ok; WIN32_FIND_DATA ffd; SYSTEMTIME st; LPTSTR pszFileSpec; TCHAR EmptyExpression[] = ""; HINTERNET hFind; HINTERNET hPrevious; static LPSTR month[] = { "", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; if (argc < 2) { pszFileSpec = EmptyExpression; } else { pszFileSpec = argv[1]; } hFind = FtpFindFirstFileA(hFtpSession, pszFileSpec, &ffd, CacheFlags, // dwFlags FTPCAT_FIND_CONTEXT ); if (AsyncMode && (hFind == NULL)) { if (GetLastError() == ERROR_IO_PENDING) { if (Verbose) { printf("waiting for async FtpFindFirstFile()...\n"); } WaitForSingleObject(AsyncEvent, INFINITE); hFind = (HINTERNET)AsyncResult; if (hFind == NULL) { SetLastError(AsyncError); } } } if (hFind == NULL) { print_error("dir", "%sFtpFindFirstFile()", AsyncMode ? "async " : ""); return FALSE; } hPrevious = hCancel; hCancel = hFind; get_response(hFind); putchar('\n'); ok = TRUE; while (ok) { if (!FileTimeToSystemTime(&ffd.ftLastWriteTime, &st)) { printf("| ftLastWriteTime = ERROR\n"); } printf("%02d-%s-%04d %02d:%02d:%02d %15d bytes %-s%-s%-s%-s%-s%-s %s\n", st.wDay, month[st.wMonth], st.wYear, st.wHour, st.wMinute, st.wSecond, ffd.nFileSizeLow, (ffd.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE) ? "Archive " : "", (ffd.dwFileAttributes & FILE_ATTRIBUTE_NORMAL) ? "Normal " : "", (ffd.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM) ? "System " : "", (ffd.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) ? "Hidden " : "", (ffd.dwFileAttributes & FILE_ATTRIBUTE_READONLY) ? "ReadOnly " : "", (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ? "Directory " : "", ffd.cFileName ); if (UseQueryData) { DWORD error; DWORD avail; ok = InternetQueryDataAvailable(hFind, &avail, 0, 0); if (!ok) { error = GetLastError(); if (error == ERROR_IO_PENDING) { if (Verbose) { printf("waiting for async InternetQueryDataAvailable()...\n"); } WaitForSingleObject(AsyncEvent, INFINITE); ok = (BOOL)AsyncResult; SetLastError(AsyncError); } } if (!ok) { print_error("dir", "%sSYNC InternetQueryDataAvailable()", AsyncMode ? "A" : ""); break; } if (Verbose) { printf("InternetQueryDataAvailable() returns %d available\n", avail); } if (avail == 0) { break; } } ok = InternetFindNextFile(hFind, &ffd); if (!ok && AsyncMode) { if (GetLastError() == ERROR_IO_PENDING) { if (Verbose) { printf("waiting for async InternetFindNextFile()...\n"); } WaitForSingleObject(AsyncEvent, INFINITE); ok = (BOOL)AsyncResult; if (!ok) { SetLastError(AsyncError); } } } if (!ok) { if (GetLastError() != ERROR_NO_MORE_FILES) { print_error("dir", "%sInternetFindNextFile()", AsyncMode ? "async " : ""); break; } } } putchar('\n'); close_handle(hFind); hCancel = hPrevious; return ok; } BOOL pwd( IN HINTERNET hFtpSession, IN int argc, IN PTCHAR argv[] ) { BOOL ok; char* buf; DWORD len; len = 0; ok = FtpGetCurrentDirectory(hFtpSession, NULL, &len); if (AsyncMode && !ok) { if (GetLastError() != ERROR_IO_PENDING) { print_error("pwd", "async FtpGetCurrentDirectory()"); } else { if (Verbose) { printf("waiting for async FtpGetCurrentDirectory()...\n"); } WaitForSingleObject(AsyncEvent, INFINITE); ok = (BOOL)AsyncResult; SetLastError(AsyncError); } } if (ok) { printf("error: FtpGetCurrentDirectory() w/ no buffer returns ok\n"); return FALSE; } else if (Verbose) { printf("FtpGetCurrentDirectory() returns %d, %d bytes in cur dir\n", GetLastError(), len ); } buf = (char*)malloc(len); ok = FtpGetCurrentDirectory(hFtpSession, buf, &len); if (AsyncMode && !ok) { if (GetLastError() != ERROR_IO_PENDING) { print_error("pwd", "async FtpGetCurrentDirectory()"); } else { if (Verbose) { printf("waiting for async FtpGetCurrentDirectory()...\n"); } WaitForSingleObject(AsyncEvent, INFINITE); ok = (BOOL)AsyncResult; SetLastError(AsyncError); } } if (!ok) { print_error("pwd", "%sFtpGetCurrentDirectory()", AsyncMode ? "async " : ""); } else { get_response(hFtpSession); lprintf(TEXT("Current directory: %s\n"), buf); } free(buf); return ok; } BOOL help(IN HINTERNET hFtpSession, IN int argc, IN PTCHAR argv[]) { int i; for (i = 0; Commands[i].pszCommand != NULL; ++i) { lprintf(TEXT("\t%s\t%s\n"), Commands[i].pszCommand, Commands[i].HelpText ); } return TRUE; } #if DBG BOOL CheckHandles(HINTERNET hFtpSession, int argc, PTCHAR argv[]) { printf("handle count = %d\n", GetProcessHandleCount()); return TRUE; } #endif BOOL lcd(HINTERNET hInternet, int argc, char** argv) { char curDir[MAX_PATH + 1]; DWORD curDirLen; if (argc == 2) { if (!SetCurrentDirectory(argv[1])) { print_error("lcd", "SetCurrentDirectory()"); return FALSE; } } else if (argc != 1) { printf("error: lcd: incorrect number of arguments\n"); return FALSE; } curDirLen = sizeof(curDir); if (GetCurrentDirectory(curDirLen, curDir)) { printf("Current directory is %s\n", curDir); return TRUE; } else { print_error("lcd", "GetCurrentDirectory()"); return FALSE; } } BOOL rb(HINTERNET hInternet, int argc, char** argv) { DWORD value; DWORD valueLength; if (argc > 1) { value = atoi(argv[1]); if (!InternetSetOption(hInternet, INTERNET_OPTION_READ_BUFFER_SIZE, (LPVOID)&value, sizeof(DWORD) )) { print_error("rb", "InternetSetOption()"); return FALSE; } } valueLength = sizeof(value); if (InternetQueryOption(hInternet, INTERNET_OPTION_READ_BUFFER_SIZE, (LPVOID)&value, &valueLength )) { printf("Read buffer size = %d bytes\n", value); return TRUE; } else { print_error("rb", "InternetQueryOption()"); return FALSE; } } BOOL wb(HINTERNET hInternet, int argc, char** argv) { DWORD value; DWORD valueLength; if (argc > 1) { value = atoi(argv[1]); if (!InternetSetOption(hInternet, INTERNET_OPTION_WRITE_BUFFER_SIZE, (LPVOID)&value, sizeof(DWORD) )) { print_error("wb", "InternetSetOption()"); return FALSE; } } valueLength = sizeof(value); if (InternetQueryOption(hInternet, INTERNET_OPTION_WRITE_BUFFER_SIZE, (LPVOID)&value, &valueLength )) { printf("Write buffer size = %d bytes\n", value); return TRUE; } else { print_error("wb", "InternetQueryOption()"); return FALSE; } } BOOL toggle_verbose(HINTERNET hInternet, int argc, PTCHAR * argv) { static DWORD PreviousVerbose = 0; if (Verbose) { PreviousVerbose = Verbose; Verbose = 0; } else { Verbose = PreviousVerbose; if (Verbose == 0) { Verbose = 1; } } printf("Verbose mode is o%s\n", Verbose ? "n" : "ff"); return TRUE; } BOOL toggle_callback(HINTERNET hInternet, int argc, PTCHAR * argv) { INTERNET_STATUS_CALLBACK callback; if (PreviousCallback != NULL && PreviousCallback != my_callback) { printf("error: PreviousCallback %x not recognized\n", PreviousCallback); } else { PreviousCallback = InternetSetStatusCallback(hInternet, PreviousCallback); if (PreviousCallback == INTERNET_INVALID_STATUS_CALLBACK) { print_error("toggle_callback", "InternetSetStatusCallback()"); } else if (PreviousCallback != NULL && PreviousCallback != my_callback) { printf("error: PreviousCallback %x not recognized\n", PreviousCallback); } else if (Verbose) { printf("callback toggled Ok\n"); } } printf("Verbose mode is o%s\n", Verbose ? "n" : "ff"); return TRUE; } BOOL dbgbreak(HINTERNET hInternet, int argc, PTCHAR * argv) { DebugBreak(); return TRUE; } BOOL set_type(HINTERNET hInternet, int argc, PTCHAR * argv) { return TRUE; } BOOL open_file(HINTERNET hInternet, int argc, PTCHAR * argv) { HINTERNET hFile; BOOL bOk; if (argc < 2) { printf("error: required filename missing\n"); return FALSE; } hFile = FtpOpenFile(hInternet, argv[1], GENERIC_READ, 0, AsyncMode ? FTPCAT_OPEN_CONTEXT : 0 ); if (AsyncMode && !hFile) { if (GetLastError() != ERROR_IO_PENDING) { print_error("open_file", "async FtpOpenFile()"); return FALSE; } if (Verbose) { printf("waiting for async FtpOpenFile()...\n"); } WaitForSingleObject(AsyncEvent, INFINITE); hFile = (HINTERNET)AsyncResult; SetLastError(AsyncError); } if (!hFile) { print_error("open_file", "%sFtpOpenFile()", AsyncMode ? "async " : ""); } else { get_response(hInternet); printf("returned handle is %#x\n", hFile); } return hFile != NULL; } BOOL close_file(HINTERNET hInternet, int argc, PTCHAR * argv) { HINTERNET hFile; BOOL bOk; if (argc < 2) { printf("error: required handle missing\n"); return FALSE; } hFile = (HINTERNET)strtol(argv[1], NULL, 0); bOk = InternetCloseHandle(hFile); if (!bOk) { print_error("close_file", "InternetCloseHandle()"); } else if (Verbose) { printf("handle %#x closed OK\n", hFile); } return bOk; } BOOL read_file(HINTERNET hInternet, int argc, PTCHAR * argv) { return TRUE; } BOOL write_file(HINTERNET hInternet, int argc, PTCHAR * argv) { return TRUE; } BOOL DispatchCommand( IN LPTSTR pszCommand, IN HINTERNET hFtpSession ) { COMMAND_ENTRY *pce; PTCHAR ArgV[MAX_ARGV]; int index; int state; if (*pszCommand == TEXT('!')) { LPSTR shellPath; shellPath = getenv("COMSPEC"); if (shellPath == NULL) { printf("error: COMSPEC environment variable not set\n"); return FALSE; } ++pszCommand; while (isspace(*pszCommand)) { ++pszCommand; } if (*pszCommand != TEXT('\0')) { _spawnlp(_P_WAIT, shellPath, "/C", pszCommand, NULL); } else { printf("\nSpawning command interpreter. Type \"exit\" to return to FTP\n\n"); _spawnlp(_P_WAIT, shellPath, "/K", NULL); putchar('\n'); } return TRUE; } state = 0; index = 0; while (*pszCommand) { switch (state) { case 0: if (!isspace(*pszCommand)) { if (*pszCommand == '"') { state = 2; } else { state = 1; } ArgV[index++] = (state == 2) ? (pszCommand + 1) : pszCommand; } break; case 1: if (isspace(*pszCommand)) { *pszCommand = '\0'; state = 0; } break; case 2: if (*pszCommand == '"') { *pszCommand = '\0'; state = 0; } break; } ++pszCommand; } if (index == 0) { return FALSE; } for (pce = Commands; pce->pszCommand != NULL; pce++) { if (lstrcmpi(pce->pszCommand, ArgV[0]) == 0) { return pce->fn(hFtpSession, index, ArgV); } } if (!lstrcmpi(ArgV[0], "q")) { return quit(hFtpSession, index, ArgV); } printf("error: unrecognized command: \"%s\"\n", ArgV[0]); return FALSE; }