Repository: beckysag/ftp Branch: master Commit: 7fc6696c4b6f Files: 11 Total size: 21.9 KB Directory structure: gitextract_3_b0db68/ ├── README.md ├── client/ │ ├── ftclient.c │ ├── ftclient.h │ └── makefile ├── common/ │ ├── common.c │ └── common.h └── server/ ├── .auth ├── .auth~ ├── ftserve.c ├── ftserve.h └── makefile ================================================ FILE CONTENTS ================================================ ================================================ FILE: README.md ================================================ FTP Client-Server Implementation =========== Simple implementation of a file transfer program. It includes custom client and server programs that provide functionality to authenticate a user, list remote files, and retrieve remote files. ### Directory layout: ftp/ client/ ftclient.c ftclient.h makefile common/ common.c common.h server/ ftserve.c ftserve.h makefile .auth ###Usage To compile and link ftserve: ``` $ cd server/ $ make ``` To compile and link ftclient: ``` $ cd client/ $ make ``` To run ftserve: ``` $ server/ftserve PORTNO ``` To run ftclient: ``` $ client/ftclient HOSTNAME PORTNO Commands: list get quit ``` Available commands: ``` list - retrieve list of files in the current remote directory get - get the specified file quit - end the ftp session ``` Logging In: ``` Name: anonymous Password: [empty] ``` ================================================ FILE: client/ftclient.c ================================================ #include "ftclient.h" int sock_control; /** * Receive a response from server * Returns -1 on error, return code on success */ int read_reply(){ int retcode = 0; if (recv(sock_control, &retcode, sizeof retcode, 0) < 0) { perror("client: error reading message from server\n"); return -1; } return ntohl(retcode); } /** * Print response message */ void print_reply(int rc) { switch (rc) { case 220: printf("220 Welcome, server ready.\n"); break; case 221: printf("221 Goodbye!\n"); break; case 226: printf("226 Closing data connection. Requested file action successful.\n"); break; case 550: printf("550 Requested action not taken. File unavailable.\n"); break; } } /** * Parse command in cstruct */ int ftclient_read_command(char* buf, int size, struct command *cstruct) { memset(cstruct->code, 0, sizeof(cstruct->code)); memset(cstruct->arg, 0, sizeof(cstruct->arg)); printf("ftclient> "); // prompt for input fflush(stdout); // wait for user to enter a command read_input(buf, size); char *arg = NULL; arg = strtok (buf," "); arg = strtok (NULL, " "); if (arg != NULL){ // store the argument if there is one strncpy(cstruct->arg, arg, strlen(arg)); } // buf = command if (strcmp(buf, "list") == 0) { strcpy(cstruct->code, "LIST"); } else if (strcmp(buf, "get") == 0) { strcpy(cstruct->code, "RETR"); } else if (strcmp(buf, "quit") == 0) { strcpy(cstruct->code, "QUIT"); } else {//invalid return -1; } // store code in beginning of buffer memset(buf, 0, 400); strcpy(buf, cstruct->code); // if there's an arg, append it to the buffer if (arg != NULL) { strcat(buf, " "); strncat(buf, cstruct->arg, strlen(cstruct->arg)); } return 0; } /** * Do get command */ int ftclient_get(int data_sock, int sock_control, char* arg) { char data[MAXSIZE]; int size; FILE* fd = fopen(arg, "w"); while ((size = recv(data_sock, data, MAXSIZE, 0)) > 0) { fwrite(data, 1, size, fd); } if (size < 0) { perror("error\n"); } fclose(fd); return 0; } /** * Open data connection */ int ftclient_open_conn(int sock_con) { int sock_listen = socket_create(CLIENT_PORT_ID); // send an ACK on control conn int ack = 1; if ((send(sock_con, (char*) &ack, sizeof(ack), 0)) < 0) { printf("client: ack write error :%d\n", errno); exit(1); } int sock_conn = socket_accept(sock_listen); close(sock_listen); return sock_conn; } /** * Do list commmand */ int ftclient_list(int sock_data, int sock_con) { size_t num_recvd; // number of bytes received with recv() char buf[MAXSIZE]; // hold a filename received from server int tmp = 0; // Wait for server starting message if (recv(sock_con, &tmp, sizeof tmp, 0) < 0) { perror("client: error reading message from server\n"); return -1; } memset(buf, 0, sizeof(buf)); while ((num_recvd = recv(sock_data, buf, MAXSIZE, 0)) > 0) { printf("%s", buf); memset(buf, 0, sizeof(buf)); } if (num_recvd < 0) { perror("error"); } // Wait for server done message if (recv(sock_con, &tmp, sizeof tmp, 0) < 0) { perror("client: error reading message from server\n"); return -1; } return 0; } /** * Input: cmd struct with an a code and an arg * Concats code + arg into a string and sends to server */ int ftclient_send_cmd(struct command *cmd) { char buffer[MAXSIZE]; int rc; sprintf(buffer, "%s %s", cmd->code, cmd->arg); // Send command string to server rc = send(sock_control, buffer, (int)strlen(buffer), 0); if (rc < 0) { perror("Error sending command to server"); return -1; } return 0; } /** * Get login details from user and * send to server for authentication */ void ftclient_login() { struct command cmd; char user[256]; memset(user, 0, 256); // Get username from user printf("Name: "); fflush(stdout); read_input(user, 256); // Send USER command to server strcpy(cmd.code, "USER"); strcpy(cmd.arg, user); ftclient_send_cmd(&cmd); // Wait for go-ahead to send password int wait; recv(sock_control, &wait, sizeof wait, 0); // Get password from user fflush(stdout); char *pass = getpass("Password: "); // Send PASS command to server strcpy(cmd.code, "PASS"); strcpy(cmd.arg, pass); ftclient_send_cmd(&cmd); // wait for response int retcode = read_reply(); switch (retcode) { case 430: printf("Invalid username/password.\n"); exit(0); case 230: printf("Successful login.\n"); break; default: perror("error reading message from server"); exit(1); break; } } int main(int argc, char* argv[]) { int data_sock, retcode, s; char buffer[MAXSIZE]; struct command cmd; struct addrinfo hints, *res, *rp; if (argc != 3) { printf("usage: ./ftclient hostname port\n"); exit(0); } char *host = argv[1]; char *port = argv[2]; // Get matching addresses memset(&hints, 0, sizeof(struct addrinfo)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; s = getaddrinfo(host, port, &hints, &res); if (s != 0) { printf("getaddrinfo() error %s", gai_strerror(s)); exit(1); } // Find an address to connect to & connect for (rp = res; rp != NULL; rp = rp->ai_next) { sock_control = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); if (sock_control < 0) continue; if(connect(sock_control, res->ai_addr, res->ai_addrlen)==0) { break; } else { perror("connecting stream socket"); exit(1); } close(sock_control); } freeaddrinfo(rp); // Get connection, welcome messages printf("Connected to %s.\n", host); print_reply(read_reply()); /* Get name and password and send to server */ ftclient_login(); while (1) { // loop until user types quit // Get a command from user if ( ftclient_read_command(buffer, sizeof buffer, &cmd) < 0) { printf("Invalid command\n"); continue; // loop back for another command } // Send command to server if (send(sock_control, buffer, (int)strlen(buffer), 0) < 0 ) { close(sock_control); exit(1); } retcode = read_reply(); if (retcode == 221) { /* If command was quit, just exit */ print_reply(221); break; } if (retcode == 502) { // If invalid command, show error message printf("%d Invalid command.\n", retcode); } else { // Command is valid (RC = 200), process command // open data connection if ((data_sock = ftclient_open_conn(sock_control)) < 0) { perror("Error opening socket for data connection"); exit(1); } // execute command if (strcmp(cmd.code, "LIST") == 0) { ftclient_list(data_sock, sock_control); } else if (strcmp(cmd.code, "RETR") == 0) { // wait for reply (is file valid) if (read_reply() == 550) { print_reply(550); close(data_sock); continue; } ftclient_get(data_sock, sock_control, cmd.arg); print_reply(read_reply()); } close(data_sock); } } // loop back to get more user input // Close the socket (control connection) close(sock_control); return 0; } ================================================ FILE: client/ftclient.h ================================================ /* ftclient.h * * Rebecca Sagalyn * 11/15/13 * * Client side of TCP file transfer implementation, runs with custom server, * ftserve.c. Receives commands from input, and retreives list of files in current * and files. * * Valid commands: * get * list * quit * * Usage: * ./ftclient SERVER_HOSTNAME PORT# */ #ifndef FTCLIENT_H #define FTCLIENT_H #include "../common/common.h" /** * Receive a response from server * Returns -1 on error, return code on success */ int read_reply(); /** * Print response message */ void print_reply(int rc); /** * Parse command in cstruct */ int ftclient_read_command(char* buf, int size, struct command *cstruct); /** * Do get command */ int ftclient_get(int data_sock, int sock_control, char* arg); /** * Open data connection */ int ftclient_open_conn(int sock_con); /** * Do list commmand */ int ftclient_list(int sock_data, int sock_con); /** * Input: cmd struct with an a code and an arg * Concats code + arg into a string and sends to server */ int ftclient_send_cmd(struct command *cmd); /** * Get login details from user and * send to server for authentication */ void ftclient_login(); #endif ================================================ FILE: client/makefile ================================================ CC := gcc CFLAGS := -Wall -g -Os SHDIR := ../common OBJS = ftclient.o $(SHDIR)/common.o all: ftclient ftclient: $(OBJS) @$(CC) -o ftclient $(CFLAGS) $(OBJS) $(OBJS) : %.o: %.c @$(CC) -c $(CFLAGS) $< -o $@ .PHONY: clean: @rm -f *.o ftclient @rm -f ../common/*.o @echo Done cleaning ================================================ FILE: common/common.c ================================================ #include "common.h" /** * Create listening socket on remote host * Returns -1 on error, socket fd on success */ int socket_create(int port) { int sockfd; int yes = 1; struct sockaddr_in sock_addr; // create new socket if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { perror("socket() error"); return -1; } // set local address info sock_addr.sin_family = AF_INET; sock_addr.sin_port = htons(port); sock_addr.sin_addr.s_addr = htonl(INADDR_ANY); if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1) { close(sockfd); perror("setsockopt() error"); return -1; } // bind if (bind(sockfd, (struct sockaddr *) &sock_addr, sizeof(sock_addr)) < 0) { close(sockfd); perror("bind() error"); return -1; } // begin listening for incoming TCP requests if (listen(sockfd, 5) < 0) { close(sockfd); perror("listen() error"); return -1; } return sockfd; } /** * Create new socket for incoming client connection request * Returns -1 on error, or fd of newly created socket */ int socket_accept(int sock_listen) { int sockfd; struct sockaddr_in client_addr; socklen_t len = sizeof(client_addr); // Wait for incoming request, store client info in client_addr sockfd = accept(sock_listen, (struct sockaddr *) &client_addr, &len); if (sockfd < 0) { perror("accept() error"); return -1; } return sockfd; } /** * Connect to remote host at given port * Returns: socket fd on success, -1 on error */ int socket_connect(int port, char*host) { int sockfd; struct sockaddr_in dest_addr; // create socket if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { perror("error creating socket"); return -1; } // create server address memset(&dest_addr, 0, sizeof(dest_addr)); dest_addr.sin_family = AF_INET; dest_addr.sin_port = htons(port); dest_addr.sin_addr.s_addr = inet_addr(host); // Connect on socket if(connect(sockfd, (struct sockaddr *)&dest_addr, sizeof(dest_addr)) < 0 ) { perror("error connecting to server"); return -1; } return sockfd; } /** * Receive data on sockfd * Returns -1 on error, number of bytes received * on success */ int recv_data(int sockfd, char* buf, int bufsize){ size_t num_bytes; memset(buf, 0, bufsize); num_bytes = recv(sockfd, buf, bufsize, 0); if (num_bytes < 0) { return -1; } return num_bytes; } /** * Trim whiteshpace and line ending * characters from a string */ void trimstr(char *str, int n) { int i; for (i = 0; i < n; i++) { if (isspace(str[i])) str[i] = 0; if (str[i] == '\n') str[i] = 0; } } /** * Send resposne code on sockfd * Returns -1 on error, 0 on success */ int send_response(int sockfd, int rc) { int conv = htonl(rc); if (send(sockfd, &conv, sizeof conv, 0) < 0 ) { perror("error sending...\n"); return -1; } return 0; } /** * Read input from command line */ void read_input(char* buffer, int size) { char *nl = NULL; memset(buffer, 0, size); if ( fgets(buffer, size, stdin) != NULL ) { nl = strchr(buffer, '\n'); if (nl) *nl = '\0'; // truncate, ovewriting newline } } ================================================ FILE: common/common.h ================================================ #ifndef COMMON_H #define COMMON_H #include #include #include #include #include #include // getaddrinfo() #include #include #include #include #include #include #include #include /* constants */ #define DEBUG 1 #define MAXSIZE 512 // max buffer size #define CLIENT_PORT_ID 30020 /* Holds command code and argument */ struct command { char arg[255]; char code[5]; }; /** * Create listening socket on remote host * Returns -1 on error, socket fd on success */ int socket_create(int port); /** * Create new socket for incoming client connection request * Returns -1 on error, or fd of newly created socket */ int socket_accept(int sock_listen); /** * Connect to remote host at given port * Returns socket fd on success, -1 on error */ int socket_connect(int port, char *host); /** * Receive data on sockfd * Returns -1 on error, number of bytes received * on success */ int recv_data(int sockfd, char* buf, int bufsize); /** * Send resposne code on sockfd * Returns -1 on error, 0 on success */ int send_response(int sockfd, int rc); //------------------- UTILITY FUNCTIONS-------------------// /** * Trim whiteshpace and line ending * characters from a string */ void trimstr(char *str, int n); /** * Read input from command line */ void read_input(char* buffer, int size); #endif ================================================ FILE: server/.auth ================================================ user pass test testpass anonymous ================================================ FILE: server/.auth~ ================================================ user pass test testpass ================================================ FILE: server/ftserve.c ================================================ #include "ftserve.h" int main(int argc, char *argv[]) { int sock_listen, sock_control, port, pid; if (argc != 2) { printf("usage: ./ftserve port\n"); exit(0); } port = atoi(argv[1]); // create socket if ((sock_listen = socket_create(port)) < 0 ) { perror("Error creating socket"); exit(1); } while(1) { // wait for client request // create new socket for control connection if ((sock_control = socket_accept(sock_listen)) < 0 ) break; // create child process to do actual file transfer if ((pid = fork()) < 0) { perror("Error forking child process"); } else if (pid == 0) { close(sock_listen); ftserve_process(sock_control); close(sock_control); exit(0); } close(sock_control); } close(sock_listen); return 0; } /** * Send file specified in filename over data connection, sending * control message over control connection * Handles case of null or invalid filename */ void ftserve_retr(int sock_control, int sock_data, char* filename) { FILE* fd = NULL; char data[MAXSIZE]; size_t num_read; fd = fopen(filename, "r"); if (!fd) { // send error code (550 Requested action not taken) send_response(sock_control, 550); } else { // send okay (150 File status okay) send_response(sock_control, 150); do { num_read = fread(data, 1, MAXSIZE, fd); if (num_read < 0) { printf("error in fread()\n"); } // send block if (send(sock_data, data, num_read, 0) < 0) perror("error sending file\n"); } while (num_read > 0); // send message: 226: closing conn, file transfer successful send_response(sock_control, 226); fclose(fd); } } /** * Send list of files in current directory * over data connection * Return -1 on error, 0 on success */ int ftserve_list(int sock_data, int sock_control) { char data[MAXSIZE]; size_t num_read; FILE* fd; int rs = system("ls -l | tail -n+2 > tmp.txt"); if ( rs < 0) { exit(1); } fd = fopen("tmp.txt", "r"); if (!fd) { exit(1); } /* Seek to the beginning of the file */ fseek(fd, SEEK_SET, 0); send_response(sock_control, 1); //starting memset(data, 0, MAXSIZE); while ((num_read = fread(data, 1, MAXSIZE, fd)) > 0) { if (send(sock_data, data, num_read, 0) < 0) { perror("err"); } memset(data, 0, MAXSIZE); } fclose(fd); send_response(sock_control, 226); // send 226 return 0; } /** * Open data connection to client * Returns: socket for data connection * or -1 on error */ int ftserve_start_data_conn(int sock_control) { char buf[1024]; int wait, sock_data; // Wait for go-ahead on control conn if (recv(sock_control, &wait, sizeof wait, 0) < 0 ) { perror("Error while waiting"); return -1; } // Get client address struct sockaddr_in client_addr; socklen_t len = sizeof client_addr; getpeername(sock_control, (struct sockaddr*)&client_addr, &len); inet_ntop(AF_INET, &client_addr.sin_addr, buf, sizeof(buf)); // Initiate data connection with client if ((sock_data = socket_connect(CLIENT_PORT_ID, buf)) < 0) return -1; return sock_data; } /** * Authenticate a user's credentials * Return 1 if authenticated, 0 if not */ int ftserve_check_user(char*user, char*pass) { char username[MAXSIZE]; char password[MAXSIZE]; char *pch; char buf[MAXSIZE]; char *line = NULL; size_t num_read; size_t len = 0; FILE* fd; int auth = 0; fd = fopen(".auth", "r"); if (fd == NULL) { perror("file not found"); exit(1); } while ((num_read = getline(&line, &len, fd)) != -1) { memset(buf, 0, MAXSIZE); strcpy(buf, line); pch = strtok (buf," "); strcpy(username, pch); if (pch != NULL) { pch = strtok (NULL, " "); strcpy(password, pch); } // remove end of line and whitespace trimstr(password, (int)strlen(password)); if ((strcmp(user,username)==0) && (strcmp(pass,password)==0)) { auth = 1; break; } } free(line); fclose(fd); return auth; } /** * Log in connected client */ int ftserve_login(int sock_control) { char buf[MAXSIZE]; char user[MAXSIZE]; char pass[MAXSIZE]; memset(user, 0, MAXSIZE); memset(pass, 0, MAXSIZE); memset(buf, 0, MAXSIZE); // Wait to recieve username if ( (recv_data(sock_control, buf, sizeof(buf)) ) == -1) { perror("recv error\n"); exit(1); } int i = 5; int n = 0; while (buf[i] != 0) user[n++] = buf[i++]; // tell client we're ready for password send_response(sock_control, 331); // Wait to recieve password memset(buf, 0, MAXSIZE); if ( (recv_data(sock_control, buf, sizeof(buf)) ) == -1) { perror("recv error\n"); exit(1); } i = 5; n = 0; while (buf[i] != 0) { pass[n++] = buf[i++]; } return (ftserve_check_user(user, pass)); } /** * Wait for command from client and * send response * Returns response code */ int ftserve_recv_cmd(int sock_control, char*cmd, char*arg) { int rc = 200; char buffer[MAXSIZE]; memset(buffer, 0, MAXSIZE); memset(cmd, 0, 5); memset(arg, 0, MAXSIZE); // Wait to recieve command if ((recv_data(sock_control, buffer, sizeof(buffer)) ) == -1) { perror("recv error\n"); return -1; } strncpy(cmd, buffer, 4); char *tmp = buffer + 5; strcpy(arg, tmp); if (strcmp(cmd, "QUIT")==0) { rc = 221; } else if((strcmp(cmd, "USER")==0) || (strcmp(cmd, "PASS")==0) || (strcmp(cmd, "LIST")==0) || (strcmp(cmd, "RETR")==0)) { rc = 200; } else { //invalid command rc = 502; } send_response(sock_control, rc); return rc; } /** * Child process handles connection to client */ void ftserve_process(int sock_control) { int sock_data; char cmd[5]; char arg[MAXSIZE]; // Send welcome message send_response(sock_control, 220); // Authenticate user if (ftserve_login(sock_control) == 1) { send_response(sock_control, 230); } else { send_response(sock_control, 430); exit(0); } while (1) { // Wait for command int rc = ftserve_recv_cmd(sock_control, cmd, arg); if ((rc < 0) || (rc == 221)) { break; } if (rc == 200 ) { // Open data connection with client if ((sock_data = ftserve_start_data_conn(sock_control)) < 0) { close(sock_control); exit(1); } // Execute command if (strcmp(cmd, "LIST")==0) { // Do list ftserve_list(sock_data, sock_control); } else if (strcmp(cmd, "RETR")==0) { // Do get ftserve_retr(sock_control, sock_data, arg); } // Close data connection close(sock_data); } } } ================================================ FILE: server/ftserve.h ================================================ /* ftserve.h * * Rebecca Sagalyn * CS372, Program 1 * 11/15/13 * * Server side of TCP file transfer implementation, runs with custom client, * ftclient.c. Sends list of files in current directory and files to ftclient. * Requires user login. * * Usage: * ./ftserve PORT# */ #ifndef FTSERVE_H #define FTSERVE_H #include "../common/common.h" /** * Send file specified in filename over data connection, sending * control message over control connection * Handles case of null or invalid filename */ void ftserve_retr(int sock_control, int sock_data, char* filename); /** * Send list of files in current directory * over data connection * Return -1 on error, 0 on success */ int ftserve_list(int sock_data, int sock_control); /** * Open data connection to client * Returns: socket for data connection * or -1 on error */ int ftserve_start_data_conn(int sock_control); /** * Authenticate a user's credentials * Return 1 if authenticated, 0 if not */ int ftserve_check_user(char*user, char*pass); /** * Log in connected client */ int ftserve_login(int sock_control); /** * Wait for command from client and send response * Returns response code */ int ftserve_recv_cmd(int sock_control, char*cmd, char*arg); /** * Child process handles connection to client */ void ftserve_process(int sock_control); #endif ================================================ FILE: server/makefile ================================================ CC := gcc CFLAGS := -Wall -g -Os SHDIR := ../common OBJS = ftserve.o $(SHDIR)/common.o all: ftserve ftserve: $(OBJS) @$(CC) -o ftserve $(CFLAGS) $(OBJS) $(OBJS) : %.o: %.c @$(CC) -c $(CFLAGS) $< -o $@ .PHONY: clean: @rm -f *.o ftserve @rm -f ../common/*.o @echo Done cleaning