Compare commits

..

No commits in common. "85c0736c8d138a2c092206076dff987a73821a40" and "9942d94c94fc538dcd53fae226dd4a5c0073ef99" have entirely different histories.

17 changed files with 74 additions and 201 deletions

View File

@ -1,29 +0,0 @@
# 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
```

2
dependencies/tlibc vendored

@ -1 +1 @@
Subproject commit 425794361bc52240bb50001becbb5fb6e9ebcf20
Subproject commit ae0fa95d6aee2a7427c3e3575d74af1ed360e6e5

View File

@ -1,5 +1,5 @@
#include "client.h"
#include "requests/requests.h"
#include "network/tcp-chat-protocol/v1.h"
void ServerConnection_close(ServerConnection* conn){
if(!conn)
@ -7,8 +7,6 @@ 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);
}
@ -66,39 +64,53 @@ 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 ClientHandshake using server public key for encryption
PacketHeader req_header;
// send PacketHeader and ClientHandshake
// encryption by server public key
PacketHeader packet_header;
ClientHandshake client_handshake;
try_void(ClientHandshake_tryConstruct(&client_handshake, &req_header,
try_void(ClientHandshake_tryConstruct(&client_handshake, &packet_header,
conn->session_key));
try_void(EncryptedSocketTCP_sendStructRSA(&conn->sock, &conn->rsa_enc, &req_header));
try_void(EncryptedSocketTCP_sendStructRSA(&conn->sock, &conn->rsa_enc, &packet_header));
try_void(EncryptedSocketTCP_sendStructRSA(&conn->sock, &conn->rsa_enc, &client_handshake));
// receive server response
PacketHeader res_header;
ServerHandshake server_handshake;
try_void(recvResponse(&conn->sock, &res_header, &server_handshake,
PacketType_ServerHandshake));
conn->session_id = server_handshake.session_id;
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));
// 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));
// receive message content
try_void(
EncryptedSocketTCP_recv(
&conn->sock,
Array_sliceTo(err_buf, err_msg.msg_size),
SocketRecvFlag_WholeBuffer
)
);
((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);
}

View File

@ -157,18 +157,24 @@ static Result(void) commandExec(Client* client, str command, bool* stop){
ServerConnection_open(new_server_link.data));
printf("connection established\n");
// TODO: show server info
// TODO: save server info to user's db
// TODO: ask in loop: log in / register
// 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: call Client_runIO():
// 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
// TODO: show scrollable list of servers, get selected one
// TODO: ask in loop: log in / register
// 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
}
else {
printf("ERROR: unknown command.\n"

View File

@ -2,7 +2,6 @@
#include "cryptography/AES.h"
#include "cryptography/RSA.h"
#include "network/encrypted_sockets.h"
#include "tlibc/string/str.h"
typedef struct Client Client;
@ -21,17 +20,16 @@ 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);

View File

@ -1,43 +0,0 @@
#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;
}

View File

@ -1,34 +0,0 @@
#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;
}

View File

@ -1,20 +0,0 @@
#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))

View File

@ -172,14 +172,13 @@ Result(u32) AESStreamEncryptor_encrypt(AESStreamEncryptor* ptr,
Array(u8) src, Array(u8) dst)
{
Deferral(4);
u32 encrypted_size = src.size;
u32 encrypted_size = AESStreamEncryptor_calcDstSize(src.size);
try_assert(dst.size >= encrypted_size);
// if it is the beginning of the stream, write IV
if(ptr->block_counter == 0){
__Array_writeNext(&dst, ptr->iv, __AES_STREAM_IV_SIZE);
encrypted_size = AESStreamEncryptor_calcDstSize(encrypted_size);
}
try_assert(dst.size >= encrypted_size);
// encrypt full buffers
while(src.size > __AES_BUFFER_SIZE){

View File

@ -101,7 +101,7 @@ void AESStreamEncryptor_changeKey(AESStreamEncryptor* ptr, Array(u8) key);
/// @brief If ptr->block_counter == 0, writes random IV to `dst`. After that writes encrypted data to dst.
/// @param src array of any size
/// @param dst array of size >= `AESStreamEncryptor_calcDstSize(src.size)` for first block and `src.size `for other blocks
/// @param dst array of size >= AESStreamEncryptor_calcDstSize(src.size)
/// @return size of encrypted data
Result(u32) AESStreamEncryptor_encrypt(AESStreamEncryptor* ptr, Array(u8) src, Array(u8) dst);

View File

@ -39,8 +39,6 @@ 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);
@ -166,9 +164,10 @@ static Result(void) Table_validateEncryption(Table* t){
static Result(void) Table_validateRowSize(Table* t, u32 row_size){
if(row_size != t->header.row_size){
return RESULT_ERROR_FMT(
ResultVar(void) error_result = 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;
@ -199,8 +198,6 @@ 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);
@ -271,9 +268,10 @@ Result(Table*) idb_getOrCreateTable(IncrementalDB* db, str table_name, u32 row_s
}
if(!HashMap_tryPush(&db->tables_map, t->name, &t)){
Return RESULT_ERROR_FMT(
ResultVar(void) error_result = RESULT_ERROR_FMT(
"Table '%s' is already open",
t->name.data);
Return error_result;
}
success = true;

View File

@ -46,7 +46,6 @@ Result(void) EncryptedSocketTCP_send(EncryptedSocketTCP* ptr,
)
);
// printf("SEND data_size: %u, enc_size: %u\n", buffer.size, encrypted_size);
Return RESULT_VOID;
}
@ -75,7 +74,6 @@ 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);
}
@ -98,7 +96,6 @@ Result(void) EncryptedSocketTCP_sendRSA(EncryptedSocketTCP* ptr,
)
);
// printf("SEND-RSA data_size: %u, enc_size: %u\n", buffer.size, encrypted_size);
Return RESULT_VOID;
}
@ -147,8 +144,6 @@ 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);
}
@ -170,6 +165,7 @@ 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);

View File

@ -40,7 +40,7 @@ typedef enum PacketType {
typedef struct ErrorMessage {
u32 msg_size; // <= ERROR_MESSAGE_MAX_SIZE
/* stream of size msg_size */
} ALIGN_PACKET_STRUCT ErrorMessage;
} 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 */
} ALIGN_PACKET_STRUCT ServerPublicInfoResponse;
} ServerPublicInfoResponse;
void ServerPublicInfoResponse_construct(ServerPublicInfoResponse* ptr, PacketHeader* header,
u32 data_size);

View File

@ -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 ServerHandshake
// send PacketHeader and ServerHandshake over encrypted TCP socket
ServerHandshake server_handshake;
ServerHandshake_construct(&server_handshake, &packet_header,
conn->session_id);

View File

@ -23,7 +23,7 @@ declare_RequestHandler(ServerPublicInfo)
content = str_castTo_Array(server->name);
break;
case ServerPublicInfo_Description:
content = str_castTo_Array(server->description);
content = str_castTo_Array(server->name);
break;
}

View File

@ -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_aes_key
// parse db_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 = 0; id < users_count; id++){
for(u64 id; 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,10 +145,9 @@ static void* handleConnection(void* _args){
ResultVar(void) r = try_handleConnection(args, log_ctx);
if(r.error){
str e_str = Error_toStr(r.error);
logError(log_ctx, "%s", e_str.data);
free(e_str.data);
Error_free(r.error);
str error_s = Error_toStr(r.error);
logError(log_ctx, "%s", error_s.data);
free(error_s.data);
}
return NULL;

View File

@ -1,9 +0,0 @@
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 = <generate with './tcp-chat --random-bytes-base64 32'>
rsa_private_key = <generate with './tcp-chat --rsa-gen-random'>
rsa_public_key =