#include #include #include #include #include #include #include #include #ifndef INBUFSIZE #define INBUFSIZE 4096 #endif #ifndef OUTBUFSIZE #define OUTBUFSIZE 4096 #endif #ifndef LISTEN_QLEN #define LISTEN_QLEN 32 #endif #ifndef INIT_SESS_ARR_SIZE #define INIT_SESS_ARR_SIZE 32 #endif #ifndef FILENAME_SIZE #define FILENAME_SIZE 1024 #endif enum fsm_states { header_start, waiting_host, empty_line, writing_header, writing_body }; struct session { int fd; unsigned long from_ip; unsigned short from_port; char in_buffer[INBUFSIZE]; char out_buffer[OUTBUFSIZE]; int in_buf_used; int out_buf_used; enum fsm_states state; int rs_fd; int data_transfer_status; char *path; int bad_request; int not_found; }; struct server { int listen_socket; struct session **session_array; int session_size; char *host; char *resource_dir; }; static void init_session(struct session *new_session, struct sockaddr_in *from, int fd) { new_session->fd = fd; new_session->from_ip = ntohl(from->sin_addr.s_addr); new_session->from_port = ntohs(from->sin_port); new_session->in_buf_used = 0; new_session->out_buf_used = 0; new_session->state = header_start; new_session->rs_fd = -1; new_session->data_transfer_status = 0; new_session->path = NULL; new_session->bad_request = 0; new_session->not_found = 0; } static int accept_client(struct server *serv) { int fd, i, flags; struct sockaddr_in addr; struct session *new_session; socklen_t len = sizeof(addr); fd = accept(serv->listen_socket, (struct sockaddr*) &addr, &len); if(fd == -1) return 0; /* setting non-blocking mode for socket*/ flags = fcntl(fd, F_GETFL); fcntl(fd, F_SETFL, flags | O_NONBLOCK); if(fd >= serv->session_size) { int newlen = serv->session_size; while(fd >= newlen) newlen += INIT_SESS_ARR_SIZE; serv->session_array = realloc(serv->session_array, newlen * sizeof(struct session*)); for(i = serv->session_size; i < newlen; i++) serv->session_array[i] = NULL; serv->session_size = newlen; } new_session = malloc(sizeof(struct session)); serv->session_array[fd] = new_session; init_session(new_session, &addr, fd); return 1; } static int host_search_condition(const char *line) { return !(strncmp(line, "Host:", strlen("Host:")) && strncmp(line, "host:", strlen("host:"))); } static int match_hosts(struct server *serv, const char *line, int length) { int i, last_idx; if(strlen("host:") > length) return 0; if(!host_search_condition(line)) return 0; for(i = strlen("host:"); i < length; ++i) { if(line[i] == ' ') continue; break; } if(i+strlen(serv->host) > length) return 0; if(strncmp(line+i, serv->host, strlen(serv->host))) return 0; for(last_idx = i+strlen(serv->host); last_idx < length; ++last_idx) { if(line[last_idx] == ' ') continue; else break; } if(line[last_idx] == '\r'|| line[last_idx] == '\n') return 1; return 0; } static int match_protocols(const char *line, int length) { int i; /* skip extract path */ for(i = 4; i < length && line[i] != ' '; ++i) {} if(i >= length) return 0; ++i; if(i+strlen("HTTP/1.1") > length) return 0; if(strncmp(line+i, "HTTP/1.1", strlen("HTTP/1.1"))) return 0; for(i += strlen("HTTP/1.1"); i < length; ++i) { if(line[i] == ' ') continue; if(line[i] == '\r' || line[i] == '\n') return 1; return 0; } return 0; } static char* extract_path(const char *line, int length) { int i; char *path; for(i = 4; i < length && line[i] != ' '; ++i) {} if(i >= length) return NULL; length = i-4+1 ; path = malloc(length); memcpy(path, line+4, length-1); path[length-1] = '\0'; return path; } static int command_processing(struct server *serv, struct session *cur_session, const char *line, int length) { switch(cur_session->state) { case header_start: if(length > strlen("GET") && !strncmp(line, "GET", strlen("GET"))) { cur_session->path = extract_path(line, length); if(cur_session->path == NULL) return 0; if(!match_protocols(line, length)) return 0; cur_session->state = waiting_host; return 1; } case waiting_host: if(!match_hosts(serv, line, length)) return 0; cur_session->state = empty_line; return 1; case empty_line: if(line == NULL) { cur_session->data_transfer_status = 1; cur_session->state = writing_header; return 1; } default: return 0; } } /* * 0 - close current session * 1 - continue */ static int read_in_session(struct server *serv, struct session *cur_session) { int read_res, i, end_idx; char *line = NULL; end_idx = -1; if(!cur_session->in_buf_used) read_res = read(cur_session->fd, cur_session->in_buffer, INBUFSIZE); if(read_res <= 0) return 0; cur_session->in_buf_used += read_res; do { for(i = 0; i < cur_session->in_buf_used; ++i) { if(cur_session->in_buffer[i] == '\n') { end_idx = i; break; } } if(end_idx == -1) { if(cur_session->in_buf_used == INBUFSIZE) return 0; return 1; } if(cur_session->state != empty_line) { line = malloc(end_idx+1); memcpy(line, cur_session->in_buffer, end_idx+1); } cur_session->in_buf_used -= i+1; memmove(cur_session->in_buffer, cur_session->in_buffer+end_idx+1, cur_session->in_buf_used); /* skip the remaining data in the header */ if(cur_session->state == empty_line && strncmp(cur_session->in_buffer, "\r\n", 2)) continue; else if(cur_session->state == empty_line && !strncmp(cur_session->in_buffer, "\r\n", 2)) cur_session->in_buf_used = 0; if(!command_processing(serv, cur_session, line, end_idx)) { cur_session->bad_request = 1; cur_session->data_transfer_status = 1; cur_session->state = writing_header; } if(line) { free(line); line = NULL; } if(cur_session->bad_request) return 1; } while(cur_session->in_buf_used); return 1; } static void print_header(struct session *cur_session, char *status) { write(cur_session->fd, status, strlen(status)); } static int is_index_page(char *path) { int i; for(i = strlen(path)-1; i >= 0 && path[i] != '/'; --i) { if(path[i] == '.') return 0; } return 1; } static void remove_slash(char *root) { int last_idx = strlen(root); --last_idx; if(root[last_idx] == '/') root[last_idx] = '\0'; } static int open_file(char *root, struct session *cur_session) { int i, j; int add_index_page = 0; char *given_path = cur_session->path; char *str_index_page = "index.html"; char new_path[FILENAME_SIZE]; remove_slash(root); strncpy(new_path, root, strlen(root)); add_index_page = is_index_page(given_path) ? 1 : 0; for(i = strlen(root), j = 0; i < FILENAME_SIZE && j < strlen(given_path); ++i, ++j) new_path[i] = given_path[j]; if(add_index_page) { for(j = 0; i < FILENAME_SIZE && j < strlen(str_index_page); ++i, ++j) new_path[i] = str_index_page[j]; } if(i < FILENAME_SIZE) new_path[i] = '\0'; cur_session->rs_fd = open(new_path, O_RDONLY); if(cur_session->rs_fd == -1) return 0; else return 1; } static void change_out_buffer(char *out_buffer, int *out_buf_used, int writed_size) { memmove(out_buffer, out_buffer+writed_size, (*out_buffer)-writed_size); *out_buf_used -= writed_size; } static void write_error_page(struct session *cur_session) { int read_result; read_result = read(cur_session->rs_fd, cur_session->out_buffer, OUTBUFSIZE); write(cur_session->fd, cur_session->out_buffer, read_result); } static int file_open_condition(struct session *cur_session) { return !cur_session->bad_request && !cur_session->not_found && cur_session->rs_fd == -1; } static char* get_page_error_path(struct server *serv, char *page) { char *new_path; new_path = malloc(strlen(serv->resource_dir) + strlen(page)); strcpy(new_path, serv->resource_dir); strcat(new_path, page); return new_path; } static int write_in_session(struct server *serv, struct session *cur_session) { int read_result, write_result, open_result; char *page_error_path = NULL; if(file_open_condition(cur_session)) { open_result = open_file(serv->resource_dir, cur_session); if(!open_result) { cur_session->state = writing_header; cur_session->not_found = 1; } } switch(cur_session->state) { case writing_header: if(cur_session->bad_request) print_header(cur_session, "HTTP/1.1 400 Bad Request\n\n"); else if(cur_session->not_found) print_header(cur_session, "HTTP/1.1 404 Not Found\n\n"); else print_header(cur_session, "HTTP/1.1 200 OK\n\n"); cur_session->state = writing_body; return 1; case writing_body: /* ---------- error page write ---------- */ if(cur_session->bad_request) page_error_path = get_page_error_path(serv, "/400.html"); else if(cur_session->not_found) page_error_path = get_page_error_path(serv, "/404.html"); if(cur_session->bad_request || cur_session->not_found) { cur_session->rs_fd = open(page_error_path, O_RDONLY); if(cur_session->rs_fd == -1) syslog(LOG_ERR, "can't find error page"); free(page_error_path); write_error_page(cur_session); return 0; } /* ---------- normal page write ---------- */ /* if buffer doesn't contain any data*/ if(!cur_session->out_buf_used) { read_result = read(cur_session->rs_fd, cur_session->out_buffer, OUTBUFSIZE); /* end of file */ if(!read_result) { close(cur_session->rs_fd); cur_session->rs_fd = -1; cur_session->data_transfer_status = 0; cur_session->state = header_start; return 0; } cur_session->out_buf_used += read_result; } write_result = write(cur_session->fd, cur_session->out_buffer, cur_session->out_buf_used); /* writed remaining some of data */ if(write_result < cur_session->out_buf_used) change_out_buffer(cur_session->out_buffer, &cur_session->out_buf_used, write_result); /* writed all data from out_buffer */ else cur_session->out_buf_used = 0; return 1; default: return 0; } } static void close_session(struct server *serv, int fd) { close(fd); serv->session_array[fd]->fd = -1; if(serv->session_array[fd]->path) free(serv->session_array[fd]->path); if(serv->session_array[fd]->rs_fd) close(serv->session_array[fd]->rs_fd); serv->session_array[fd] = NULL; } static int reading_condition(struct session *cur_session) { return cur_session && !cur_session->data_transfer_status; } static int writing_condition(struct session *cur_session) { return cur_session && cur_session->data_transfer_status; } int main_loop(struct server *serv) { int i, maxfd, select_result, accept_result, read_result, write_result; fd_set readfds, writefds; for(;;) { FD_ZERO(&readfds); FD_ZERO(&writefds); FD_SET(serv->listen_socket, &readfds); maxfd = serv->listen_socket; for(i = 0; i < serv->session_size; ++i) { if(reading_condition(serv->session_array[i])) { FD_SET(i, &readfds); if(i > maxfd) maxfd = i; } if(writing_condition(serv->session_array[i])) { FD_SET(i, &writefds); if(i > maxfd) maxfd = i; } } select_result = select(maxfd + 1, &readfds, &writefds, NULL, NULL); if(!select_result) { syslog(LOG_ERR, "select error"); return 3; } if(FD_ISSET(serv->listen_socket, &readfds)) { accept_result = accept_client(serv); if(!accept_result) { syslog(LOG_ERR, "accept error"); return 4; } } for(i = 0; i < serv->session_size; ++i) { if(serv->session_array[i] && FD_ISSET(i, &readfds)) { read_result = read_in_session(serv, serv->session_array[i]); /* normal connection closure (by user or full of in_buffer) */ if(!read_result) close_session(serv, i); } if(serv->session_array[i] && FD_ISSET(i, &writefds)) { write_result = write_in_session(serv, serv->session_array[i]);; /* * Closing the connection if all data is written or * error 400 or 404 occurs */ if(!write_result) close_session(serv, i); } } } } /* * 0 - error * 1 - success */ static int init_server(struct server *serv, int port, char *host, char *resource_dir) { int opt, i; struct sockaddr_in addr; serv->host = host; serv->resource_dir = resource_dir; serv->listen_socket = socket(AF_INET, SOCK_STREAM, 0); if(serv->listen_socket == -1) return 0; opt = 1; setsockopt(serv->listen_socket, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); addr.sin_family = AF_INET; addr.sin_addr.s_addr = htonl(INADDR_ANY); addr.sin_port = htons(port); if(bind(serv->listen_socket, (struct sockaddr*) &addr, sizeof(addr)) == -1) return 0; serv->session_array = malloc(sizeof(serv->session_array) * INIT_SESS_ARR_SIZE); serv->session_size = INIT_SESS_ARR_SIZE; for(i = 0; i < INIT_SESS_ARR_SIZE; i++) serv->session_array[i] = NULL; listen(serv->listen_socket, LISTEN_QLEN); return 1; } static void demonization() { int pid; close(0); close(1); close(2); open("/dev/null", O_RDONLY); open("/dev/null", O_WRONLY); open("/dev/null", O_WRONLY); chdir("/"); pid = fork(); if(pid) exit(0); setsid(); pid = fork(); if(pid) exit(0); } int main(int argc, char **argv) { unsigned short port; struct server serv; if(argc != 4) { fprintf(stderr, "Usage: