implemented client interactive mode

This commit is contained in:
Timerix 2025-08-28 19:11:28 +03:00
parent c008d759ae
commit f01c5fc8a9
11 changed files with 234 additions and 105 deletions

2
dependencies/tlibc vendored

@ -1 +1 @@
Subproject commit bf56984482d83d1a178f4da6483fbd350457e438 Subproject commit c415e2ca8ff51f41984ace8fe796187e6ad0fa27

View File

@ -1,4 +1,5 @@
#pragma once #pragma once
#include "tlibc/errors.h" #include "tlibc/errors.h"
Result(void) client_run();
Result(void) server_run(cstr server_endpoint_str); Result(void) server_run(cstr server_endpoint_str);

View File

@ -1,9 +1,10 @@
#include "client.h" #include "chat.h"
#include <readline/readline.h> #include <readline/readline.h>
#include <readline/history.h> #include <readline/history.h>
#include "term.h" #include "term.h"
#include "network/socket.h"
#define inp_eq(LITERAL) str_equals(input, STR(LITERAL)) #include "cryptography/cryptography.h"
#include "tlibc/string/StringBuilder.h"
static const str greeting_art = STR( static const str greeting_art = STR(
" ^,,^ |\n" " ^,,^ |\n"
@ -19,8 +20,30 @@ static const str farewell_art = STR(
"\\(_,J J L l`,)/\n" "\\(_,J J L l`,)/\n"
); );
static Result(void) commandExec(str command, bool* stop);
typedef struct ClientCredential {
str username;
u8 passhash_lvl2[password_hash_size];
EncryptorAES aes_enc;
DecryptorAES aes_dec;
EncryptorRSA rsa_enc;
DecryptorRSA rsa_dec;
} ClientCredential;
typedef struct ServerConnection {
EndpointIPv4 server_end;
Socket system_socket;
Socket content_socket;
EncryptorRSA rsa_enc;
EncryptorAES session_aes_enc;
DecryptorAES session_aes_dec;
} ServerConnection;
Result(ServerConnection*) connectToServer(EndpointIPv4 server_end, str server_key);
Result(void) client_run() { Result(void) client_run() {
Deferral(128); Deferral(32);
if(!term_init()){ if(!term_init()){
Return RESULT_ERROR("can't init terminal", false); Return RESULT_ERROR("can't init terminal", false);
} }
@ -28,74 +51,159 @@ Result(void) client_run() {
fputs(greeting_art.data, stdout); fputs(greeting_art.data, stdout);
char* input_prev = NULL; char* command_input_prev = NULL;
char* input_raw = NULL; char* command_input_raw = NULL;
Defer(rl_free(input_prev)); Defer(rl_free(command_input_prev));
str input = str_null; str command_input = str_null;
while((input_raw = readline("> "))){ bool stop = false;
rl_free(input_prev); while((command_input_raw = readline("> ")) && !stop){
input_prev = input_raw; rl_free(command_input_prev);
input = str_from_cstr(input_raw); command_input_prev = command_input_raw;
line_trim(&input, true); command_input = str_from_cstr(command_input_raw);
add_history(input.data); str_trim(&command_input, true);
if(input.size == 0){ if(command_input.size == 0)
continue; continue;
}
else if(inp_eq("q") || inp_eq("quit") || inp_eq("exit")){
fputs(farewell_art.data, stdout);
break;
}
else if(inp_eq("clear")){
term_clear();
}
else if(inp_eq("h") || inp_eq("help")){
} add_history(command_input.data);
else if(inp_eq("c") || inp_eq("connect")){ Result(void) com_result = commandExec(command_input, &stop);
if(com_result.error){
} str e_str = Error_toStr(com_result.error);
else { printfe("%s\n", e_str.data);
printf("ERROR: unknown kommand: '%s'\n" free(e_str.data);
"Use 'h' to see list of avaliable commands\n", Error_free(com_result.error);
input.data);
} }
} }
Return RESULT_VOID; Return RESULT_VOID;
} }
void line_trim(str* line, bool set_zero_at_end){ #define inp_eq(LITERAL) str_equals(command, STR(LITERAL))
bool stop = false;
// loop forward static ClientCredential* client_credential = NULL;
while(line->size > 0 && !stop){
char first_char = line->data[line->size - 1]; static Result(void) commandExec(str command, bool* stop){
switch(first_char){ Deferral(64);
case '\0': case '\r': case '\n': char answer_buf[512];
case '\t': case ' ': const u32 answer_buf_size = sizeof(answer_buf);
line->data++; if(is_alias("q") || is_alias("quit") || is_alias("exit")){
line->size--; fputs(farewell_art.data, stdout);
break; *stop = true;
default:
stop = true;
break;
}
} }
// loop backward else if(is_alias("clear")){
while(line->size > 0 && !stop) term_clear();
{
char last_char = line->data[line->size - 1];
switch(last_char){
case '\0': case '\r': case '\n':
case '\t': case ' ':
line->size--;
break;
default:
stop = true;
break;
}
} }
if(set_zero_at_end){ else if(is_alias("h") || is_alias("help")){
line->data[line->size] = '\0'; puts(
line->isZeroTerminated = true; "COMMANDS:\n"
"q, quit, exit Close the program.\n"
"clear Clear the screen.\n"
"j, join Join a server.\n"
"c, connect Connect to a server you joined.\n"
);
} }
else if (is_alias("j") || is_alias("join")){
puts("Enter server address (ip:port): ");
fgets(answer_buf, answer_buf_size, stdin);
EndpointIPv4 new_server_end;
try_void(EndpointIPv4_parse(answer_buf, &new_server_end));
puts("Enter server key (): ");
fgets(answer_buf, answer_buf_size, stdin);
str new_server_key = str_from_cstr(answer_buf);
try(ServerConnection* conn, p, connectToServer(new_server_end, new_server_key));
}
else if(is_alias("c") || is_alias("connect")){
// TODO: read saved servers from database
}
else {
Return RESULT_ERROR_FMT("unknown kommand: '%s'\n"
"Use 'h' to see list of avaliable commands\n",
command.data);
}
Return RESULT_VOID;
}
void ClientCredential_free(ClientCredential* cred){
if(cred == NULL)
return;
free(cred->username.data);
free(cred);
}
#define __passhash_lvl_iter 1e5
static Result(ClientCredential*) ClientCredential_create(str username, str password){
Deferral(32);
ClientCredential* cred = (ClientCredential*)malloc(sizeof(ClientCredential));
memset(cred, 0, sizeof(ClientCredential));
bool success = false;
Defer(
if(!success)
ClientCredential_free(cred);
);
cred->username = str_copy(username);
// concat password and username
StringBuilder sb = StringBuilder_alloc(username.size + password.size + 1);
Defer(StringBuilder_destroy(&sb));
StringBuilder_append_str(&sb, password);
StringBuilder_append_str(&sb, username);
// lvl 1 hash - is being used to generate rsa keys
u8 passhash_lvl1[password_hash_size];
hash_password(StringBuilder_getStr(&sb), passhash_lvl1, __passhash_lvl_iter);
// lvl 2 hash - is being used to authorize client on server
str _passhash_lvl1_str = str_construct(passhash_lvl1, password_hash_size, false);
hash_password(_passhash_lvl1_str, cred->passhash_lvl2, __passhash_lvl_iter);
success = true;
return RESULT_VALUE(p, cred);
}
static void ServerConnection_close(ServerConnection* conn){
if(conn == NULL)
return;
socket_close(conn->system_socket);
socket_close(conn->content_socket);
free(conn);
}
static Result(ServerConnection*) connectToServer(EndpointIPv4 server_end, str server_key){
Deferral(64);
if(client_credential == NULL){
}
str end_str = EndpointIPv4_toStr(server_end);
Defer(free(end_str.data));
if(EndpointIPv4_is_invalid(server_end)){
Return RESULT_ERROR_FMT("endpoint is invalid: %s", end_str.data);
}
ServerConnection* conn = (ServerConnection*)malloc(sizeof(ServerConnection));
memset(conn, 0, sizeof(ServerConnection));
bool success = false;
Defer(
if(!success)
ServerConnection_close(conn);
);
printf("connecting to server %s\n", end_str.data);
try(conn->system_socket, i, socket_open_TCP());
try_void(socket_connect(conn->system_socket, server_end));
// ask user name and password
// calculate key pair from password hash
// send client public key to server
// request server info
// show server info
// save server info to user's db
// hash password more times
// request log in
// if not registered, request registration and then log in
success = true;
Return RESULT_VALUE(p, conn);
} }

View File

@ -1,7 +0,0 @@
#pragma once
#include "tlibc/errors.h"
Result(void) client_run();
/// @brief removes blank characters from start and end of the line
void line_trim(str* line, bool set_zero_at_end);

View File

@ -4,13 +4,14 @@
#include "tlibc/string/str.h" #include "tlibc/string/str.h"
#include "bearssl_block.h" #include "bearssl_block.h"
#include "bearssl_rand.h" #include "bearssl_rand.h"
#include "bearssl_rsa.h"
/// @brief hashes password multiple times using its own hash as salt /// @brief hashes password multiple times using its own hash as salt
/// @param password some byte array /// @param password some byte array
/// @param out_buffer u8[hash_password_out_size] /// @param out_buffer u8[password_hash_size]
/// @param iterations number of iterations /// @param iterations number of iterations
void hash_password(str password, u8* out_buffer, i32 iterations); void hash_password(str password, u8* out_buffer, i32 iterations);
#define hash_password_out_size 32 #define password_hash_size 32
typedef struct EncryptedBlockInfo { typedef struct EncryptedBlockInfo {
@ -59,7 +60,11 @@ void DecryptorAES_decrypt(DecryptorAES* ptr, Array(u8) src, Array(u8) dst, u32*
typedef struct EncryptorRSA EncryptorRSA; typedef struct EncryptorRSA {
br_rsa_public_key public_key;
} EncryptorRSA;
typedef struct DecryptorRSA DecryptorRSA; typedef struct DecryptorRSA {
br_rsa_private_key private_key;
} DecryptorRSA;

View File

@ -3,7 +3,7 @@
#include "assert.h" #include "assert.h"
void hash_password(str password, u8* out_buffer, i32 iterations){ void hash_password(str password, u8* out_buffer, i32 iterations){
assert(hash_password_out_size == br_sha256_SIZE);; assert(password_hash_size == br_sha256_SIZE);;
memset(out_buffer, 0, br_sha256_SIZE); memset(out_buffer, 0, br_sha256_SIZE);
br_sha256_context sha256_ctx; br_sha256_context sha256_ctx;
br_sha256_init(&sha256_ctx); br_sha256_init(&sha256_ctx);
@ -11,7 +11,7 @@ void hash_password(str password, u8* out_buffer, i32 iterations){
for(i32 i = 0; i < iterations; i++){ for(i32 i = 0; i < iterations; i++){
br_sha256_update(&sha256_ctx, password.data, password.size); br_sha256_update(&sha256_ctx, password.data, password.size);
br_sha256_out(&sha256_ctx, out_buffer); br_sha256_out(&sha256_ctx, out_buffer);
br_sha256_update(&sha256_ctx, out_buffer, hash_password_out_size); br_sha256_update(&sha256_ctx, out_buffer, password_hash_size);
} }
br_sha256_out(&sha256_ctx, out_buffer); br_sha256_out(&sha256_ctx, out_buffer);
} }

View File

@ -1,6 +1,5 @@
#include "network/network.h" #include "network/network.h"
#include "client.h" #include "chat.h"
#include "server.h"
typedef enum ProgramMode { typedef enum ProgramMode {
Client, Client,
@ -20,9 +19,9 @@ int main(const int argc, cstr const* argv){
if(arg_is("-h") || arg_is("--help")){ if(arg_is("-h") || arg_is("--help")){
printf( printf(
"USAGE:\n" "USAGE:\n"
"no arguments Interactive client mode.\n" "no arguments Interactive client mode.\n"
"-h, --help Show this message.\n" "-h, --help Show this message.\n"
"-l, --listen [addr:port] Start server.\n" "-l, --listen [addr:port] Start server.\n"
); );
Return 0; Return 0;
} }

View File

@ -18,20 +18,42 @@ EndpointIPv4 EndpointIPv4_fromSockaddr(struct sockaddr_in saddr){
return end; return end;
} }
//TODO Endpoint functions Result(void) AddressIPv4_parse(cstr s, AddressIPv4* addr){
AddressIPv4 AddressIPv4_fromStr(cstr s); *addr = AddressIPv4_INVALID;
u32 a, b, c, d;
str AddressIPv4_toStr(AddressIPv4 address); if(sscanf(s, "%u.%u.%u.%u", &a, &b, &c, &d) != 4){
return RESULT_ERROR_FMT("can't parse as AddressIPv4: '%s'", s);
EndpointIPv4 EndpointIPv4_fromStr(cstr s){ }
u32 a, b, c, d, p; *addr = AddressIPv4_fromBytes(a, b, c, d);
sscanf(s, "%u.%u.%u.%u:%u", &a, &b, &c, &d, &p); return RESULT_VOID;
EndpointIPv4 e = (EndpointIPv4){
.address = AddressIPv4_fromBytes(a, b, c, d),
.port = p
};
return e;
} }
str EndpointIPv4_toStr(EndpointIPv4 end);
Result(void) EndpointIPv4_parse(cstr s, EndpointIPv4* end){
*end = EndpointIPv4_INVALID;
u32 a, b, c, d, p;
if(sscanf(s, "%u.%u.%u.%u:%u", &a, &b, &c, &d, &p) != 5){
return RESULT_ERROR_FMT("can't parse as EndpointIPv4: '%s'", s);
}
*end = EndpointIPv4_create(AddressIPv4_fromBytes(a, b, c, d), p);
return RESULT_VOID;
}
str AddressIPv4_toStr(AddressIPv4 addr){
char* data = malloc(16);
memset(data, 0, 16);
sprintf(data, "%u.%u.%u.%u",
addr.bytes[0], addr.bytes[1],
addr.bytes[2], addr.bytes[3]);
return str_from_cstr(data);
}
str EndpointIPv4_toStr(EndpointIPv4 end){
char* data = malloc(24);
memset(data, 0, 24);
sprintf(data, "%u.%u.%u.%u:%u",
end.address.bytes[0], end.address.bytes[1],
end.address.bytes[2], end.address.bytes[3],
end.port);
return str_from_cstr(data);
}

View File

@ -2,7 +2,7 @@
#include "tlibc/std.h" #include "tlibc/std.h"
#include "tlibc/string/str.h" #include "tlibc/string/str.h"
#define port_INVALID ((port)~0) #define port_INVALID ((u16)~0)
#define port_is_invalid(PORT) (PORT == port_INVALID) #define port_is_invalid(PORT) (PORT == port_INVALID)
@ -18,8 +18,8 @@ typedef union AddressIPv4 {
#define AddressIPv4_fromBytes(A, B, C, D) ((AddressIPv4){ .bytes = {A,B,C,D} }) #define AddressIPv4_fromBytes(A, B, C, D) ((AddressIPv4){ .bytes = {A,B,C,D} })
#define AddressIPv4_fromU32(N) ((AddressIPv4){ .UintBigEndian = N }) #define AddressIPv4_fromU32(N) ((AddressIPv4){ .UintBigEndian = N })
AddressIPv4 AddressIPv4_fromStr(cstr s); Result(void) AddressIPv4_parse(cstr s, AddressIPv4* addr);
str AddressIPv4_toStr(AddressIPv4 address); str AddressIPv4_toStr(AddressIPv4 addr);
typedef struct EndpointIPv4 { typedef struct EndpointIPv4 {
@ -31,8 +31,5 @@ typedef struct EndpointIPv4 {
#define EndpointIPv4_is_invalid(ENDP) (AddressIPv4_is_invalid(ENDP.address) || port_is_invalid(ENDP.port)) #define EndpointIPv4_is_invalid(ENDP) (AddressIPv4_is_invalid(ENDP.address) || port_is_invalid(ENDP.port))
#define EndpointIPv4_create(ADDR, PORT) ((EndpointIPv4){ADDR, PORT}) #define EndpointIPv4_create(ADDR, PORT) ((EndpointIPv4){ADDR, PORT})
EndpointIPv4 EndpointIPv4_fromStr(cstr s); Result(void) EndpointIPv4_parse(cstr s, EndpointIPv4* end);
str EndpointIPv4_toStr(EndpointIPv4 end); str EndpointIPv4_toStr(EndpointIPv4 end);
struct sockaddr_in EndpointIPv4_toSockaddr(EndpointIPv4 end);
EndpointIPv4 EndpointIPv4_fromSockaddr(struct sockaddr_in saddr);

View File

@ -1,5 +1,6 @@
#pragma once #pragma once
#include "tlibc/errors.h" #include "tlibc/errors.h"
#include "endpoint.h"
#if !defined(KN_USE_WINSOCK) #if !defined(KN_USE_WINSOCK)
#if defined(_WIN64) || defined(_WIN32) #if defined(_WIN64) || defined(_WIN32)
@ -24,3 +25,6 @@
#define RESULT_ERROR_SOCKET() RESULT_ERROR(strerror(errno), false) #define RESULT_ERROR_SOCKET() RESULT_ERROR(strerror(errno), false)
#endif #endif
struct sockaddr_in EndpointIPv4_toSockaddr(EndpointIPv4 end);
EndpointIPv4 EndpointIPv4_fromSockaddr(struct sockaddr_in saddr);

View File

@ -1,4 +1,4 @@
#include "server.h" #include "chat.h"
#include "network/socket.h" #include "network/socket.h"
#include "db/idb.h" #include "db/idb.h"
#include <pthread.h> #include <pthread.h>