From fa729a8a454194f60a8fa2e760994f414ec2b875 Mon Sep 17 00:00:00 2001 From: night0721 Date: Wed, 18 Sep 2024 08:37:02 +0100 Subject: [PATCH] UI drawing and input, and move encryption to ui.c --- include/client/ui.h | 14 +++ include/client/user.h | 6 +- src/client/client.c | 102 ++++++++++++------- src/client/ui.c | 223 ++++++++++++++++++++++++++++++++++-------- src/client/user.c | 6 +- 5 files changed, 270 insertions(+), 81 deletions(-) diff --git a/include/client/ui.h b/include/client/ui.h index 37f37e8..7bcf036 100644 --- a/include/client/ui.h +++ b/include/client/ui.h @@ -3,9 +3,23 @@ #include +#define USERS_WINDOW 0 +#define CHAT_WINDOW 1 + +/* Keybindings */ +#define CTRLA 0x01 +#define CTRLD 0x04 +#define CTRLE 0x05 +#define DOWN 0x102 +#define UP 0x103 +#define LEFT 0x104 +#define RIGHT 0x105 + void ncurses_init(); void windows_init(); void draw_border(WINDOW *window, bool active); +void add_message(uint8_t *author, uint8_t *recipient, uint8_t *content, uint32_t length, time_t creation); +void show_chat(uint8_t *recipient); void add_username(char *username); void ui(); diff --git a/include/client/user.h b/include/client/user.h index 967baa4..f1d71bb 100644 --- a/include/client/user.h +++ b/include/client/user.h @@ -6,7 +6,7 @@ #include typedef struct user { - char *name; + uint8_t *name; wchar_t *icon; int color; } user; @@ -19,9 +19,9 @@ typedef struct ArrayList { ArrayList *arraylist_init(size_t capacity); void arraylist_free(ArrayList *list); -long arraylist_search(ArrayList *list, char *username); +long arraylist_search(ArrayList *list, uint8_t *username); void arraylist_remove(ArrayList *list, long index); -void arraylist_add(ArrayList *list, char *name, wchar_t *icon, int color, bool marked, bool force); +void arraylist_add(ArrayList *list, uint8_t *username, wchar_t *icon, int color, bool marked, bool force); char *get_line(ArrayList *list, long index, bool icons); #endif diff --git a/src/client/client.c b/src/client/client.c index 25b6037..ea5a12b 100644 --- a/src/client/client.c +++ b/src/client/client.c @@ -1,9 +1,11 @@ #include "config.h" #include "packet.h" #include "key.h" +#include "notification.h" #include "util.h" #include "client/ui.h" #include "client/db.h" +#include "server/server.h" int sockfd; @@ -12,7 +14,7 @@ int sockfd; */ int authenticate_server(key_pair *kp) { - packet server_auth_pkt; + packet_t server_auth_pkt; int status; if ((status = recv_packet(&server_auth_pkt, sockfd, ZSM_TYP_AUTH) != ZSM_STA_SUCCESS)) { return status; @@ -28,7 +30,7 @@ int authenticate_server(key_pair *kp) memcpy(pk_content + PK_BIN_SIZE + MAX_NAME, &kp->pk.creation, TIME_SIZE); memcpy(pk_content + PK_BIN_SIZE + METADATA_SIZE, kp->pk.signature, SIGN_SIZE); - packet *auth_pkt = create_packet(1, ZSM_TYP_AUTH, SIGN_SIZE, pk_content, sig); + packet_t *auth_pkt = create_packet(1, ZSM_TYP_AUTH, SIGN_SIZE, pk_content, sig); if (send_packet(auth_pkt, sockfd) != ZSM_STA_SUCCESS) { /* fd already closed */ error(0, "Could not authenticate with server"); @@ -38,43 +40,74 @@ int authenticate_server(key_pair *kp) } free_packet(auth_pkt); - packet response; + packet_t response; status = recv_packet(&response, sockfd, ZSM_TYP_INFO); return (response.status == ZSM_STA_AUTHORISED ? ZSM_STA_SUCCESS : ZSM_STA_ERROR_AUTHENTICATE); } /* - * For sending packets to server + * Starting ui */ -void *send_message(void *arg) +void *ui_worker(void *arg) { - key_pair *kp = (key_pair *) arg; - - while (1) { - int status = encrypt_packet(sockfd, kp); - if (status != ZSM_STA_SUCCESS) { - error(1, "Error encrypting packet %x", status); - } - } - + ui(sockfd); return NULL; } /* * For receiving packets from server */ -void *receive_message(void *arg) +void *receive_worker(void *arg) { key_pair *kp = (key_pair *) arg; while (1) { - packet pkt; - + packet_t pkt; if (verify_packet(&pkt, sockfd) == 0) { error(0, "Error verifying packet"); } - uint8_t *decrypted = decrypt_data(&pkt); - free(decrypted); + size_t cipher_len = pkt.length - NONCE_SIZE - MAX_NAME * 2; + size_t data_len = cipher_len - ADDITIONAL_SIZE; + + uint8_t nonce[NONCE_SIZE], encrypted[cipher_len]; + + uint8_t *from = memalloc(MAX_NAME); + uint8_t *to = memalloc(MAX_NAME); + uint8_t *decrypted = memalloc(data_len + 1); + + /* Deconstruct data */ + memcpy(from, pkt.data, MAX_NAME); + memcpy(to, pkt.data + MAX_NAME, MAX_NAME); + memcpy(nonce, pkt.data + MAX_NAME * 2, NONCE_SIZE); + memcpy(encrypted, pkt.data + MAX_NAME * 2 + NONCE_SIZE, cipher_len); + + key_pair *kp_from = get_key_pair(from); + key_pair *kp_to = get_key_pair(to); + + uint8_t shared_key[SHARED_SIZE]; + if (crypto_kx_client_session_keys(shared_key, NULL, kp_from->pk.bin, + kp_from->sk.bin, kp_to->pk.bin) != 0) { + /* Suspicious server public key, bail out */ + error(0, "Error performing key exchange"); + } + + /* We don't need it anymore */ + free(pkt.data); + if (crypto_aead_xchacha20poly1305_ietf_decrypt(decrypted, NULL, + NULL, encrypted, + cipher_len, + NULL, 0, + nonce, shared_key) != 0) { + free(decrypted); + error(0, "Cannot decrypt data"); + return NULL; + } else { + /* Terminate decrypted data so we don't print random bytes */ + decrypted[data_len] = '\0'; + add_message(from, to, decrypted, data_len, time(NULL)); + show_chat(from); + send_notification(from, decrypted); + } } return NULL; @@ -85,7 +118,11 @@ int main() if (sodium_init() < 0) { write_log(LOG_ERROR, "Error initializing libsodium\n"); } - //ui(); + + /* Init libnotify with app name */ + if (notify_init("zen") < 0) { + error(1, "Error initializing libnotify"); + } sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd < 0) { @@ -106,47 +143,42 @@ int main() /* free(server); Can't be freed seems */ if (connect(sockfd, (struct sockaddr *) &server_addr, sizeof(server_addr) ) < 0) { - if (errno != EINPROGRESS) { - /* Connection is in progress, shouldn't be treated as error */ - error(1, "Error on connect"); - close(sockfd); - return 0; - } + error(1, "Error on connect"); + close(sockfd); + return 0; } write_log(LOG_INFO, "Connected to server at %s\n", DOMAIN); -/* set_nonblocking(sockfd); */ /* key_pair *kpp = create_key_pair("palanix"); key_pair *kpn = create_key_pair("night"); */ - key_pair *kpp = get_key_pair("palanix"); - key_pair *kpn = get_key_pair("night"); + key_pair *kp = get_key_pair(USERNAME); - if (authenticate_server(kpp) != ZSM_STA_SUCCESS) { + if (authenticate_server(kp) != ZSM_STA_SUCCESS) { /* Fatal */ error(1, "Error authenticating with server"); } else { write_log(LOG_INFO, "Authenticated with server\n"); - printf("Authenticated as palanix\n"); + printf("Authenticated as %s\n", USERNAME); } /* Create threads for sending and receiving messages */ - pthread_t send_thread, receive_thread; + pthread_t ui_thread, receive_thread; - if (pthread_create(&send_thread, NULL, send_message, kpp) != 0) { + if (pthread_create(&ui_thread, NULL, ui_worker, NULL) != 0) { close(sockfd); error(1, "Failed to create send thread"); exit(EXIT_FAILURE); } - if (pthread_create(&receive_thread, NULL, receive_message, kpp) != 0) { + if (pthread_create(&receive_thread, NULL, receive_worker, kp) != 0) { close(sockfd); error(1, "Failed to create receive thread"); } /* Wait for threads to finish */ - pthread_join(send_thread, NULL); + pthread_join(ui_thread, NULL); pthread_join(receive_thread, NULL); close(sockfd); diff --git a/src/client/ui.c b/src/client/ui.c index 08c76dd..d4d6087 100644 --- a/src/client/ui.c +++ b/src/client/ui.c @@ -5,27 +5,41 @@ #include "client/db.h" #include "client/user.h" -typedef struct windows { - WINDOW *users_border; - WINDOW *users_content; - WINDOW *chat_border; - WINDOW *chat_content; -} windows; - WINDOW *panel; WINDOW *users_border; WINDOW *chat_border; - WINDOW *users_content; +WINDOW *textbox; WINDOW *chat_content; ArrayList *users; ArrayList *marked; +message_t messages[100]; +int num_messages = 0; long current_selection = 0; - +int current_window = 0; +int sockfd; bool show_icons; +static char content[MAX_MESSAGE_LENGTH]; -void show_chat(); +void send_message(); + +void signal_handler(int signal) +{ + switch (signal) { + case SIGPIPE: + error(0, "SIGPIPE received"); + break; + case SIGABRT: + case SIGINT: + case SIGTERM: + shutdown(sockfd, SHUT_WR); + endwin(); + close(sockfd); + error(1, "Shutdown signal received"); + break; + } +} void ncurses_init() { @@ -70,13 +84,13 @@ void windows_init() int chat_width = COLS - 32; /*------------------------------+ - |----border(0)--||--border(2)--|| + |-----border----||---border----|| || || || - || content (1) || content (3) || + || content || content || || (users) || (chat) || || || || - |---------------||-------------|| - +==========panel (4)===========*/ + |---------------||-textbox-----|| + +==========panel===============*/ /* lines, cols, y, x */ panel = newwin(PANEL_HEIGHT, COLS, LINES - PANEL_HEIGHT, 0 ); @@ -85,6 +99,7 @@ void windows_init() chat_border = newwin(LINES - PANEL_HEIGHT, chat_width - 2, 0, users_width + 2); users_content = newwin(LINES - PANEL_HEIGHT - 2, users_width, 1, 1 ); + textbox = newwin(1, users_width, LINES - PANEL_HEIGHT - 2, users_width + 3); chat_content = newwin(LINES - PANEL_HEIGHT - 2, chat_width - 4, 1, users_width + 3); refresh(); @@ -231,23 +246,30 @@ void highlight_current_line() wrefresh(users_content); wrefresh(panel); /* show chat conversation every time cursor changes */ - #if DRAW_PREVIEW - show_chat(); - #endif + show_chat(users->items[current_selection].name); #if DRAW_BORDERS draw_border_title(preview_border, true); #endif wrefresh(chat_content); } +void add_message(uint8_t *author, uint8_t *recipient, uint8_t *content, uint32_t length, time_t creation) +{ + message_t *msg = &messages[num_messages]; + strcpy(msg->author, author); + strcpy(msg->recipient, recipient); + msg->content = memalloc(length); + strcpy(msg->content, content); + msg->creation = creation; + num_messages++; +} /* * Add message to chat window * user_color is the color defined above at ncurses_init */ - -void add_message(time_t rawtime, char *username, int user_color, char *content) +void print_message(message_t *msg, int user_color) { - struct tm *timeinfo = localtime(&rawtime); + struct tm *timeinfo = localtime(&msg->creation); char buffer[21]; strftime(buffer, sizeof(buffer), "%b %d %Y %H:%M:%S", timeinfo); @@ -255,25 +277,37 @@ void add_message(time_t rawtime, char *username, int user_color, char *content) wattron(chat_content, A_BOLD); wattron(chat_content, COLOR_PAIR(user_color)); - wprintw(chat_content, "<%s> ", username); + wprintw(chat_content, "<%s> ", msg->author); wattroff(chat_content, A_BOLD); wattroff(chat_content, COLOR_PAIR(user_color)); - wprintw(chat_content, "%s", content); + wprintw(chat_content, "%s", msg->content); } /* * Get chat conversation into buffer and show it to chat window */ -void show_chat() +void show_chat(uint8_t *recipient) { - add_message(1725932011, "night", 1, "I go to school by bus.\n"); - add_message(1725933011, "night", 2, "I go to school by tram.\n"); - add_message(1725934011, "night", 3, "I go to school by train.\n"); - add_message(1725935011, "night", 4, "I go to school by car.\n"); - add_message(1725936011, "night", 5, "I go to school by run.\n"); - add_message(1725937011, "night", 6, "I go to school by bike.\n"); - add_message(1725938011, "night", 7, "I go to school by plane.\n"); + wclear(chat_content); + for (int i = 0; i < 100; i++) { + message_t message = messages[i]; + if (message.content == NULL) continue; + /* Find messages from recipient to client or vice versa */ + /* outgoing = 1, incoming = 2 */ + if (strncmp(message.author, USERNAME, MAX_NAME) == 0 && + strncmp(message.recipient, recipient, MAX_NAME) == 0) { + print_message(&message, 1); + continue; + } + + if (strncmp(message.author, recipient, MAX_NAME) == 0 && + strncmp(message.recipient, USERNAME, MAX_NAME) == 0) { + print_message(&message, 2); + continue; + } + } + wrefresh(chat_content); } /* * Require heap allocated username @@ -286,11 +320,95 @@ void add_username(char *username) arraylist_add(users, username, icon_str, 7, false, false); } +void get_chatbox_content(int ch) +{ + /* For tracking position in content */ + static int pos = 0; + + if (ch == KEY_BACKSPACE || ch == 127) { + if (pos > 0) { + pos--; + content[pos] = '\0'; + } + } + /* Input done */ + else if (ch == '\n') { + content[pos++] = ch; + content[pos++] = '\0'; + send_message(); + /* Reset for new input */ + pos = 0; + } + /* Append it to the content if it is normal character */ + else if (pos < MAX_MESSAGE_LENGTH - 1) { + content[pos++] = ch; + content[pos] = '\0'; + } + + /* Display the current content */ + mvwprintw(textbox, 0, 0, "%s", content); + wrefresh(textbox); +} + +void send_message() +{ + uint8_t *recipient = users->items[current_selection].name; + + key_pair *kp_from = get_key_pair(USERNAME); + key_pair *kp_to = get_key_pair(recipient); + + int status = ZSM_STA_SUCCESS; + + uint8_t shared_key[SHARED_SIZE]; + if (crypto_kx_client_session_keys(shared_key, NULL, kp_from->pk.bin, + kp_from->sk.bin, kp_to->pk.bin) != 0) { + /* Recipient public key is suspicious */ + error(0, "Error performing key exchange"); + } + + size_t content_len = strlen(content); + + uint32_t cipher_len = content_len + ADDITIONAL_SIZE; + uint8_t nonce[NONCE_SIZE], encrypted[cipher_len]; + + /* Generate random nonce(number used once) */ + randombytes_buf(nonce, sizeof(nonce)); + + /* Encrypt the content and store it to encrypted, should be cipher_len */ + crypto_aead_xchacha20poly1305_ietf_encrypt(encrypted, NULL, content, + content_len, NULL, 0, NULL, nonce, shared_key); + + size_t data_len = MAX_NAME * 2 + NONCE_SIZE + cipher_len; + uint8_t *data = memalloc(data_len); + + /* Construct data */ + memcpy(data, kp_from->sk.username, MAX_NAME); + memcpy(data + MAX_NAME, kp_to->sk.username, MAX_NAME); + memcpy(data + MAX_NAME * 2, nonce, NONCE_SIZE); + memcpy(data + MAX_NAME * 2 + NONCE_SIZE, encrypted, cipher_len); + + uint8_t *signature = create_signature(data, data_len, &kp_from->sk); + packet_t *pkt = create_packet(1, ZSM_TYP_MESSAGE, data_len, data, signature); + + if (send_packet(pkt, sockfd) != ZSM_STA_SUCCESS) { + close(sockfd); + write_log(LOG_ERROR, "Failed to send message"); + } + add_message(USERNAME, recipient, content, content_len, time(NULL)); + free_packet(pkt); + highlight_current_line(); +} + /* * Main loop of user interface */ -void ui() +void ui(int fd) { + signal(SIGPIPE, signal_handler); + signal(SIGABRT, signal_handler); + signal(SIGINT, signal_handler); + signal(SIGTERM, signal_handler); + sockfd = fd; ncurses_init(); windows_init(); users = arraylist_init(LINES); @@ -301,32 +419,57 @@ void ui() refresh(); int ch; while (1) { + /* if (COLS < 80 || LINES < 24) { endwin(); error(1, "Terminal size needs to be at least 80x24"); } + */ ch = getch(); switch (ch) { - case 'q': + case CTRLD: goto cleanup; /* go up by k or up arrow */ case UP: - case 'k': - if (current_selection > 0) - current_selection--; + if (current_window == USERS_WINDOW) { + if (current_selection > 0) + current_selection--; - highlight_current_line(); + highlight_current_line(); + } break; /* go down by j or down arrow */ case DOWN: - case 'j': - if (current_selection < (users->length - 1)) - current_selection++; + if (current_window == USERS_WINDOW) { + if (current_selection < (users->length - 1)) + current_selection++; - highlight_current_line(); + highlight_current_line(); + } break; + + /* A is normally for left and E for right */ + case CTRLA: + case CTRLE: + current_window ^= 1; + if (current_window == USERS_WINDOW) { + draw_border(users_border, true); + draw_border(chat_border, false); + } else { + draw_border(chat_border, true); + draw_border(users_border, false); + } + /* Need to reprint everything after drawing border */ + highlight_current_line(); + + break; + + default: + if (current_window == CHAT_WINDOW) + get_chatbox_content(ch); + } } cleanup: diff --git a/src/client/user.c b/src/client/user.c index 7d000f9..b0bb86e 100644 --- a/src/client/user.c +++ b/src/client/user.c @@ -28,7 +28,7 @@ void arraylist_free(ArrayList *list) /* * Check if the user is in the arraylist */ -long arraylist_search(ArrayList *list, char *username) +long arraylist_search(ArrayList *list, uint8_t *username) { for (long i = 0; i < list->length; i++) { if (strcmp(list->items[i].name, username) == 0) { @@ -55,9 +55,9 @@ void arraylist_remove(ArrayList *list, long index) /* * Force will not remove duplicate marked users, instead it just skip adding */ -void arraylist_add(ArrayList *list, char *name, wchar_t *icon, int color, bool marked, bool force) +void arraylist_add(ArrayList *list, uint8_t *username, wchar_t *icon, int color, bool marked, bool force) { - user new_user = { name, icon, color }; + user new_user = { username, icon, color }; if (list->capacity != list->length) { if (marked) {