From fdb2c3ba4f4d8bacee41df910a4e6f81d15a8b4d Mon Sep 17 00:00:00 2001 From: Alexander Zhylin Date: Thu, 16 Oct 2025 14:12:55 +0300 Subject: [PATCH] Code release --- .gitignore | 16 +++ Makefile | 30 +++++ README.md | 11 +- TODO.md | 12 ++ deps.mk | 7 ++ setup.sh | 19 ++++ src/client.c | 47 ++++++++ src/client.h | 7 ++ src/file_p.c | 305 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/file_p.h | 16 +++ src/main.c | 16 +++ src/main.h | 77 +++++++++++++ src/server.c | 143 +++++++++++++++++++++++ src/server.h | 8 ++ src/session.c | 237 +++++++++++++++++++++++++++++++++++++++ src/session.h | 12 ++ src/user.c | 89 +++++++++++++++ src/user.h | 8 ++ 18 files changed, 1058 insertions(+), 2 deletions(-) create mode 100644 Makefile create mode 100644 TODO.md create mode 100644 deps.mk create mode 100755 setup.sh create mode 100644 src/client.c create mode 100644 src/client.h create mode 100644 src/file_p.c create mode 100644 src/file_p.h create mode 100644 src/main.c create mode 100644 src/main.h create mode 100644 src/server.c create mode 100644 src/server.h create mode 100644 src/session.c create mode 100644 src/session.h create mode 100644 src/user.c create mode 100644 src/user.h diff --git a/.gitignore b/.gitignore index 845cda6..1eb3055 100644 --- a/.gitignore +++ b/.gitignore @@ -53,3 +53,19 @@ dkms.conf # debug information files *.dwo + +# Valgrind debug log +WG_LOG + +#Tags +tags + +#Binaries +bin +build + +#VS code +.vscode + +#wdir contents +wdir \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..15bb5a1 --- /dev/null +++ b/Makefile @@ -0,0 +1,30 @@ +CFLAGS := -ggdb -Wall -pedantic + +SRC_DIR := src +OBJ_DIR := src/obj +BIN_DIR := bin + +SRCS := $(wildcard $(SRC_DIR)/*.c) +OBJS := $(SRCS:$(SRC_DIR)/%.c=$(OBJ_DIR)/%.o) + +$(OBJ_DIR)/%.o: $(SRC_DIR)/%.c $(SRC_DIR)/%.h + $(CC) $(CFLAGS) -c $< -o $@ + +server: $(OBJS) + $(CC) $(CFLAGS) $^ -o $(BIN_DIR)/$@ + +ifneq (clean, $(MAKECMDGOALS)) +-include deps.mk +endif + +deps.mk: $(SRCS) + $(CC) -MM $^ > $(SRC_DIR)/$@ + +clean: + rm -f $(OBJ_DIR)/*.o $(BIN_DIR)/server + +run: + $(BIN_DIR)/server wdir + +valgrind: + valgrind --tool=memcheck ./server wdir \ No newline at end of file diff --git a/README.md b/README.md index 79b545b..4b380b7 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,9 @@ -# bbs-server -Bulletin Board System (server part) +# Bulletin Board System (Server Side) +This is my sandbox project. I made it test and develop my programming skills. Maybe one day it become a mature and production-ready project. + +## Setup +1. Create necessary file structure by running `$ ./setup.sh`. +It will create all necessary file structure in order to run the program correctly and will create a test user `user` with password `1234` with all privileges. **Delete this user before running into production!** It will also create a test file inside the `wdir/storage/` directory. + +2. Build the binary running `make` or `make server` +3. Run it by `make run` \ No newline at end of file diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..b147059 --- /dev/null +++ b/TODO.md @@ -0,0 +1,12 @@ +# Development plans + +## Features + +- [ ] Database integration +- [ ] Registration +- [ ] Message to admin +- [ ] Advanced folder structure for the storage +- [ ] File encryption +- [ ] Connection encryption + +- [ ] Multithreading \ No newline at end of file diff --git a/deps.mk b/deps.mk new file mode 100644 index 0000000..e4e6f36 --- /dev/null +++ b/deps.mk @@ -0,0 +1,7 @@ +client.o: src/client.c src/client.h src/main.h src/file_p.h src/session.h +file_p.o: src/file_p.c src/file_p.h src/main.h src/session.h +main.o: src/main.c src/main.h src/file_p.h src/server.h +server.o: src/server.c src/main.h src/session.h src/user.h +session.o: src/session.c src/session.h src/main.h src/client.h \ + src/file_p.h src/user.h +user.o: src/user.c src/main.h src/session.h diff --git a/setup.sh b/setup.sh new file mode 100755 index 0000000..c38e99f --- /dev/null +++ b/setup.sh @@ -0,0 +1,19 @@ +#!/bin/sh + +mkdir wdir +mkdir wdir/storage +touch wdir/file_descriptions +touch wdir/welcome +touch wdir/userlist + +echo "Welcome to the BBS Setup!" >> wdir/welcome +echo "user 1234 15" >> wdir/userlist + +# Test file +touch wdir/storage/testfile.txt + +printf 'testfile.txt\nuser\n1\nThis is a test file. You may not need it.\n:END:\n' >> wdir/file_descriptions + +echo "Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. \ +Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per \ +conubia nostra inceptos himenaeos." >> wdir/storage/testfile.txt \ No newline at end of file diff --git a/src/client.c b/src/client.c new file mode 100644 index 0000000..e1a8e07 --- /dev/null +++ b/src/client.c @@ -0,0 +1,47 @@ +#include "client.h" +#include "file_p.h" +#include "main.h" +#include "session.h" +#include +#include +#include +#include + +// TODO: синхронизовать c C++ +void process_client_command(char *line, session *sess, server_data_t *s_d) { + char arg_1[32]; + char arg_2[32]; + int res; + + sscanf(line, "%s %s", arg_1, arg_2); + + if (!strcmp(arg_1, "file")) { + /* LIST */ + if (!strcmp(arg_2, "list")) { + file_list(sess, s_d); + return; + } + + /* UPLOAD */ + if (!strcmp(arg_2, "upload")) { + res = file_receive_prepare(sess, line, s_d); + if (!res) + sess->state = OP_UPLOAD; + return; + } + + /* DOWNLOAD */ + if (!strcmp(arg_2, "download")) { + res = file_send_prepare(sess, line, s_d); + if (!res) + sess->state = OP_DOWNLOAD; + return; + } + } + + if (!strncmp(line, "exit", sizeof "exit")) { + sess->state = ERR; + sess->reason = EXIT; + session_send_string(sess, "OK. Bye!\n"); + } +} \ No newline at end of file diff --git a/src/client.h b/src/client.h new file mode 100644 index 0000000..9b515a8 --- /dev/null +++ b/src/client.h @@ -0,0 +1,7 @@ +#ifndef CLIENT_H +#define CLIENT_H + +#include "main.h" +void process_client_command(char *line, session *sess, server_data_t *s_d); + +#endif \ No newline at end of file diff --git a/src/file_p.c b/src/file_p.c new file mode 100644 index 0000000..0d57d32 --- /dev/null +++ b/src/file_p.c @@ -0,0 +1,305 @@ +#include "file_p.h" +#include "main.h" +#include "session.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +char *extract_second_arg(char *); +size_t get_file_size(char *, char *); + +void file_list(session *sess, server_data_t *s_d) { + // TODO: change of format (rewrite in C++) + char list_end[] = "list_end\n"; + fl_t *current; + current = s_d->fl_start; + char item_h[256]; + do { + int n_len = strlen(sess->uname); + if (strncmp(current->owner, sess->uname, n_len) && !current->permissions) { + continue; + } + int h_len = sprintf(item_h, "%s %zu %s", current->name, current->size, + current->owner); + int d_len = strlen(current->description); + int f_len = h_len + d_len + 2; + char *full_str = malloc(sizeof(char) * f_len); + sprintf(full_str, "%s %s", item_h, current->description); + int i; + for (i = h_len; i < h_len + d_len; i++) { + if (full_str[i] == '\n') { + full_str[i] = '\a'; + } + } + write(sess->sd, full_str, f_len-1); // -1 : do not include \0 + free(full_str); + } while ((current = current->next) != NULL); + write(sess->sd, list_end, sizeof(list_end)); +} + +int file_send_prepare(session *sess, char *line, server_data_t *s_d) { + // TODO: рефактор в C++ + int sd = sess->sd; + char fname[32]; + sscanf(line, "%*s %*s %s", fname); + char filepath[256]; + sprintf(filepath, "%s/%s", STORAGE_FOLDER, fname); + size_t fsize; + char st_message[256]; + + int file_d = open(filepath, O_RDONLY); + + if (file_d == -1) { + fprintf(stdout, "ERROR: %d\n", errno); + char err_mes[256]; + int mlen; + mlen = sprintf(err_mes, "Can't open file named \"%s\"\n", fname); + write(sd, err_mes, mlen); + return -1; + } + + fsize = lseek(file_d, 0, SEEK_END); + lseek(file_d, 0, SEEK_SET); + + sess->fd = file_d; + sess->fsize = fsize; + sess->f_rest = fsize; + sess->fname = malloc(strlen(fname)); + int fname_len = strlen(fname) - 1; + strncpy(sess->fname, fname, fname_len); + sess->fname[fname_len] = 0; + return 0; +} + +int file_receive_prepare(session *sess, char *line, server_data_t *s_d) { + int sd = sess->sd; + char fname[64]; + size_t fsize; + int perm; + sscanf(line, "file upload \"%s %zd %d", fname, &fsize, &perm); + sess->fname = malloc(sizeof(char) * strlen(fname)); + sess->f_perm = (char)perm; + strncpy(sess->fname, fname, strlen(fname) - 1); // remove the last \" + sess->fpath = malloc(strlen(sess->fname) + sizeof(STORAGE_FOLDER) + 2); + sprintf(sess->fpath, "%s/%s", STORAGE_FOLDER, sess->fname); + + char mes[256]; + char mes_len = 0; + struct statvfs st_str; + statvfs(".", &st_str); + size_t available_space = st_str.f_bavail * st_str.f_bsize; + if (strlen(sess->fname) > st_str.f_namemax) { + mes_len = sprintf(mes, "file name is tool long\n"); + write(sd, mes, mes_len); + sess->state = OP_WAIT; + clear_file_from_sess(sess); + return -1; + } + if (fsize > available_space) { + mes_len = sprintf(mes, "There is no space for such length! (%ld)\n", fsize); + write(sd, mes, mes_len); + clear_file_from_sess(sess); + return -1; + } + int file_d = open(sess->fpath, O_WRONLY | O_CREAT | O_EXCL, 0666); + if (file_d == -1) { + if (errno == EEXIST) { + mes_len = + sprintf(mes, "File with name \"%s\" is already exist\n", sess->fname); + write(sd, mes, mes_len); + } + mes_len = + sprintf(mes, "Can't create file with such name: \"%s\"\n", sess->fname); + write(sd, mes, mes_len); + clear_file_from_sess(sess); + return -1; + } + mes_len = sprintf(mes, "accept"); + sess->fd = file_d; + sess->fsize = fsize; + sess->f_rest = fsize; + write(sd, mes, mes_len); + return 0; +} + +void clear_file_from_sess(session *sess) { + if (sess->fname != NULL) { + free(sess->fname); + sess->fname = NULL; + } + if (sess->fpath != NULL) { + free(sess->fpath); + sess->fpath = NULL; + } + if (sess->fdesc != NULL) { + free(sess->fdesc); + sess->fdesc = NULL; + } + if (sess->fd > -1) { + close(sess->fd); + } + sess->fd = -1; + sess->fsize = 0; + sess->f_rest = 0; +} + +void file_download_upload(session *sess, enum f_actions f_action) { + int source_d = f_action == F_DOWNLOAD ? sess->fd : sess->sd; + int dest_d = f_action == F_DOWNLOAD ? sess->sd : sess->fd; + char buf[INBUFSIZE]; + int rlen = read(source_d, buf, INBUFSIZE); + if (rlen == 0) { + if (sess->f_rest) { + if (f_action == F_DOWNLOAD) { + fprintf(stderr, "Error downloading file %s!\n", sess->fname); + } else { + fprintf(stderr, "Error uploading file %s!\n", sess->fname); + unlink(sess->fpath); /* remove file */ + } + clear_file_from_sess(sess); + } + sess->state = OP_WAIT; + return; + } + write(dest_d, buf, rlen); + sess->f_rest -= rlen; + if (!sess->f_rest) { + if (f_action == F_DOWNLOAD) { + printf("File %s is downloaded from the server\n", sess->fname); + clear_file_from_sess(sess); + } else { + printf("File %s is uploaded to the server\n", sess->fname); + } + if (f_action == F_UPLOAD) { + sess->state = OP_UPLOAD_DESCRIPTION; + } else { + sess->state = OP_WAIT; + } + } +} + +size_t get_file_size(char *dir_n, char *file_n) { + char filepath[256]; + int dn_len; + size_t fsize; + strcpy(filepath, dir_n); + dn_len = strlen(filepath); + strcat(filepath + dn_len, file_n); + + int file_d = open(filepath, O_RDONLY); + + if (file_d == -1) + return 0; + + fsize = lseek(file_d, 0, SEEK_END); + lseek(file_d, 0, SEEK_SET); + + close(file_d); + return fsize; +} + +/* returns 1 if there is :END: ; 0 if opposite */ +int file_upload_description(session *sess, char *line, server_data_t *s_d) { + for (;;) { + if (line != NULL && sess->fdesc == NULL) { /* first query */ + sess->fdesc = malloc(strlen(line) + 1); + strcpy(sess->fdesc, line); + } else { /* subsequent queries */ + if (line == NULL) { + if (sess->buf_used > 0) { + query_extract_from_buf(sess, &line); + } else { + return 0; + } + } + sess->fdesc = + realloc(sess->fdesc, strlen(sess->fdesc) + strlen(line) + 1); + strcat(sess->fdesc, line); + } + char *desc_end = strstr(line, ":END:"); + if (desc_end != NULL) { + return 1; + } + } +} + +// TODO: синхронизовать c C++ +void get_files_descriptions(server_data_t *s_d) { + int fd = open(FILE_DESCRIPTIONS_NAME, O_RDONLY); + if (-1 == fd) { + perror(FILE_DESCRIPTIONS_NAME); + exit(8); + } + int rlen; + + char buf[INBUFSIZE] = ""; + chdir(STORAGE_FOLDER); + + while ((rlen = read(fd, buf, FILEBUFSIZE)) > 0) { + char *buf_pos = buf; + char tmp[256]; + while (buf_pos[0] != '\0') { + fl_t *fl_item = malloc(sizeof(fl_t)); + fl_item->next = NULL; + + /* file name */ + sscanf(buf_pos, "%s\n", tmp); + buf_pos += strlen(tmp)+1; + fl_item->name = malloc(sizeof(char) * (strlen(tmp) + 1)); + strcpy(fl_item->name,tmp); + + /* file owner */ + sscanf(buf_pos, "%s\n", tmp); + buf_pos += strlen(tmp)+1; + fl_item->owner = malloc(sizeof(char) * (strlen(tmp) + 1)); + strcpy(fl_item->owner, tmp); + + /* file permissions */ + sscanf(buf_pos, "%s\n", tmp); + buf_pos += strlen(tmp)+1; + fl_item->permissions = atoi(tmp); + + /* file size */ + int fd = open(fl_item->name, O_RDONLY); + if (-1 == fd) { + perror(fl_item->name); + free(fl_item->name); + free(fl_item->owner); + free(fl_item); + buf_pos += strstr(buf_pos, ":END:") + 6 - buf_pos; + continue; + } + fl_item->size = lseek(fd, 0, SEEK_END); + close(fd); + + /* file description */ + int d_len = strstr(buf_pos, ":END:\n") - buf_pos; + fl_item->description = malloc(d_len + 1); + strncpy(fl_item->description, buf_pos, d_len); + fl_item->description[d_len] = 0; + buf_pos += d_len + 6; + if (s_d->fl_start == NULL) { + s_d->fl_start = fl_item; + s_d->fl_current = fl_item; + } else { + s_d->fl_current->next = fl_item; + s_d->fl_current = fl_item; + } + } + } + close(fd); + chdir(".."); +} \ No newline at end of file diff --git a/src/file_p.h b/src/file_p.h new file mode 100644 index 0000000..9c0b58f --- /dev/null +++ b/src/file_p.h @@ -0,0 +1,16 @@ +#ifndef FILEP_H +#define FILEP_H +#include "main.h" +void get_files_descriptions(server_data_t *); +void file_list(session *, server_data_t *); +int file_send_prepare(session *, char *, server_data_t *); +enum f_actions { + F_UPLOAD, + F_DOWNLOAD, +}; +void file_download_upload(session *sess, enum f_actions); +int file_receive_prepare(session *sess, char *line, server_data_t *s_d); +int file_upload_description(session *sess, char *line, server_data_t *s_d); +int file_save_db(session *sess, server_data_t *s_d); +void clear_file_from_sess(session *sess); +#endif \ No newline at end of file diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..be2d5d3 --- /dev/null +++ b/src/main.c @@ -0,0 +1,16 @@ +#include "main.h" +#include "file_p.h" +#include "server.h" + +/* *** MAIN *** */ +int main(int argc, char *argv[]) { + server_data_t server_data; + prepare_start(argc, argv); + server_data.welcome_message = get_welcome_mes(); + server_data.fl_start = NULL; + server_data.fl_current = NULL; + get_files_descriptions(&server_data); + server_data.ls = start_server(); + server_main_loop(&server_data); + return 0; +} \ No newline at end of file diff --git a/src/main.h b/src/main.h new file mode 100644 index 0000000..5aba0c8 --- /dev/null +++ b/src/main.h @@ -0,0 +1,77 @@ +#ifndef MAIN_H +#define MAIN_H +#include +#define PORT 2000 +#define MAX_CONNECTIONS 1024 +#define INBUFSIZE 1024 +#define FILEBUFSIZE 4096 +#define WSIZE 128 +#define LISTEN_QLEN 32 +#define STORAGE_FOLDER "./storage" +#define FILE_DESCRIPTIONS_NAME "./file_descriptions" +#define FILE_USER_LIST_NAME "userlist" +#define WELCOME_FILE_NAME "welcome" + +enum sess_states { + OP_EDIT_FILE, + OP_DOWNLOAD, + OP_EDIT_USER, + OP_LOGIN_USR, + OP_LOGIN_PSS, + OP_WAIT, + OP_UPLOAD, + OP_UPLOAD_DESCRIPTION, + ERR, + FIN +}; + +enum user_privileges { + ADD_FILE = 01, + EDIT_FILES = 02, + REMOVE_FILES = 04, + EDIT_USER = 010 +}; + +enum error_reason { + NO_REASON, + LOGIN, + EXIT, +}; + +typedef struct session { + unsigned long from_ip; + unsigned short from_port; + char buf[INBUFSIZE]; + int buf_used; + enum sess_states state; + enum error_reason reason; + char privileges; + char *uname; + int sd; /* session descriptor */ + int fd; /* file descriptor */ + char *fname; + char *fpath; + char f_perm; + char *fdesc; + size_t fsize; + size_t f_rest; + long int userpos; /* hint to the position in user list */ +} session; + +typedef struct fl_t { + char *name; + char *description; + char *owner; + char permissions; + size_t size; + struct fl_t *next; +} fl_t; + +typedef struct server_data_t { + fl_t *fl_start; + fl_t *fl_current; + char *welcome_message; + int ls; +} server_data_t; + +#endif \ No newline at end of file diff --git a/src/server.c b/src/server.c new file mode 100644 index 0000000..2ad236e --- /dev/null +++ b/src/server.c @@ -0,0 +1,143 @@ +#include "main.h" +#include "session.h" +#include "user.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +void server_main_loop(server_data_t *s_d) { + fd_set readfds; + int i, sr; + + session *connections[MAX_CONNECTIONS]; + + for (i = 0; i < MAX_CONNECTIONS; i++) { + connections[i] = NULL; + } + + int maxfd, res; + + for (;;) { + FD_ZERO(&readfds); + FD_SET(s_d->ls, &readfds); + maxfd = s_d->ls; + + for (i = 0; i < MAX_CONNECTIONS; i++) { + if (connections[i] != NULL) { + FD_SET(i, &readfds); + if (i > maxfd) + maxfd = i; + /* active uploads/downloads */ + int fd = connections[i]->fd; + if (fd > -1) { + FD_SET(fd, &readfds); + if (fd > maxfd) + maxfd = fd; + } + } + } + + sr = select(maxfd + 1, &readfds, NULL, NULL, NULL); + + if (sr == -1) { + perror("select"); + exit(6); + } + + if (FD_ISSET(s_d->ls, &readfds)) { + if (-1 == accept_client(s_d->ls, connections, s_d->welcome_message)) { + fprintf(stderr, "Can't accept connection, error: %d\n", errno); + } + } + + for (i = 0; i < MAX_CONNECTIONS; i++) { + /* download/upload file */ + if (connections[i] != NULL && (connections[i]->state == OP_UPLOAD || + connections[i]->state == OP_DOWNLOAD)) { + if (connections[i] != NULL && connections[i]->fd > -1 && + FD_ISSET(connections[i]->fd, &readfds)) { + perform_session_action(connections[i], NULL, s_d); + } + } else if (connections[i] != NULL && FD_ISSET(i, &readfds)) { + /* server/client requests */ + session *sess = connections[i]; + char *line; + res = session_do_read(sess, &line); + if (line != NULL && res) { + perform_session_action(sess, line, s_d); + free(line); + if (process_error(sess)) { + close_session(connections, i); + } + } + if (res == 0) { + close_session(connections, i); + } + } + } + } +} + +char *get_welcome_mes() { + int fd = open(WELCOME_FILE_NAME, O_RDONLY); + if (fd == -1) { + perror(WELCOME_FILE_NAME); + exit(7); + } + int filesize = lseek(fd, 0, SEEK_END); + lseek(fd, 0, SEEK_SET); + char *welcome_message = + mmap(NULL, filesize, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0); + if (welcome_message == MAP_FAILED) { + perror(WELCOME_FILE_NAME); + close(fd); + exit(5); + } + close(fd); + return welcome_message; +} + +int start_server() { + int res; + int ls = socket(AF_INET, SOCK_STREAM, 0); + struct sockaddr_in s_addr; + + s_addr.sin_family = AF_INET; + s_addr.sin_port = htons(PORT); + s_addr.sin_addr.s_addr = htonl(INADDR_ANY); + + int opt = 1; + setsockopt(ls, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); + + res = bind(ls, (struct sockaddr *)&s_addr, sizeof(s_addr)); + if (res == -1) { + perror("bind"); + exit(3); + } + + res = listen(ls, LISTEN_QLEN); + if (res == -1) { + perror("listen"); + exit(4); + } + return ls; +} + +void prepare_start(int argc, char *argv[]) { + if (argc != 2) { + fprintf(stderr, "Usage: %s \n", argv[0]); + exit(1); + } + + if (-1 == chdir(argv[1])) { + perror(argv[1]); + exit(2); + } +} \ No newline at end of file diff --git a/src/server.h b/src/server.h new file mode 100644 index 0000000..a370e5c --- /dev/null +++ b/src/server.h @@ -0,0 +1,8 @@ +#ifndef SERVER_H +#define SERVER_H +#include "main.h" +int start_server(); +char *get_welcome_mes(); +void server_main_loop(server_data_t *); +void prepare_start(int argc, char *argv[]); +#endif \ No newline at end of file diff --git a/src/session.c b/src/session.c new file mode 100644 index 0000000..f4afff0 --- /dev/null +++ b/src/session.c @@ -0,0 +1,237 @@ +#include "session.h" +#include "client.h" +#include "file_p.h" +#include "main.h" +#include "user.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +session *make_new_session(int fd, struct sockaddr_in *from, char *wm); + +int accept_client(int ls, session *connections[], char *wm) { + int sd; + struct sockaddr_in addr; + socklen_t len = sizeof(addr); + sd = accept(ls, (struct sockaddr *)&addr, &len); + if (sd == -1) { + perror("accept"); + return -1; + } + session *sess = make_new_session(sd, &addr, wm); + fprintf(stdout, "New connection: %s:%u\n", inet_ntoa(addr.sin_addr), + ntohs(addr.sin_port)); + connections[sd] = sess; + return 0; +} + +session *make_new_session(int fd, struct sockaddr_in *from, char *wm) { + struct session *sess = malloc(sizeof(*sess)); + sess->from_ip = ntohl(from->sin_addr.s_addr); + sess->from_port = ntohs(from->sin_port); + sess->buf_used = 0; + sess->state = OP_LOGIN_USR; + sess->reason = NO_REASON; + sess->uname = NULL; + sess->f_perm = 0; + sess->fdesc = NULL; + sess->sd = fd; + sess->fd = -1; + sess->fsize = 0; + sess->f_rest = 0; + sess->fname = NULL; + sess->fpath = NULL; + session_send_string(sess, wm); + session_send_string(sess, "login> "); + return sess; +} + +void session_send_string(struct session *sess, const char *str) { + write(sess->sd, str, strlen(str)); +} + +int session_do_read(session *sess, char **read_str) { + int rc, bufp = sess->buf_used; + rc = read(sess->sd, sess->buf + bufp, INBUFSIZE - bufp); + sess->buf[bufp + rc] = 0; + if (rc <= 0) { + sess->state = ERR; + return 0; + } + sess->buf_used += rc; + char *line; + query_extract_from_buf(sess, &line); + if (line == NULL) { + *read_str = line; + return 0; + } else { + *read_str = line; + } + if (sess->buf_used >= INBUFSIZE) { + /* we can't read further, no room in the buffer, no whole line yet */ + session_send_string(sess, "Line too long! Good bye...\n"); + sess->state = ERR; + return 0; + } + if (sess->state == FIN) + return 0; + return 1; +} + +// TODO: синхронизовать c C++ +int query_extract_from_buf(session *sess, char **output_line) { + char *line; + int pos = -1; + + if (sess->buf_used > 0) { + char *cptr = strchr(sess->buf, '\n'); + if (cptr != NULL) + pos = cptr - sess->buf; + } else { + return 0; + } + + if (pos == -1) { + int b_used = sess->buf_used; + sess->buf_used = 0; + line = malloc(b_used + 1); + strncpy(line, sess->buf, b_used); + + line[b_used] = 0; + + *output_line = line; + return b_used; + } else { + line = malloc(pos + 2); + strncpy(line, sess->buf, pos + 1); + line[++pos] = 0; + sess->buf_used -= (pos); + if (!pos) + pos++; + memmove(sess->buf, sess->buf + pos, sess->buf_used); + sess->buf[sess->buf_used] = 0; + if (line[pos-2] == '\r') { + line[pos-2] = line[pos-1]; + pos--; + line[pos-1] = 0; + } + *output_line = line; + } + return pos + 1; +} + +void perform_session_action(session *sess, char *line, server_data_t *s_d) { + int state = sess->state; + char tmp_buf[256]; + int priv; + int tmp_buf_pos; + int res; + /* while (sess->buf_used) { */ + switch (state) { + case OP_LOGIN_USR: + res = find_user(line); + if (res == -2) { + sess->state = ERR; + sess->reason = EXIT; + session_send_string(sess, "Bye!"); + break; + } + sess->userpos = res; + sess->state = OP_LOGIN_PSS; + session_send_string(sess, "password> "); + break; + case OP_LOGIN_PSS: + if (sess->userpos == -1) { + sess->state = ERR; + sess->reason = LOGIN; + } else { + if (login(sess, line)) { + sess->state = OP_WAIT; + sprintf(tmp_buf, "Welcome, %s\n", sess->uname); + session_send_string(sess, tmp_buf); + } else { + sess->state = ERR; + sess->reason = LOGIN; + } + } + break; + case OP_WAIT: + process_client_command(line, sess, s_d); + break; + case OP_DOWNLOAD: + file_download_upload(sess, F_DOWNLOAD); + break; + case OP_UPLOAD: + file_download_upload(sess, F_UPLOAD); + break; + case OP_UPLOAD_DESCRIPTION: + res = file_upload_description(sess, line, s_d); + if (res) { + file_save_db(sess, s_d); + session_send_string(sess, "File is saved!\n"); + sess->state = OP_WAIT; + } + break; + } + /* } */ +} + +int file_save_db(session *sess, server_data_t *s_d) { + /* crate and fill the new item */ + fl_t *nitem = malloc(sizeof(fl_t)); + nitem->description = sess->fdesc; + nitem->name = sess->fname; + nitem->size = sess->fsize; + nitem->owner = malloc(strlen(sess->uname)); + strcpy(nitem->owner, sess->uname); + nitem->permissions = sess->f_perm; + nitem->next = NULL; + + /* set to NULL and clear */ + sess->fname = NULL; + free(sess->fpath); + sess->fpath = NULL; + sess->fdesc = NULL; + if (sess->fd > -1) { + close(sess->fd); + } + sess->fd = -1; + sess->fsize = 0; + sess->f_rest = 0; + + /* add new item to list */ + s_d->fl_current->next = nitem; + s_d->fl_current = nitem; + + /* save to the db file */ + int fd = open(FILE_DESCRIPTIONS_NAME, O_WRONLY); + lseek(fd, 0, SEEK_END); + char permissions[4]; + snprintf(permissions, 3, "%d", nitem->permissions); + unsigned int res_len = strlen(nitem->name) + 1 + strlen(nitem->owner) + 1 + + strlen(permissions) + 1 + strlen(nitem->description) + + 1; + char *res_str = malloc(res_len); + res_len = sprintf(res_str, "%s\n%s\n%s\n%s", nitem->name, nitem->owner, + permissions, nitem->description); + nitem->description[strlen(nitem->description) - 6] = 0; + write(fd, res_str, res_len); + close(fd); + return 0; +} + +void close_session(session *connections[], int sd) { + if (connections[sd] != NULL) { + close(sd); + connections[sd]->sd = -1; + free(connections[sd]->uname); + free(connections[sd]); + connections[sd] = NULL; + } +} \ No newline at end of file diff --git a/src/session.h b/src/session.h new file mode 100644 index 0000000..3e3c55d --- /dev/null +++ b/src/session.h @@ -0,0 +1,12 @@ +#ifndef SESSION_H +#define SESSION_H +#include "main.h" +int accept_client(int ls, session *connections[], char *wm); +void session_send_string(struct session *sess, const char *str); +int query_extract_from_buf(session *sess, char **output_line); +int session_do_read(session *sess, char **read_str); +void perform_session_action(session *sess, char *line, server_data_t *s_d); +void close_session(session *connections[], int sd); +/* session *make_new_session(int fd, struct sockaddr_in *from, char *wm); */ +/* int query_extract_from_sess(session *sess, char **output_line); */ +#endif \ No newline at end of file diff --git a/src/user.c b/src/user.c new file mode 100644 index 0000000..e296333 --- /dev/null +++ b/src/user.c @@ -0,0 +1,89 @@ +#include "main.h" +#include "session.h" +#include +#include +#include + +long int find_user(char *uname) { + FILE *fptr = fopen(FILE_USER_LIST_NAME, "r"); + + if (fptr == NULL) { + perror(FILE_USER_LIST_NAME); + exit(9); + } + + char *line = NULL; + size_t bsize = WSIZE; + char b_uname[32]; + long int cur_pos = 0; + int lsize = 0; + + if (!strncmp(uname, "exit", sizeof "exit")) { + return -2; + } + + while (getline(&line, &bsize, fptr)) { + if (line[0] == '\b') + break; + sscanf(line, "%s", b_uname); + if (!strcmp(uname, b_uname)) { + line = NULL; + fclose(fptr); + return cur_pos; + } else { + cur_pos += lsize; + line = NULL; + } + } + + fclose(fptr); + return -1; +} + +/* returns 1 if success */ +int login(session *sess, char *pass) { + FILE *fptr = fopen(FILE_USER_LIST_NAME, "r"); + fseek(fptr, sess->userpos, SEEK_SET); + int res = 0; + + char *line = NULL; + size_t lsize = WSIZE; + char *pass_tok, *name_tok, *priv_tok; + + getline(&line, &lsize, fptr); + name_tok = strtok(line, " "); + pass_tok = strtok(NULL, " "); + + if (!strcmp(pass_tok, pass)) { + int n_len = pass_tok - name_tok - 1; /* exclude space */ + sess->uname = malloc(n_len + 1); + strncpy(sess->uname, name_tok, n_len); + sess->uname[n_len] = 0; + priv_tok = strtok(NULL, " "); + sess->privileges = (char)atoi(priv_tok); + res = 1; + } + fclose(fptr); + return res; +} + +// TODO: синхронизовать c C++ +/* returns 1 if session needs to be closed, 0 - in the opposite case */ +int process_error(session *sess) { + if (sess->state == ERR) { + switch (sess->reason) { + case NO_REASON: + case EXIT: + return 1; + break; + case LOGIN: + session_send_string(sess, "Can't find the user with such credentials\n"); + sess->state = OP_LOGIN_USR; + sess->reason = NO_REASON; + session_send_string(sess, "login_again> "); + break; + } + } + + return 0; +} diff --git a/src/user.h b/src/user.h new file mode 100644 index 0000000..3e299bc --- /dev/null +++ b/src/user.h @@ -0,0 +1,8 @@ +#ifndef USER_H +#define USER_H + +#include "main.h" +int login(session *sess, char *pass); +int process_error(session *sess); +long int find_user(char *uname); +#endif \ No newline at end of file