Skip to content
Find file
Fetching contributors…
Cannot retrieve contributors at this time
909 lines (781 sloc) 20.6 KB
#include "duoit.h"
// global, I want this!!! ;)
fd_set master; // master file descriptor list
int fdmax = 0; // maximum file descriptor number
int add_fd(int socket) {
FD_SET(socket, &master);
// keep track of the biggest file descriptor
if (socket > fdmax) {
fdmax = socket;
}
return 0;
}
int remove_fd(int socket) {
close(socket);
FD_CLR(socket, &master);
return 0;
}
// get sockaddr, IPv4 or IPv6:
void *get_in_addr(struct sockaddr *sa){
if (sa->sa_family == AF_INET){
return &(((struct sockaddr_in*)sa)->sin_addr);
}
return &(((struct sockaddr_in6*)sa)->sin6_addr);
}
/*
* Initialize the Server
*
* creates socket, binds port and listens to it
*/
int initialize_server() {
int sock_fd;
struct addrinfo hints, *servinfo, *p;
int yes=1, rv;
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE; //use own IP
if ((rv = getaddrinfo(NULL, CPORT, &hints, &servinfo)) != 0) {
fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv));
return EXIT_FAILURE;
}
// loop through all the results and bind to the first we can
for(p = servinfo; p != NULL; p = p->ai_next){
if ((sock_fd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1){
perror("server: socket");
continue;
}
// reuse sockets
if (setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1){
perror("setsockopt");
exit(1);
}
if (bind(sock_fd, p->ai_addr, p->ai_addrlen) == -1){
close(sock_fd);
perror("server: bind");
continue;
}
break;
}
if (p == NULL) {
fprintf(stderr, "server: failed to bind\n");
return EXIT_FAILURE;
}
freeaddrinfo(servinfo); //all done with this structure
if (listen(sock_fd, BACKLOG) == -1){
perror("listen");
exit(1);
}
printf("server: waiting for connections (port %s) ...\n", CPORT);
return sock_fd;
}
/*
* initialize head of user-list
*/
int ini_user_list(int newfd, usr **user) {
char ipstr[INET6_ADDRSTRLEN];
get_ip(newfd, ipstr, INET6_ADDRSTRLEN);
*user = calloc(1, sizeof(struct usr));
(*user)->c_socket = newfd;
(*user)->d_socket = 0;
strcpy((*user)->d_address, ipstr);
(*user)->d_port = get_port(newfd);
(*user)->passv = 0;
(*user)->type = IMAGE;
(*user)->pwd = calloc(1, PATHLENGTH);
(*user)->pwd_len = PATHLENGTH;
(*user)->next = NULL;
return 0;
}
/*
* append user to user-list
*/
int append_user(int newfd, usr *user) {
char ipstr[INET6_ADDRSTRLEN];
get_ip(newfd, ipstr, INET6_ADDRSTRLEN);
while (user->next) {
user = user->next;
}
//append new usr-struct
user->next = calloc(1, sizeof(struct usr));
user = user->next;
user->c_socket = newfd;
user->d_socket = 0;
strcpy(user->d_address, ipstr);
user->d_port = get_port(newfd);
user->passv = 0;
user->type = IMAGE;
user->pwd = calloc(1, PATHLENGTH);
user->pwd_len = PATHLENGTH;
user->next = NULL;
return 0;
}
/*
* find user by c_socket number
*/
int find_user_by_csocket(int socket, usr *head, usr **result) {
while (head) {
if (head->c_socket == socket) {
*result = head;
return 0;
}
head = head->next;
}
return 1;
}
/*
* find user by d_socket number
*/
int find_user_by_dsocket(int socket, usr *head, usr **result) {
while (head) {
if (head->d_socket == socket) {
*result = head;
return 0;
}
head = head->next;
}
return 1;
}
/*
* find ancestor of user with c_socket number socket
*/
int find_pre_user(int socket, usr *head, usr **result) {
usr *pre;
if (head->c_socket == socket) {
//only one user in user-list
return 1;
}
while (head) {
if (head->c_socket == socket) {
*result = pre;
return 0;
}
pre = head;
head = head->next;
}
return 1;
}
/*
* Handle new connection
*
* accepts new connections and adds them to the file discriptors
*/
void handle_new_connection(int *listener, usr **user) {
socklen_t addrlen;
struct sockaddr_storage remoteaddr;
int newfd; // newly accept()ed socket descriptor
char remoteIP[INET6_ADDRSTRLEN];
addrlen = sizeof remoteaddr;
newfd = accept(*listener, (struct sockaddr *)&remoteaddr, &addrlen);
if (newfd == -1) {
perror("accept");
} else {
// new socket to the fds
add_fd(newfd);
//adapt the user list
if (!(*user)) {
//initialize head of user-list
ini_user_list(newfd, user);
} else {
//append user to user-list
append_user(newfd, *user);
}
send_message(newfd, "220 Welcome to duoit FTP-Server. Service ready.");
inet_ntop(remoteaddr.ss_family, get_in_addr((struct sockaddr*)&remoteaddr), remoteIP, INET6_ADDRSTRLEN);
printf("new connection from %s on socket %d\n", remoteIP, newfd);
}
}
/*
* handle new data connection
*/
int handle_data_connection(int lsocket, usr *head) {
struct sockaddr_storage remoteaddr;
socklen_t addrlen = sizeof remoteaddr;
int newfd; // newly accept()ed socket descriptor
usr *user;
// find user
if (find_user_by_dsocket(lsocket, head, &user)) {
printf("TODO error");
return 1;
}
newfd = accept(lsocket, (struct sockaddr *)&remoteaddr, &addrlen);
if (newfd == -1) {
perror("accept");
} else {
add_fd(newfd);
remove_fd(lsocket);
}
user->d_socket = newfd;
//user is in passive mode
user->passv = 1;
return 0;
}
/*
* close connection and remove it from the file discriptors list
*/
void close_connection(int socket, usr **head) {
usr *user;
usr *save;
find_user_by_csocket(socket, *head, &user);
if (user == *head) {
//remove user-list head
*head = user->next;
free(user);
} else if (user->next == NULL) {
//remove end of user-list
find_pre_user(socket, *head, &save);
save->next = NULL;
free(user);
} else {
//remove entry in user-list
find_pre_user(socket, *head, &save);
save->next = user->next;
free(user);
}
remove_fd(socket);
}
/*
* check whether str is alpha-only and lower every character
*/
int upcase(char *str, int size) {
int i;
for(i = 0; i < size; i++){
if (!(isalpha(str[i]))) {
return 1;
}
str[i] = (char) toupper(str[i]);
}
return 0;
}
/*
* removes preceding/trailing white-space-characters
* TODO: make sure string is null-terminated
*/
char *trim(char *str) {
char *temp;
temp = str;
//remove trailing white-spaces
while (*temp != '\0') {
temp++;
}
while (isspace(*(--temp))) {
*temp = '\0';
}
//remove preceding white-spaces
temp = str;
while (isspace(*temp)) {
temp++;
}
return temp;
}
/*
* send messages to socket
*
* TODO: prüfen was gesendet wurde, send_all
*/
int send_message(int socket, char *message) {
char reply[strlen(message) + 2];
printf(">> %s\n", message);
strcpy(reply, message);
strcat(reply, "\r\n");
send(socket, reply, strlen(reply), 0);
}
/*
* authenticate user
*/
int user_command(instruc *instruction, usr *user) {
if (!instruction->params) {
send_message(user->c_socket, "501 Syntax error in parameters or arguments.");
return 1;
}
//remove redundant whitespaces from params
instruction->params = trim(instruction->params);
//only one parameter allowed
if (strchr(instruction->params, ' ')) {
send_message(user->c_socket, "501 Syntax error in parameters or arguments.");
return 1;
}
if (!(strcmp(instruction->params, "anonymous"))) {
send_message(user->c_socket, "230 User logged in.");
} else {
send_message(user->c_socket, "530 Not logged in.");
}
return 0;
}
/*
* save non-default data-connection infos
* TODO assure correct parameter sytnax, AF_INET (!=INET6)
*/
int port_command(instruc *instruction, usr *user) {
int i = 0, port = 0;
char *ptr, *param;
char ip[INET6_ADDRSTRLEN + 5 + 6]; //INET6_ADDR + #"," + #(port-digits)
memset(ip, '\0', sizeof ip);
param = instruction->params;
if (!param) {
send_message(user->c_socket, "501 Syntax error in parameters or arguments.");
return 1;
}
//remove redundant whitespaces from params
param = trim(param);
//determine IP and Port for Data-Connection
ptr = strtok(param, ",");
while(ptr != NULL) {
if (i < 3) {
strcat(ip, ptr);
strcat(ip, ".");
} else if (i == 3) {
strcat(ip, ptr);
} else if (i == 4) {
port += atoi(ptr) * 256;
} else if (i == 5) {
port += atoi(ptr);
} else {
send_message(user->c_socket, "501 Syntax error in parameters or arguments.");
return 1;
}
i++;
ptr = strtok(NULL, ",");
}
strcpy(user->d_address, ip);
user->d_port = port;
send_message(user->c_socket, "200 PORT command successful.");
return 0;
}
/*
* sets the mode of file transfer
*/
int type_command(instruc *instruction, usr *user) {
int i = 0;
if (!instruction->params) {
send_message(user->c_socket, "501 Syntax error in parameters or arguments.");
return 1;
}
//demand one parameter - make its chars upper-case
while(instruction->params[i] != '\0') {
i++;
}
if (upcase(instruction->params, i)) {
send_message(user->c_socket, "501 Syntax error in parameters or arguments.");
return 1;
}
if (((i==1) && instruction->params[0] == 'A') || !strcmp(instruction->params, "ASCII")) {
//ascii type demanded
user->type = 2;
send_message(user->c_socket, "200 Type set to A");
return 0;
} else if (((i==1) && (instruction->params[0] == 'I' || instruction->params[0] == 'B')) \
|| !strcmp(instruction->params, "IMAGE") || !strcmp(instruction->params, "BINARY")) {
//binary type demanded
user->type = 1;
send_message(user->c_socket, "200 Type set to I");
return 0;
}
send_message(user->c_socket, "501 Syntax error in parameters or arguments.");
return 1;
}
/*
* send content of pwd
* TODO: check for null params
*/
int list_command(instruc *instruction, usr *user) {
int sock_fd;
struct addrinfo hints, *servinfo, *p;
char port[5];
int yes=1, rv;
if (user->passv == 1) {
send_message(user->c_socket, "125 Data connection already open.");
sock_fd = user->d_socket;
} else {
send_message(user->c_socket, "150 Opening ASCII mode data connection for file list.");
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
sprintf(port, "%d", user->d_port);
if ((rv = getaddrinfo(user->d_address, port, &hints, &servinfo)) != 0) {
send_message(user->c_socket, "425 Unable to build data connection: Invalid arguments.");
return 1;
}
// loop through all the results
for (p = servinfo; p != NULL; p = p->ai_next) {
if ((sock_fd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1){
perror("server: socket");
continue;
}
if (connect(sock_fd, p->ai_addr, p->ai_addrlen) == -1) {
close(sock_fd);
perror("connect");
continue;
}
//got one
user->d_socket = sock_fd;
break;
}
if (user->d_socket == 0) {
send_message(user->c_socket, "425 Can't open data connection.");
return 1;
}
}
send_message(sock_fd, "drwx------+ 26 Grobie staff 884 8 Jun 19:01 Documents\r\ndrwxr--r--+ 498 Grobie staff 16932 10 Jun 18:28 Downloads\r\ndrwx---rwx+ 28 Grobie staff 952 10 Jun 15:21 Movies\r\ndrwx---r-x+ 8 Grobie staff 272 25 Feb 22:18 Music\r\ndrwx------+ 16 Grobie staff 544 5 Jun 20:12 Pictures");
send_message(user->c_socket, "226 Transfer complete.");
user->d_socket = 0;
remove_fd(sock_fd);
return 0;
}
/*
* sets mode to passive and prepares d_socket for listening
*/
int pasv_command(instruc *instruction, usr *user) {
char reply[30 + 18 + 7]; //#chars of standard reply + ip + #,
char ipstr[INET6_ADDRSTRLEN];
char port1[4];
char port2[4];
char possible_port[5];
char *ptr;
int mod, div, i, h;
int sock_fd;
struct addrinfo hints, *servinfo, *p;
int yes=1, rv;
memset(port1, '\0', sizeof port1);
memset(port2, '\0', sizeof port2);
memset(possible_port, '\0', sizeof possible_port);
memset(reply, '\0', sizeof reply);
memset(ipstr, '\0', sizeof ipstr);
strcpy(ipstr, LISTENIP);
//Create new d_socket we will listen on
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_INET; //PASV command only definded for IPv4
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE; //use own IP
for(h = 3022; h < 65535; h++) {
sprintf(possible_port, "%d", h);
if ((rv = getaddrinfo(NULL, possible_port, &hints, &servinfo)) != 0) {
fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv));
return 1;
}
// loop through all the results and bind to the first we can
for(p = servinfo; p != NULL; p = p->ai_next) {
if ((sock_fd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1) {
perror("server: socket");
continue;
}
// reuse sockets
if (setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1) {
perror("setsockopt");
return 1;
}
if (bind(sock_fd, p->ai_addr, p->ai_addrlen) == -1) {
close(sock_fd);
perror("server: bind");
continue;
}
break;
}
break;
}
freeaddrinfo(servinfo); //all done with this structure
//listen
if (listen(sock_fd, BACKLOG) == -1) {
perror("listen");
return 1;
}
// saving fd
user->d_socket = sock_fd;
add_fd(sock_fd);
printf("server: waiting for data-connections (port %s) ...\n", possible_port);
//generating reply for client
strcpy(reply, "227 Entering Passive Mode (");
//format IP-Adress - e.g. 127,0,0,1
ptr = strtok(ipstr, ".");
while(ptr != NULL) {
if (i < 4) {
strcat(reply, ptr);
strcat(reply, ",");
} else {
return 1;
}
i++;
ptr = strtok(NULL, ".");
}
//determine formatted port
div = atoi(possible_port) / 256;
mod = atoi(possible_port) % 256;
sprintf(port1, "%d", div);
sprintf(port2, "%d", mod);
//append port
strcat(reply, port1 );
strcat(reply, ",");
strcat(reply, port2);
strcat(reply, ").");
send_message(user->c_socket, reply);
return 0;
}
/*
* sets mode to passive and prepares d_socket for listening | extended version
*/
int epsv_command(instruc *instruction, usr *user) {
char reply[43 + 5]; // chars of standard reply + port
char possible_port[5];
char *ptr;
int mod, div, i, h;
int sock_fd;
struct addrinfo hints, *servinfo, *p;
int yes=1, rv;
memset(possible_port, '\0', sizeof possible_port);
memset(reply, '\0', sizeof reply);
//Create new d_socket we will listen on
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_INET6; //EPASV command only definded for IPv6
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE; //use own IP
for(h = 3022; h < 65535; h++) {
sprintf(possible_port, "%d", h);
if ((rv = getaddrinfo(NULL, possible_port, &hints, &servinfo)) != 0) {
fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv));
return 1;
}
// loop through all the results and bind to the first we can
for(p = servinfo; p != NULL; p = p->ai_next) {
if ((sock_fd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1){
perror("server: socket");
continue;
}
// reuse sockets
if (setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1){
perror("setsockopt");
return 1;
}
if (bind(sock_fd, p->ai_addr, p->ai_addrlen) == -1) {
close(sock_fd);
perror("server: bind");
continue;
}
break;
}
break;
}
freeaddrinfo(servinfo); //all done with this structure
//listen
if (listen(sock_fd, BACKLOG) == -1){
perror("listen");
return 1;
}
// saving fd
user->d_socket = sock_fd;
add_fd(user->d_socket);
printf("server: waiting for data-connections (port %s) ...\n", possible_port);
//generating reply for client
strcpy(reply, "229 Entering Extended Passive Mode (|||");
//append port
strcat(reply, possible_port);
strcat(reply, "|).");
send_message(user->c_socket, reply);
return 0;
}
/*
* quits the connection with client
*/
int quit_command(instruc *instruction, usr *user, usr **head) {
send_message(user->c_socket, "221 Goodbye.");
close_connection(user->c_socket, head);
return 0;
}
/*
* find the PORT of the user's socket
*/
int get_port(int sockfd) {
socklen_t len;
struct sockaddr_storage addr;
int port;
len = sizeof(struct sockaddr_storage);
getpeername(sockfd, (struct sockaddr*)&addr, &len);
//deal with both IPv4 and IPv6
if (addr.ss_family == AF_INET) {
struct sockaddr_in *sock = (struct sockaddr_in *)&addr;
port = ntohs(sock->sin_port);
} else { //AF_INET6
struct sockaddr_in6 *sock = (struct sockaddr_in6 *)&addr;
port = ntohs(sock->sin6_port);
}
//sprintf(str_port, "%d", port);
return port;
}
/*
* fills the ip-string with the formatted IP of socket
*/
int get_ip(int socket, char *ip, int size) {
socklen_t len;
struct sockaddr_storage addr;
char ipstr[INET6_ADDRSTRLEN];
//check whether buffer is big enough
if(size < (INET6_ADDRSTRLEN)) {
return 1;
}
len = sizeof(struct sockaddr_storage);
getpeername(socket, (struct sockaddr*)&addr, &len);
//deal with both IPv4 and IPv6
if (addr.ss_family == AF_INET) {
struct sockaddr_in *sock = (struct sockaddr_in *)&addr;
inet_ntop(AF_INET, &sock->sin_addr, ipstr, sizeof ipstr);
} else { //AF_INET6
struct sockaddr_in6 *sock = (struct sockaddr_in6 *)&addr;
inet_ntop(AF_INET6, &sock->sin6_addr, ipstr, sizeof ipstr);
}
strcpy(ip, ipstr);
return 0;
}
int log_action(usr *user, char *message) {
char log_time[256];
time_t curtime;
char ip[INET6_ADDRSTRLEN];
curtime = time(NULL);
if (get_ip(user->c_socket, ip, sizeof ip)) {
return 1;
}
strftime(log_time, 256, "%H:%M", localtime(&curtime));
printf("*LOG* %s - %s[%d] - %s\n", log_time, ip, get_port(user->c_socket), message);
return 0;
}
/*
* dispatch message
*
* TODO
*/
int dispatch(usr *user, char *received, int received_len, usr **head) {
instruc instruction;
if (parse_command(received, received_len, &instruction)) {
send_message(user->c_socket, "500 Syntax Error");
return 1;
}
if (!strcmp(instruction.command, "USER")) {
if (user_command(&instruction, user)) {
return 1;
}
} else if (!strcmp(instruction.command, "PORT")) {
if (port_command(&instruction, user)) {
return 1;
}
} else if (!strcmp(instruction.command, "TYPE")) {
if (type_command(&instruction, user)) {
return 1;
}
} else if (!strcmp(instruction.command, "LIST")) {
if (list_command(&instruction, user)) {
return 1;
}
} else if (!strcmp(instruction.command, "PASV")) {
if (pasv_command(&instruction, user)) {
return 1;
}
} else if (!strcmp(instruction.command, "EPSV")) {
if (epsv_command(&instruction, user)) {
return 1;
}
} else if (!strcmp(instruction.command, "QUIT")) {
if (quit_command(&instruction, user, head)) {
return 1;
}
} else {
send_message(user->c_socket, "502 Command not implemented.");
}
return 0;
}
int parse_command(char *received, int received_len, instruc *instruction) {
char *parameter = NULL;
int input_length;
int space_pos;
received[received_len] = '\0';
printf("<< %s", received);
if (received[received_len - 2] == '\r' && received[received_len - 1] == '\n'){
received[received_len -2] = '\0';
input_length = received_len - 2;
}
else {
return 1;
}
parameter = strchr(received, 32); //search for <SP>
if (!parameter) {
//no parameter
//check whether command is to long
if (input_length > 4) {
return 1;
}
if (upcase(received, input_length)) {
return 1;
}
} else {
space_pos = parameter - received;
if (space_pos > 4) {
return 1;
}
received[space_pos] = '\0'; //null terminate command
parameter++; //parameters begin after <SP>
if (upcase(received, space_pos)) {
return 1;
}
}
instruction->command = received;
instruction->params = parameter;
return 0;
}
/*
* Main programm
*
* accepts new connections, receives new messages and dispatches them
*/
int main(int argc, char *argv[]) {
struct sockaddr_storage client_addr; //client's address information
socklen_t sin_size;
fd_set read_fds; // temp file descriptor list for select()
int listener; // listening socket descriptor
usr *head = NULL;
usr *current_user = NULL;
int i, nbytes;
char buf[256];
// initialize server
listener = initialize_server();
// initialize sets
FD_ZERO(&read_fds);
FD_ZERO(&master);
// add the listener to the master set
add_fd(listener);
// main loop
while(1) {
read_fds = master;
if (select(fdmax+1, &read_fds, NULL, NULL, NULL) == -1) {
perror("select");
exit(4);
}
for(i = 0; i <= fdmax; i++) {
if (FD_ISSET(i, &read_fds)) {
if (i == listener) {
handle_new_connection(&listener, &head);
} else {
if (!find_user_by_dsocket(i, head, &current_user)) {
//got new data-connection
handle_data_connection(i, head);
} else {
if ((nbytes = recv(i, buf, sizeof buf, 0)) <= 0) {
// got error or connection closed by client
if (nbytes == 0) {
// connection closed
printf("socket %d hung up\n", i);
} else {
perror("recv");
}
remove_fd(i);
} else {
// client sends message. dispatch it
if (find_user_by_csocket(i, head, &current_user)) {
printf("*ERR* corresponding user to socket %d not found!\n", i);
continue;
}
dispatch(current_user, buf, nbytes, &head);
}
}
}
}
}
}
return 0;
}
Jump to Line
Something went wrong with that request. Please try again.