/****************************************************************************** Source File: ftp_client.C Description: FTP Client Application Author(s): Ben Teitelbaum Class: UW/CS640 Modifications: $Log: ftp_client.C,v $ ******************************************************************************/ #include #include "atm-garb2.h" #include "ftp_client.h" // To start an FTP client application a thread must be created running // ftpClientMain(); this is like the main() for the client application. // // The single argument passed in is an identifying name used for // debugging purposees. void* ftpClientMain(void* cName) { // Client name helps disambiguate multiple client output assert(strlen((char*) cName) <= MAX_THREAD_NAME_LEN); char thrName[MAX_THREAD_NAME_LEN + 1]; char curDir[_POSIX_PATH_MAX + 1]; // Initialize curDir and oldDir pthread_mutex_lock(&chDirLock); getcwd(curDir, (size_t)(_POSIX_PATH_MAX + 1)); pthread_mutex_unlock(&chDirLock); strcpy(thrName, (char*)cName); ftp_cmd_t nextCmd; // Next command from the luser char* arg; // Pointer to the cmd arg, if there is one char* arg2; // Pointer to second cmd arg, if there is one int windowSize; // Int value of arg if cmd is: // setwindow [n] ifstream* sendFile = NULL; // File stream for outgoing file ofstream* recvFile = NULL; // File stream for incoming file aal7_sap serverAddr; // Address of FTP Server serverAddr.servID = FTPD_SERVICE_ID; // Only interested in the // 'well-known' FTP servid int connectDesc = FTP_DISCONNECTED; // Connection descriptor identifying // the current connection int tempConnectDesc; status_t stat; // Place to store returned statuses int aal7Stat; struct timeval startTime; // Start of x-mission struct timeval endTime; // End of x-mission float elapsedSecs; msg_type_t msgType; char fileName[FTP_MAX_PAYLOAD_SIZE]; // Char buffer to hold file // name field from messages unsigned numBytesXFered; // Number of bytes sent/recv'd ftp_msg_c inMsg(thrName); // the incoming FTP message... ftp_msg_c outMsg(thrName); // the outcoming FTP message... DO_FTP_WRITE_CONSOLE("FTP client started.\n"); while(TRUE) { // Prompt the luser and get the next command // Lock the console and get the next line pthread_mutex_lock(&consoleLock); printf("[%s] -> ", thrName); fflush(stdout); pthread_mutex_unlock(&consoleLock); nextCmd = getFTPUserCommand(&arg, &arg2); if (arg != NULL) { if (*arg == '?') { DO_FTP_WRITE_CONSOLE("Invalid command syntax -- type 'help' for assistance.\n"); continue; } } switch(nextCmd) { // Handle 'open [host]' command case FTP_CMD_OPEN: // If already open, report the error if (connectDesc != FTP_DISCONNECTED) { ftpShowError("Connection already exists!\nopen failed"); break; } // Otherwise, open an FTP connection to the spec'd ATM host ID sscanf(arg, "%d", &(serverAddr.hostID)); connectDesc = aal7_connect(&serverAddr); if (connectDesc < 0) { tempConnectDesc = connectDesc; connectDesc = FTP_DISCONNECTED; switch (tempConnectDesc) { case AAL7_STAT_BAD_HOST: DO_FTP_SHOW_ERROR2("aal7_connect() returned AAL7_BAD_HOST -- host %d not available", serverAddr.hostID); break; case AAL7_STAT_BAD_SAP: DO_FTP_SHOW_ERROR2("aal7_connect() returned AAL7_BAD_SAP -- service %d not available", serverAddr.servID); break; case AAL7_STAT_NOSPACE: DO_FTP_SHOW_ERROR("aal7_connect() returned AAL7_STAT_NOSPACE -- insufficient VC-table space exists"); break; case AAL7_STAT_UNREACHABLE: DO_FTP_SHOW_ERROR("aal7_connect() returned AAL7_STAT_UNREACHABLE -- remote host unreachable due to network partition"); break; case AAL7_STAT_TIMEOUT: DO_FTP_SHOW_ERROR("aal7_connect() returned AAL7_STAT_TIMEOUT -- timed out trying to establish the connection"); break; default: // Unknown error code returned assert(FALSE); } } else { DO_FTP_WRITE_CONSOLE3("Connected to ATM host %d with descript %d\n", serverAddr.hostID , connectDesc); } break; // FTP_CMD_OPEN // Handle 'close' command case FTP_CMD_CLOSE: DO_FTP_CONNECTED_P("close failed"); // Create a CLIABORT message and send it outMsg.init(FTP_CLIABORT); DO_FTP_SEND("close failed"); // People don't seem to like having aal7_disconnect() called by // both client and server on a close. Now client just sends // disconnect request to server and server closes the connection DO_FTP_WRITE_CONSOLE2("Connection %d will be terminated by server\n", connectDesc); connectDesc = FTP_DISCONNECTED; break; // FTP_CMD_CLOSE // Wait for the ack from the server... DO_FTP_RECV("close failed"); // Examine reply message... switch(inMsg.getType()) { case FTP_CLIABORTOK: // Close down AAL7 connection switch (aal7_disconnect(connectDesc)) { case AAL7_STAT_OK: DO_FTP_WRITE_CONSOLE2("Connection %d terminated\n", connectDesc); connectDesc = FTP_DISCONNECTED; break; case AAL7_STAT_BAD: DO_FTP_SHOW_ERROR("aal7_disconnect() returned AAL7_STAT_BAD -- invalid connection descriptor - non-fatal"); // connectDesc should never be invalid !!! assert(FALSE); break; case AAL7_STAT_CLOSED: DO_FTP_SHOW_ERROR2("Connection %d already closed", connectDesc); break; default: // Unexpected reply type from atm_disconnect() assert(FALSE); } break; default: // Unexpected reply type from server assert(FALSE); } break; // FTP_CMD_CLOSE // Handle 'send [file]' command case FTP_CMD_SEND: DO_FTP_CONNECTED_P("send failed"); // If there was a sendFile open already, close it if (sendFile != NULL) sendFile->close(); // Make sure that the file exists and open it pthread_mutex_lock(&chDirLock); chdir(curDir); sendFile = new ifstream(arg); pthread_mutex_unlock(&chDirLock); if (sendFile->fail()) { DO_FTP_SHOW_ERROR2("Cannot open file: %s -- send failed", arg); break; } // Construct an FTP_SR message and send it outMsg.init(FTP_SR, arg); DO_FTP_SEND("send failed"); // Get the acknowledgement from the server DO_FTP_RECV("send failed"); switch(inMsg.getType()) { case FTP_ERR: DO_FTP_SHOW_ERROR("Server cannot receive file transfers now."); break; case FTP_SROK: // First construct and send an FTP_FILESTART message ??? // outMsg.init(FTP_FILESTART); // DO_FTP_SEND("send failed"); // Then, construct and send data messages until eof is // reached numBytesXFered = 0; gettimeofday(&startTime, NULL); while(TRUE) { // Construct the next FTP data message stat = outMsg.init(sendFile); if (stat == STAT_FAIL) { DO_FTP_SHOW_ERROR2("Problem reading file: %s -- send failed", arg); outMsg.init(FTP_CLIABORT); DO_FTP_SEND("send failed trying to send abort to server"); break; } else if (stat == STAT_EOF) { // Send the last packet DO_FTP_SEND("send failed after reading eof"); numBytesXFered += outMsg.getLength(); if (inMsg.getLength() > 0) { printf("."); fflush(stdout); } // Send EOF packet outMsg.init(FTP_EOF); DO_FTP_SEND("send failed trying to send FTP_EOF signal"); // Wait for FTP_EOFOK acknowledgement from server // If server goes down at this point, we have a // potential race condition. We really should define // whether an atm_recv() over a dead connection will // eventually time out at the AAL7 level, or whether // it is the responsability of FTP to time out. !!! DO_FTP_RECV("send failed to get EOFOK from server"); msgType = inMsg.getType(); if (msgType == FTP_EOFOK) { gettimeofday(&endTime, NULL); elapsedSecs = (float)((endTime.tv_sec - startTime.tv_sec) + (endTime.tv_usec - startTime.tv_usec)/1000000.0); ftpShowMsg("\nSent %d bytes at %.2f bytes/sec\n", numBytesXFered, (float)numBytesXFered/ elapsedSecs); } else { // Unexpected message returned by DO_FTP_RECV DO_FTP_SHOW_ERROR2("Got unexpected message type %d", msgType); assert(FALSE); } break; } else if (stat == STAT_OK) { DO_FTP_SEND("send failed"); numBytesXFered += outMsg.getLength(); printf("."); fflush(stdout); } else { // Unexpected stat returned by init DO_FTP_SHOW_ERROR2("Got unexpected stat from init %d", stat); assert(FALSE); } } break; default: // Unexpected message type assert(FALSE); } break; // FTP_CMD_SEND // Handle 'get [file]' command case FTP_CMD_GET: DO_FTP_CONNECTED_P("get failed"); // NOTE: File xfers that fail may leave the partially received // file on disk -- wrong semantics ??? // Construct an FTP_GR message and send it outMsg.init(FTP_GR, arg); DO_FTP_SEND("get failed"); // Get the acknowledgement from the server DO_FTP_RECV("get failed"); switch(inMsg.getType()) { case FTP_ERRFNAME: DO_FTP_SHOW_ERROR2("File: %s is not available from the server", arg); break; case FTP_FILESTART: // Open the recvFile to receive the transfer pthread_mutex_lock(&chDirLock); chdir(curDir); recvFile = new ofstream(arg); // Check for err !!!! pthread_mutex_unlock(&chDirLock); // Grab the consoleLock for the entire ls pthread_mutex_lock(&consoleLock); numBytesXFered = 0; gettimeofday(&startTime, NULL); while(TRUE) { DO_FTP_RECV("get failed"); if (inMsg.getType() == FTP_EOF) { // Append the data packet payload to the receive file // and break; (*recvFile) << inMsg; // Check error !!! numBytesXFered += inMsg.getLength(); if (inMsg.getLength() > 0) { printf("."); fflush(stdout); } gettimeofday(&endTime, NULL); elapsedSecs = (float)((endTime.tv_sec - startTime.tv_sec) + (endTime.tv_usec - startTime.tv_usec)/1000000.0); ftpShowMsg("\nReceived %d bytes at %.2f bytes/sec\n", numBytesXFered, (float)numBytesXFered/ elapsedSecs); pthread_mutex_unlock(&consoleLock); break; } else if (inMsg.getType() == FTP_ERR) { pthread_mutex_unlock(&consoleLock); DO_FTP_SHOW_ERROR("File I/O problem at server -- connection down"); recvFile->close(); break; } else if (inMsg.getType() == FTP_DATA) { // Append the data packet payload to the receive file (*recvFile) << inMsg; numBytesXFered += inMsg.getLength(); printf("."); fflush(stdout); } else { // Unexpected message type assert(FALSE); } } // Close recvFile and delete recvFile->close(); delete recvFile; break; default: // Unexpected message type assert(FALSE); } break; // FTP_CMD_GET // Handle 'cd [dir]' command case FTP_CMD_CD: DO_FTP_CONNECTED_P("cd failed"); // Create a CDR message and send it outMsg.init(FTP_CDR, arg); DO_FTP_SEND("cd failed"); // Get the reply from the server... DO_FTP_RECV("cd failed"); // Extract FNAME field inMsg.GetPayload(fileName); switch(inMsg.getType()) { case FTP_CDOK: DO_FTP_WRITE_CONSOLE2("OK, directory is now: %s\n", fileName); break; case FTP_ERRFNAME: DO_FTP_SHOW_ERROR("Could not change directory -- perhaps directory does not exist."); break; default: // Unexpected reply type from server assert(FALSE); } break; // FTP_CMD_CD // Handle 'lcd [dir]' command case FTP_CMD_LCD: pthread_mutex_lock(&chDirLock); chdir(curDir); if (chdir(arg) != 0) { pthread_mutex_unlock(&chDirLock); DO_FTP_SHOW_ERROR2("Cannot change to local directory %s", arg); } getcwd(curDir, (size_t)(_POSIX_PATH_MAX + 1)); pthread_mutex_unlock(&chDirLock); break; // FTP_CMD_LCD // Handle 'ls' command case FTP_CMD_LS: DO_FTP_CONNECTED_P("ls failed"); // Create a LSR message and send it outMsg.init(FTP_LSR); DO_FTP_SEND("ls failed"); // Wait for the ack from the server... DO_FTP_RECV("ls failed"); switch(inMsg.getType()) { case FTP_ERR: DO_FTP_SHOW_ERROR("Server cannot list current directory."); break; case FTP_FILESTART: // Grab the consoleLock for the entire ls pthread_mutex_lock(&consoleLock); while(TRUE) { // Can't call DO_FTP_RECV("ls failed") because we are // holding consoleLock. // Receive next message, handling errors appropriately aal7Stat = inMsg.init(connectDesc); if (aal7Stat < 0) { switch (aal7Stat) { case AAL7_STAT_UNREACHABLE: ftpShowError("[%s] aal7_recv() returned AAL7_STAT_UNREACHABLE for connection %d", thrName, connectDesc); break; case AAL7_STAT_BAD: // Something bad happened -- connectDesc should never be invalid ftpShowError("[%s] aal7_recv() returned AAL7_STAT_BAD for connection %d", thrName, connectDesc); break; case AAL7_STAT_CLOSED: ftpShowError("[%s] aal7_recv() returned AAL7_STAT_CLOSED for connection %d", thrName, connectDesc); break; default: ftpShowError("[%s] aal7_recv() returned an unexpected error code", thrName); assert(FALSE); } ftpShowError("ls failed"); connectDesc = FTP_DISCONNECTED; pthread_mutex_unlock(&consoleLock); break; } // Next message received, no errors encountered if (inMsg.getType() == FTP_EOF) { pthread_mutex_unlock(&consoleLock); break; } else if (inMsg.getType() == FTP_DATA) { // Write the payload to standard output cout << inMsg << endl; } else { // Unexpected message type assert(FALSE); } } break; default: // Unexpected message type assert(FALSE); } break; // FTP_CMD_LS // Handle 'setwindow [n]' command case FTP_CMD_SET_WINDOW: sscanf(arg, "%d", &windowSize); switch(aal7_setMaxRecvWinSize(windowSize)) { case AAL7_STAT_OK: DO_FTP_WRITE_CONSOLE2("AAL7 window size set to: %d", windowSize); break; case AAL7_STAT_BAD: DO_FTP_SHOW_ERROR("aal7_setMaxRecvWinSize() returned AAL7_STAT_BAD -- invalid window size"); break; case AAL7_STAT_FAIL: DO_FTP_SHOW_ERROR("aal7_setMaxRecvWinSize() returned AAL7_STAT_FAIL -- some error occurred"); break; default: // Unexpected stat returned from atm_setAAL7credits assert(FALSE); } break; // FTP_CMD_SET_WINDOW // Handle 'help' command case FTP_CMD_HELP: showFTPUsage(); break; // FTP_CMD_HELP // Print garbler stats case FTP_CMD_GARB_STATS: garb_secret_print_stats(911); break; // FTP_CMD_GARB_STATS // Set the garbler probabilities case FTP_CMD_SET_GARB: { char* probString[5]; probString[0] = "NORMAL_DISCARD_PROBABILITY"; probString[1] = "NORMAL_CORRUPT_PROBABILITY"; probString[2] = "NORMAL_TRAILER_PROBABILITY"; probString[3] = "ROUTING_DISCARD_PROBABILITY"; probString[4] = "ROUTING_CORRUPT_PROBABILITY"; int whichProb = atoi(arg); int retVal; float val; sscanf(arg2, "%f", &val); retVal = garb_set_prob((GarbProbability)whichProb, val); DO_FTP_WRITE_CONSOLE3("Setting %s to %f", probString[whichProb], val); if (retVal == -1) { DO_FTP_SHOW_ERROR("garb_set_prob() failed"); } } break; // FTP_CMD_SET_GARB // Silly scheme to verify that 640 students didn't spoof FTP case FTP_CMD_VERIFY: { float o=0.075,h=1.5,T,r,O,l,I;int _,L=80,s=3200;for(;s%L||(h-=o,T= -2),s;4 -(r=O*O)<(l=I*I)|++ _==L&&v1(1,(--s%L?_