|
@@ -0,0 +1,387 @@
|
|
|
+#include <stdio.h>
|
|
|
+#include <stdlib.h>
|
|
|
+#include <unistd.h>
|
|
|
+#include <string.h>
|
|
|
+#include <errno.h>
|
|
|
+#include <fcntl.h>
|
|
|
+#include <netdb.h>
|
|
|
+#include <time.h>
|
|
|
+#include <ctype.h>
|
|
|
+#include <sys/socket.h>
|
|
|
+#include <sys/types.h>
|
|
|
+#include <netinet/in.h>
|
|
|
+#include <arpa/inet.h>
|
|
|
+#include <pthread.h>
|
|
|
+#include <signal.h>
|
|
|
+
|
|
|
+#define SERVER_PORT 12345
|
|
|
+#define MAX_CONNECTIONS 1000
|
|
|
+
|
|
|
+#define LOCK_FILE_PATH "/tmp/pid.lock"
|
|
|
+#define m_string char*
|
|
|
+
|
|
|
+/*
|
|
|
+Client
|
|
|
+ socket()
|
|
|
+ ? bind() ?
|
|
|
+ connect()
|
|
|
+
|
|
|
+ ----------------
|
|
|
+ write() | read()
|
|
|
+ send() | recv()
|
|
|
+ sendto() | recvfrom()
|
|
|
+ writev() | readv()
|
|
|
+ sendmsg() | recvmsg()
|
|
|
+ ----------------
|
|
|
+
|
|
|
+ close()
|
|
|
+
|
|
|
+Server
|
|
|
+ socket()
|
|
|
+ bind()
|
|
|
+ listen()
|
|
|
+ accept()
|
|
|
+
|
|
|
+ ----------------
|
|
|
+ write() | read()
|
|
|
+ send() | recv()
|
|
|
+ sendto() | recvfrom()
|
|
|
+ writev() | readv()
|
|
|
+ sendmsg() | recvmsg()
|
|
|
+ ----------------
|
|
|
+
|
|
|
+ close()
|
|
|
+*/
|
|
|
+
|
|
|
+typedef enum {
|
|
|
+ eHTTP_UNKNOWN = 0,
|
|
|
+ eHTTP_CONNECT,
|
|
|
+ eHTTP_DELETE,
|
|
|
+ eHTTP_GET,
|
|
|
+ eHTTP_HEAD,
|
|
|
+ eHTTP_OPTIONS,
|
|
|
+ eHTTP_PATCH,
|
|
|
+ eHTTP_POST,
|
|
|
+ eHTTP_PUT,
|
|
|
+ eHTTP_TRACE
|
|
|
+} eHTTPMethod;
|
|
|
+
|
|
|
+typedef struct {
|
|
|
+ eHTTPMethod type;
|
|
|
+ char path[255];
|
|
|
+} sHTTPHeader;
|
|
|
+
|
|
|
+typedef struct {
|
|
|
+ struct strList *next;
|
|
|
+ char *cont;
|
|
|
+} strList;
|
|
|
+
|
|
|
+typedef struct {
|
|
|
+ void *access_semaphore;
|
|
|
+ char login[256];
|
|
|
+ char password[256];
|
|
|
+ char bound_ip[64];
|
|
|
+ char message[4096];
|
|
|
+} dbElem;
|
|
|
+
|
|
|
+typedef struct {
|
|
|
+ int sockd;
|
|
|
+ struct sockaddr_in client_sockaddr;
|
|
|
+ int client_sockaddr_size;
|
|
|
+} clientData;
|
|
|
+
|
|
|
+// DAEMON BASE FUNCTIONS
|
|
|
+
|
|
|
+// Starts server daemon, returns 1 on success, otherwise 0
|
|
|
+void start_server();
|
|
|
+
|
|
|
+// Stops server daemon, returns 1 on success, otherwise 0
|
|
|
+void stop_server();
|
|
|
+
|
|
|
+// Shows help and returns (is used if no parameter specified)
|
|
|
+void show_help();
|
|
|
+
|
|
|
+// Runs server event loop (called inside daemon)
|
|
|
+void process_server();
|
|
|
+
|
|
|
+
|
|
|
+// SERVER BASE FUNCTIONS
|
|
|
+
|
|
|
+// Creates socket, binds it to defined port and makes ready to listen for connections.
|
|
|
+int create_socket();
|
|
|
+
|
|
|
+// Opens (and creates, if necessary) file for user data and maps it to pointer in shared mode via memory-mapping.
|
|
|
+void *open_database();
|
|
|
+
|
|
|
+// Creates worker subprocess for connection, which lives while connection is established and communicates with client.
|
|
|
+// Returns worker pid to parent soon after creation (non-blocking communication).
|
|
|
+int process_request(int client_d, struct sockaddr *addr, void *database);
|
|
|
+
|
|
|
+
|
|
|
+// WORKER BASE FUNCTIONS
|
|
|
+
|
|
|
+// Waits until user request, then returns path, client_ip and header information
|
|
|
+void receive_request(m_string*path, m_string*client_ip, m_string*http_header);
|
|
|
+
|
|
|
+// Checks if user ip is bound to any login in database. Returns username, if bound and nullptr - if not.
|
|
|
+m_string check_user_authorisation(m_string client_ip);
|
|
|
+
|
|
|
+// Tries to update user message. Returns 1, if successfull, or 0 - if not;
|
|
|
+int update_user_message(m_string username, m_string msg);
|
|
|
+
|
|
|
+void send_404_not_found(int sockd);
|
|
|
+
|
|
|
+void send_401_not_authorised(int sockd);
|
|
|
+
|
|
|
+void send_403_forbidden(int sockd);
|
|
|
+
|
|
|
+void send_200_user_message(int sockd);
|
|
|
+
|
|
|
+void send_error_invalid_message(int sockd);
|
|
|
+
|
|
|
+// WORKER ADDITIONAL FUNCTIONS
|
|
|
+
|
|
|
+// Sends message
|
|
|
+void send_message(int sockd, const m_string msg);
|
|
|
+
|
|
|
+// Returns client address depending on ipv4/ipv6 protocol
|
|
|
+void *get_client_addr(struct sockaddr *);
|
|
|
+
|
|
|
+// Parses http request
|
|
|
+void* handle_request(void* data);
|
|
|
+
|
|
|
+void parse_http_request(const char *, sHTTPHeader *);
|
|
|
+
|
|
|
+// ============================================================================================= //
|
|
|
+
|
|
|
+int main(int argc, char** argv) {
|
|
|
+ if (argc < 2) {
|
|
|
+ show_help();
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ if (strcmp(argv[1], "start") == 0) {
|
|
|
+ start_server();
|
|
|
+ return 0;
|
|
|
+ } else if (strcmp(argv[1], "stop") == 0) {
|
|
|
+ stop_server();
|
|
|
+ return 0;
|
|
|
+ } else if (strcmp(argv[1], "help") == 0) {
|
|
|
+ show_help();
|
|
|
+ return 0;
|
|
|
+ } else {
|
|
|
+ show_help();
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+void start_server() {
|
|
|
+ FILE *lock_file = fopen(LOCK_FILE_PATH, "r");
|
|
|
+ if (lock_file) {
|
|
|
+ fprintf(stderr, "Error: seems like server is already running!\nStop it before starting the new one!\n");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ pid_t pid = fork();
|
|
|
+
|
|
|
+ if (pid == -1) {
|
|
|
+ fprintf(stderr, "Error: cannot create server! (fork exited with error: %s)\n", strerror(errno));
|
|
|
+ return;
|
|
|
+ } else if (pid == 0) {
|
|
|
+ process_server();
|
|
|
+ return;
|
|
|
+ } else {
|
|
|
+ printf("Server started with pid = %d\n", pid);
|
|
|
+ lock_file = fopen(LOCK_FILE_PATH, "w");
|
|
|
+ fprintf(lock_file, "%d", pid);
|
|
|
+ fclose(lock_file);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+void stop_server() {
|
|
|
+ FILE *lock_file = fopen(LOCK_FILE_PATH, "r");
|
|
|
+ if (!lock_file) {
|
|
|
+ fprintf(stderr, "Error: cannot stop server (no running server found)!\n");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ int pid;
|
|
|
+ if (fscanf(lock_file, "%d", &pid) != 1) {
|
|
|
+ fprintf(stderr, "Error: cannot stop server (pid read error)!\n");
|
|
|
+ fclose(lock_file);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (kill(pid, SIGTERM) != 0) {
|
|
|
+ fprintf(stderr, "Warning: server pid is incorrect, server might be already stopped (exited), but lock file still exists.\n");
|
|
|
+ }
|
|
|
+
|
|
|
+ fclose(lock_file);
|
|
|
+ if (remove(LOCK_FILE_PATH) != 0) {
|
|
|
+ fprintf(stderr, "Error: cannot remove server lock file (%s), but server was stopped...\n", LOCK_FILE_PATH);
|
|
|
+ } else {
|
|
|
+ printf("Server stopped successfully.\n");
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+void show_help() {
|
|
|
+ printf("Using: server <COMMAND>\n"
|
|
|
+ "\n"
|
|
|
+ "<COMMAND> values:\n"
|
|
|
+ "start - starts server daemon, if there's no one already started.\n"
|
|
|
+ "\n"
|
|
|
+ "stop - stops running server daemon if there's one.\n"
|
|
|
+ "\n"
|
|
|
+ "help - shows this help.\n"
|
|
|
+ "");
|
|
|
+}
|
|
|
+
|
|
|
+void * get_client_addr(struct sockaddr * sa) {
|
|
|
+ if (sa -> sa_family == AF_INET) {
|
|
|
+ return &(((struct sockaddr_in * ) sa) -> sin_addr);
|
|
|
+ }
|
|
|
+
|
|
|
+ return &(((struct sockaddr_in6 * ) sa) -> sin6_addr);
|
|
|
+}
|
|
|
+
|
|
|
+void process_server() {
|
|
|
+ int sock = create_socket();
|
|
|
+
|
|
|
+ if (sock < 0) {
|
|
|
+ fprintf(stderr, "Server: Error, cannot create socket!\n");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ printf("Server: server created and listening on port %d.\n", SERVER_PORT);
|
|
|
+
|
|
|
+ while (1) {
|
|
|
+ clientData *data = malloc(sizeof(clientData)); // would be freed in thread after its finishing
|
|
|
+ data->sockd = accept(sock, (struct sockaddr *) &data->client_sockaddr, &data->client_sockaddr_size);
|
|
|
+
|
|
|
+ pthread_t thread_id;
|
|
|
+ pthread_attr_t attr;
|
|
|
+ pthread_attr_init(&attr);
|
|
|
+
|
|
|
+ printf("Server: got new connection, creating worker thread.\n");
|
|
|
+ pthread_create(&thread_id, &attr, handle_request, data);
|
|
|
+ printf("Server: created worker thread %d.\n", thread_id);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+int create_socket() {
|
|
|
+ printf("Creating socket\n");
|
|
|
+ int sock = socket(PF_INET, SOCK_STREAM, 0);
|
|
|
+
|
|
|
+ int on = 1;
|
|
|
+ setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
|
|
|
+
|
|
|
+ /* initialize the server's sockaddr */
|
|
|
+ struct sockaddr_in server_sockaddr;
|
|
|
+ memset(&server_sockaddr, 0, sizeof(server_sockaddr));
|
|
|
+ server_sockaddr.sin_family = AF_INET;
|
|
|
+ server_sockaddr.sin_addr.s_addr = htonl(INADDR_ANY);
|
|
|
+ server_sockaddr.sin_port = htons(SERVER_PORT);
|
|
|
+
|
|
|
+ printf("Binding socket %d to sockaddr %p with size %d\n", sock, (struct sockaddr *)&server_sockaddr, sizeof(server_sockaddr));
|
|
|
+ int bind_result = bind(sock, (struct sockaddr *)&server_sockaddr, sizeof(server_sockaddr));
|
|
|
+ if (bind_result < 0) {
|
|
|
+ fprintf(stderr, "Server: Error: bind failed!");
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+
|
|
|
+ listen(sock, MAX_CONNECTIONS);
|
|
|
+
|
|
|
+ return sock;
|
|
|
+}
|
|
|
+
|
|
|
+void* handle_request(void* data) {
|
|
|
+
|
|
|
+ clientData* client_data = data;
|
|
|
+
|
|
|
+ char ip[INET_ADDRSTRLEN];
|
|
|
+ inet_ntop(AF_INET, get_client_addr((struct sockaddr *) &client_data->client_sockaddr), ip, sizeof(ip));
|
|
|
+
|
|
|
+ printf("Worker %d: Established connection with %s beginning work.\n", pthread_self(), ip);
|
|
|
+
|
|
|
+ const int request_buffer_size = 65536;
|
|
|
+ char request[request_buffer_size];
|
|
|
+
|
|
|
+ int bytes_recvd = recv(client_data->sockd, request, request_buffer_size - 1, 0);
|
|
|
+
|
|
|
+ if (bytes_recvd < 0) {
|
|
|
+ fprintf(stderr, "error recv: %s\n", strerror(errno));
|
|
|
+ return NULL;
|
|
|
+ }
|
|
|
+ request[bytes_recvd] = '\0';
|
|
|
+
|
|
|
+ printf("request:\n%s\n", request);
|
|
|
+
|
|
|
+ sHTTPHeader req;
|
|
|
+ parse_http_request(request, &req);
|
|
|
+
|
|
|
+ if (req.type == eHTTP_GET) {
|
|
|
+ send_message(client_data->sockd, "Hello! GET received...");
|
|
|
+ } else if (req.type == eHTTP_POST) {
|
|
|
+ send_message(client_data->sockd, "Hello! POST received...");
|
|
|
+ } else {
|
|
|
+ send_404_not_found(client_data->sockd);
|
|
|
+ }
|
|
|
+
|
|
|
+ close(client_data->sockd);
|
|
|
+ printf("Worker %d: Finished.\n", pthread_self());
|
|
|
+ return NULL;
|
|
|
+}
|
|
|
+
|
|
|
+void parse_http_request(const char *apstrRequest, sHTTPHeader *apHeader) {
|
|
|
+ int type_length = 0;
|
|
|
+ char type[255] = {0};
|
|
|
+ int index = 0;
|
|
|
+
|
|
|
+ apHeader->type = eHTTP_UNKNOWN;
|
|
|
+
|
|
|
+ sscanf(&apstrRequest[index], "%s", type);
|
|
|
+
|
|
|
+ type_length = strlen(type);
|
|
|
+ if (!strcmp(type, "GET")) {
|
|
|
+ apHeader->type = eHTTP_GET;
|
|
|
+ index += type_length + 1;
|
|
|
+ sscanf(&apstrRequest[index], "%s", apHeader->path);
|
|
|
+ } else {
|
|
|
+ if (!strcmp(type, "POST")) {
|
|
|
+ apHeader->type = eHTTP_POST;
|
|
|
+ char *pch = strstr(apstrRequest, "\r\n\r\n");
|
|
|
+ pch += 4;
|
|
|
+ strcpy(apHeader->path, pch);
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+void send_message(int aSock, const char *apstrMessage) {
|
|
|
+ char buffer[65536] = {0};
|
|
|
+
|
|
|
+ strcat(buffer, "HTTP/1.1 200 OK\n\n");
|
|
|
+ strcat(buffer, "<h1>");
|
|
|
+ strcat(buffer, apstrMessage);
|
|
|
+ strcat(buffer, "</h1>");
|
|
|
+
|
|
|
+ int len = strlen(buffer);
|
|
|
+ send(aSock, buffer, len, 0);
|
|
|
+}
|
|
|
+
|
|
|
+void send_404_not_found(int sockd) {
|
|
|
+ const char *buffer = "HTTP/1.1 404 \n\n";
|
|
|
+ int len = strlen(buffer);
|
|
|
+ send(sockd, buffer, len, 0);
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+// server: got connection from 127.0.0.1
|
|
|
+// request:
|
|
|
+// GET /index.html HTTP/1.1
|
|
|
+// Host: localhost:3490
|
|
|
+// Connection: keep-alive
|
|
|
+// Upgrade-Insecure-Requests: 1
|
|
|
+// User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/68.0.3440.75 Chrome/68.0.3440.75 Safari/537.36
|
|
|
+// Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
|
|
|
+// Accept-Encoding: gzip, deflate, br
|
|
|
+// Accept-Language: en-US,en;q=0.9,ru;q=0.8
|