diff --git a/README.md b/README.md new file mode 100644 index 0000000..dc3df31 --- /dev/null +++ b/README.md @@ -0,0 +1,29 @@ +# tcp-chat + +## Build +1. Clone the repository with submodules. + ``` + git clone --recurse-submodules --depth 0 https://timerix.ddns.net/git/Timerix/tcp-chat.git + ``` +2. Install [cbuild](https://timerix.ddns.net/git/Timerix/cbuild). +3. Build executable + ``` + cd tcp-chat + cbuild build_exec + ``` + +## Usage +**Client:** +```sh +cd bin +./tcp-chat +``` +**Server:** +1. ```sh + cp tcp-chat-server.config.default bin/tcp-chat-server.config + ``` +2. Edit config +3. ```sh + cd bin + ./tcp-chat -l + ``` diff --git a/dependencies/tlibc b/dependencies/tlibc index ae0fa95..4257943 160000 --- a/dependencies/tlibc +++ b/dependencies/tlibc @@ -1 +1 @@ -Subproject commit ae0fa95d6aee2a7427c3e3575d74af1ed360e6e5 +Subproject commit 425794361bc52240bb50001becbb5fb6e9ebcf20 diff --git a/src/client/ServerConnection.c b/src/client/ServerConnection.c index 6657e78..51c036d 100644 --- a/src/client/ServerConnection.c +++ b/src/client/ServerConnection.c @@ -1,5 +1,5 @@ #include "client.h" -#include "network/tcp-chat-protocol/v1.h" +#include "requests/requests.h" void ServerConnection_close(ServerConnection* conn){ if(!conn) @@ -7,6 +7,8 @@ void ServerConnection_close(ServerConnection* conn){ RSA_destroyPublicKey(&conn->server_pk); EncryptedSocketTCP_destroy(&conn->sock); free(conn->session_key.data); + free(conn->name.data); + free(conn->description.data); free(conn); } @@ -64,53 +66,39 @@ Result(ServerConnection*) ServerConnection_open(cstr server_link_cstr){ try_void(socket_connect(_s, conn->server_end)); EncryptedSocketTCP_construct(&conn->sock, _s, NETWORK_BUFFER_SIZE, conn->session_key); - // send PacketHeader and ClientHandshake - // encryption by server public key - PacketHeader packet_header; + // send ClientHandshake using server public key for encryption + PacketHeader req_header; ClientHandshake client_handshake; - try_void(ClientHandshake_tryConstruct(&client_handshake, &packet_header, + try_void(ClientHandshake_tryConstruct(&client_handshake, &req_header, conn->session_key)); - try_void(EncryptedSocketTCP_sendStructRSA(&conn->sock, &conn->rsa_enc, &packet_header)); + try_void(EncryptedSocketTCP_sendStructRSA(&conn->sock, &conn->rsa_enc, &req_header)); try_void(EncryptedSocketTCP_sendStructRSA(&conn->sock, &conn->rsa_enc, &client_handshake)); // receive server response - try_void(EncryptedSocketTCP_recvStruct(&conn->sock, &packet_header)); - try_void(PacketHeader_validateMagic(&packet_header)); - - // handle server response - switch(packet_header.type){ - case PacketType_ErrorMessage: { - ErrorMessage err_msg; - try_void(EncryptedSocketTCP_recvStruct(&conn->sock, &err_msg)); - if(err_msg.msg_size > conn->sock.recv_buf.size) - err_msg.msg_size = conn->sock.recv_buf.size; - Array(u8) err_buf = Array_alloc_size(err_msg.msg_size + 1); - bool err_msg_completed = false; - Defer(if(!err_msg_completed) free(err_buf.data)); + PacketHeader res_header; + ServerHandshake server_handshake; + try_void(recvResponse(&conn->sock, &res_header, &server_handshake, + PacketType_ServerHandshake)); + conn->session_id = server_handshake.session_id; - // receive message content - try_void( - EncryptedSocketTCP_recv( - &conn->sock, - Array_sliceTo(err_buf, err_msg.msg_size), - SocketRecvFlag_WholeBuffer - ) - ); + // get server name + ServerPublicInfoRequest public_info_req; + ServerPublicInfoResponse public_info_res; + ServerPublicInfoRequest_construct(&public_info_req, &req_header, + ServerPublicInfo_Name); + try_void(sendRequest(&conn->sock, &req_header, &public_info_req)); + try_void(recvResponse(&conn->sock, &res_header, &public_info_res, + PacketType_ServerPublicInfoResponse)); + try_void(recvStr(&conn->sock, public_info_res.data_size, &conn->name)); + + // get server description + ServerPublicInfoRequest_construct(&public_info_req, &req_header, + ServerPublicInfo_Description); + try_void(sendRequest(&conn->sock, &req_header, &public_info_req)); + try_void(recvResponse(&conn->sock, &res_header, &public_info_res, + PacketType_ServerPublicInfoResponse)); + try_void(recvStr(&conn->sock, public_info_res.data_size, &conn->description)); - ((u8*)err_buf.data)[err_msg.msg_size] = 0; - err_msg_completed = true; - Return RESULT_ERROR((char*)err_buf.data, true); - } - case PacketType_ServerHandshake: { - ServerHandshake server_handshake; - try_void(EncryptedSocketTCP_recvStruct(&conn->sock, &server_handshake)); - conn->session_id = server_handshake.session_id; - break; - } - default: - Return RESULT_ERROR_FMT("unexpected response type: %i", packet_header.type); - } - success = true; Return RESULT_VALUE(p, conn); } diff --git a/src/client/client.c b/src/client/client.c index 6326ba9..1437bad 100644 --- a/src/client/client.c +++ b/src/client/client.c @@ -157,24 +157,18 @@ static Result(void) commandExec(Client* client, str command, bool* stop){ ServerConnection_open(new_server_link.data)); printf("connection established\n"); - // TODO: request server info - // show server info - // save server info to user's db - // try log in - // if not registered, request registration and then log in + // TODO: show server info + // TODO: save server info to user's db + // TODO: ask in loop: log in / register - // call Client_runIO(): + //TODO: call Client_runIO(): // function with infinite loop which sends and receives messages // with navigation across server channels - // } else if(is_alias("c") || is_alias("connect")){ // TODO: read saved servers from database - // show scrollable list of them - // select one - // try log in - // if not registered, ask user if they want to register - // regiser and then log in + // TODO: show scrollable list of servers, get selected one + // TODO: ask in loop: log in / register } else { printf("ERROR: unknown command.\n" diff --git a/src/client/client.h b/src/client/client.h index 1fb47dd..2d934b9 100644 --- a/src/client/client.h +++ b/src/client/client.h @@ -2,6 +2,7 @@ #include "cryptography/AES.h" #include "cryptography/RSA.h" #include "network/encrypted_sockets.h" +#include "tlibc/string/str.h" typedef struct Client Client; @@ -20,16 +21,17 @@ void ClientCredentials_destroy(ClientCredentials* cred); typedef struct ServerConnection { - u64 session_id; EndpointIPv4 server_end; br_rsa_public_key server_pk; RSAEncryptor rsa_enc; + u64 session_id; Array(u8) session_key; EncryptedSocketTCP sock; + str name; + str description; } ServerConnection; Result(ServerConnection*) ServerConnection_open(cstr server_link_cstr); - void ServerConnection_close(ServerConnection* conn); diff --git a/src/client/requests/ErrorMessage.c b/src/client/requests/ErrorMessage.c new file mode 100644 index 0000000..64275ee --- /dev/null +++ b/src/client/requests/ErrorMessage.c @@ -0,0 +1,43 @@ +#include "requests.h" + +Result(void) recvStr(EncryptedSocketTCP* sock, u32 size, str* out_s){ + Deferral(4); + + str s = str_construct(malloc(size + 1), size, true); + bool success = false; + Defer(if(!success) free(s.data)); + + // receive message content + try_void( + EncryptedSocketTCP_recv( + sock, + str_castTo_Array(s), + SocketRecvFlag_WholeBuffer + ) + ); + + s.data[s.size] = 0; + *out_s = s; + success = true; + Return RESULT_VOID; +} + +Result(void) recvErrorMessage(EncryptedSocketTCP* sock, PacketHeader* res_header, + str* out_err_msg) +{ + Deferral(4); + + ErrorMessage res; + try_void(PacketHeader_validateContentSize(res_header, sizeof(res))); + try_void(EncryptedSocketTCP_recvStruct(sock, &res)); + // limit msg_size to fit in single EncryptedSocketTCP_recv call + // TODO: receive ErrorMessage content in a loop + if(res.msg_size > sock->recv_buf.size) + res.msg_size = sock->recv_buf.size; + + str err_msg; + try_void(recvStr(sock, res.msg_size, &err_msg)); + *out_err_msg = err_msg; + + Return RESULT_VOID; +} diff --git a/src/client/requests/requests.c b/src/client/requests/requests.c new file mode 100644 index 0000000..5bd5999 --- /dev/null +++ b/src/client/requests/requests.c @@ -0,0 +1,34 @@ +#include "requests.h" + + +Result(void) _recvResponse(EncryptedSocketTCP* sock, + PacketHeader* res_header, Array(u8) res, PacketType res_type) +{ + Deferral(4); + + try_void(EncryptedSocketTCP_recvStruct(sock, res_header)); + try_void(PacketHeader_validateMagic(res_header)); + if(res_header->type == PacketType_ErrorMessage){ + str err_msg; + try_void(recvErrorMessage(sock, res_header, &err_msg)); + Return RESULT_ERROR(err_msg.data, true); + } + + try_void(PacketHeader_validateType(res_header, res_type)); + try_void(PacketHeader_validateContentSize(res_header, res.size)); + + try_void(EncryptedSocketTCP_recv(sock, res, SocketRecvFlag_WholeBuffer)); + + Return RESULT_VOID; +} + +Result(void) _sendRequest(EncryptedSocketTCP* sock, + PacketHeader* req_header, Array(u8) req) +{ + Deferral(4); + + try_void(EncryptedSocketTCP_sendStruct(sock, req_header)); + try_void(EncryptedSocketTCP_send(sock, req)); + + Return RESULT_VOID; +} diff --git a/src/client/requests/requests.h b/src/client/requests/requests.h new file mode 100644 index 0000000..abd5bf4 --- /dev/null +++ b/src/client/requests/requests.h @@ -0,0 +1,20 @@ +#pragma once +#include "network/tcp-chat-protocol/v1.h" +#include "client/client.h" + + +Result(void) recvErrorMessage(EncryptedSocketTCP* sock, PacketHeader* res_header, + str* out_err_msg); + +Result(void) recvStr(EncryptedSocketTCP* sock, u32 size, str* out_s); + +Result(void) _recvResponse(EncryptedSocketTCP* sock, + PacketHeader* res_header, Array(u8) res, PacketType res_type); +#define recvResponse(sock, res_header_ptr, res_ptr, res_type) \ + _recvResponse(sock, res_header_ptr, struct_castTo_Array(res_ptr), res_type) + + +Result(void) _sendRequest(EncryptedSocketTCP* sock, + PacketHeader* req_header, Array(u8) req); +#define sendRequest(sock, req_header_ptr, req_ptr) \ + _sendRequest(sock, req_header_ptr, struct_castTo_Array(req_ptr)) diff --git a/src/db/idb.c b/src/db/idb.c index 02ac64c..f06bf8c 100644 --- a/src/db/idb.c +++ b/src/db/idb.c @@ -39,6 +39,8 @@ static const Magic32 TABLE_FILE_MAGIC = { .bytes = { 'I', 'D', 'B', 't' } }; void Table_close(Table* t){ + if(t == NULL) + return; fclose(t->table_file); fclose(t->changes_file); free(t->name.data); @@ -164,10 +166,9 @@ static Result(void) Table_validateEncryption(Table* t){ static Result(void) Table_validateRowSize(Table* t, u32 row_size){ if(row_size != t->header.row_size){ - ResultVar(void) error_result = RESULT_ERROR_FMT( + return RESULT_ERROR_FMT( "Requested row size (%u) doesn't match saved row size (%u)", row_size, t->header.row_size); - return error_result; } return RESULT_VOID; @@ -198,6 +199,8 @@ Result(IncrementalDB*) idb_open(str db_dir, NULLABLE(Array(u8) aes_key)){ } void idb_close(IncrementalDB* db){ + if(db == NULL) + return; free(db->db_dir.data); free(db->aes_key.data); HashMap_destroy(&db->tables_map); @@ -268,10 +271,9 @@ Result(Table*) idb_getOrCreateTable(IncrementalDB* db, str table_name, u32 row_s } if(!HashMap_tryPush(&db->tables_map, t->name, &t)){ - ResultVar(void) error_result = RESULT_ERROR_FMT( + Return RESULT_ERROR_FMT( "Table '%s' is already open", t->name.data); - Return error_result; } success = true; diff --git a/src/network/encrypted_sockets.c b/src/network/encrypted_sockets.c index e5b2316..cb82553 100644 --- a/src/network/encrypted_sockets.c +++ b/src/network/encrypted_sockets.c @@ -46,6 +46,7 @@ Result(void) EncryptedSocketTCP_send(EncryptedSocketTCP* ptr, ) ); + // printf("SEND data_size: %u, enc_size: %u\n", buffer.size, encrypted_size); Return RESULT_VOID; } @@ -74,6 +75,7 @@ Result(u32) EncryptedSocketTCP_recv(EncryptedSocketTCP* ptr, ) ); + // printf("RECV recv_size: %u, dec_size: %u\n", received_size, decrypted_size); Return RESULT_VALUE(u, decrypted_size); } @@ -96,6 +98,7 @@ Result(void) EncryptedSocketTCP_sendRSA(EncryptedSocketTCP* ptr, ) ); + // printf("SEND-RSA data_size: %u, enc_size: %u\n", buffer.size, encrypted_size); Return RESULT_VOID; } @@ -144,6 +147,8 @@ Result(u32) EncryptedSocketTCP_recvRSA(EncryptedSocketTCP* ptr, } memcpy(buffer.data, ptr->recv_buf.data, decrypted_size); + + // printf("RECV-RSA recv_size: %u, dec_size: %u\n", received_size, decrypted_size); Return RESULT_VALUE(u, decrypted_size); } @@ -165,7 +170,6 @@ void EncryptedSocketUDP_construct(EncryptedSocketUDP* ptr, void EncryptedSocketUDP_destroy(EncryptedSocketUDP* ptr){ if(!ptr) return; - socket_close(ptr->sock); free(ptr->recv_buf.data); free(ptr->send_buf.data); diff --git a/src/network/tcp-chat-protocol/v1.h b/src/network/tcp-chat-protocol/v1.h index b48ae0a..19d9618 100644 --- a/src/network/tcp-chat-protocol/v1.h +++ b/src/network/tcp-chat-protocol/v1.h @@ -40,7 +40,7 @@ typedef enum PacketType { typedef struct ErrorMessage { u32 msg_size; // <= ERROR_MESSAGE_MAX_SIZE /* stream of size msg_size */ -} ErrorMessage; +} ALIGN_PACKET_STRUCT ErrorMessage; void ErrorMessage_construct(ErrorMessage* ptr, PacketHeader* header, u32 msg_size); @@ -78,7 +78,7 @@ void ServerPublicInfoRequest_construct(ServerPublicInfoRequest* ptr, PacketHeade typedef struct ServerPublicInfoResponse { u32 data_size; /* stream of size data_size */ -} ServerPublicInfoResponse; +} ALIGN_PACKET_STRUCT ServerPublicInfoResponse; void ServerPublicInfoResponse_construct(ServerPublicInfoResponse* ptr, PacketHeader* header, u32 data_size); diff --git a/src/server/ClientConnection.c b/src/server/ClientConnection.c index 80e2250..4dc07a4 100644 --- a/src/server/ClientConnection.c +++ b/src/server/ClientConnection.c @@ -46,7 +46,7 @@ Result(ClientConnection*) ClientConnection_accept(ConnectionHandlerArgs* args) memcpy(conn->session_key.data, client_handshake.session_key, conn->session_key.size); EncryptedSocketTCP_changeKey(&conn->sock, conn->session_key); - // send PacketHeader and ServerHandshake over encrypted TCP socket + // send ServerHandshake ServerHandshake server_handshake; ServerHandshake_construct(&server_handshake, &packet_header, conn->session_id); diff --git a/src/server/request_handlers/ServerPublicInfo.c b/src/server/request_handlers/ServerPublicInfo.c index 4466bee..077bab4 100644 --- a/src/server/request_handlers/ServerPublicInfo.c +++ b/src/server/request_handlers/ServerPublicInfo.c @@ -23,7 +23,7 @@ declare_RequestHandler(ServerPublicInfo) content = str_castTo_Array(server->name); break; case ServerPublicInfo_Description: - content = str_castTo_Array(server->name); + content = str_castTo_Array(server->description); break; } diff --git a/src/server/server.c b/src/server/server.c index adf067f..d13208c 100644 --- a/src/server/server.c +++ b/src/server/server.c @@ -78,7 +78,7 @@ Result(Server*) Server_createFromConfig(cstr config_path){ try_void(ServerCredentials_tryConstruct(&server->cred, sk_base64_cstr, pk_base64_cstr)); - // parse db_key + // parse db_aes_key try_void(config_findValue(config_str, STR("db_aes_key"), &tmp_str, true)); Array(u8) db_aes_key = Array_alloc_size(base64_decodedSize(tmp_str.data, tmp_str.size)); base64_decode(tmp_str.data, tmp_str.size, db_aes_key.data); @@ -96,7 +96,7 @@ Result(Server*) Server_createFromConfig(cstr config_path){ // load whole table to list try_void(idb_getRows(server->db_users_table, 0, server->users_cache_list.data, users_count)); // build name-id map - for(u64 id; id < users_count; id++){ + for(u64 id = 0; id < users_count; id++){ User* u = &List_index(server->users_cache_list, User, id); str key = str_construct(u->name, u->name_len, true); if(!HashMap_tryPush(&server->users_name_id_map, key, &id)){ @@ -145,9 +145,10 @@ static void* handleConnection(void* _args){ ResultVar(void) r = try_handleConnection(args, log_ctx); if(r.error){ - str error_s = Error_toStr(r.error); - logError(log_ctx, "%s", error_s.data); - free(error_s.data); + str e_str = Error_toStr(r.error); + logError(log_ctx, "%s", e_str.data); + free(e_str.data); + Error_free(r.error); } return NULL; diff --git a/tcp-chat-server.config.default b/tcp-chat-server.config.default new file mode 100644 index 0000000..393f2b1 --- /dev/null +++ b/tcp-chat-server.config.default @@ -0,0 +1,9 @@ +name = test server +description = Lorem ipsum labuba aboba +landing_channel_id = 0 +local_address = 127.0.0.1:9988 +db_dir = server-db +db_aes_key = + +rsa_private_key = +rsa_public_key =