Remove icons from UI's user, move protocol specific constant to packet.h, reconstruct private key, rename some variables
This commit is contained in:
parent
e4856689e9
commit
b84a664669
16 changed files with 912 additions and 938 deletions
|
@ -15,6 +15,7 @@
|
||||||
#define UP 0x103
|
#define UP 0x103
|
||||||
#define LEFT 0x104
|
#define LEFT 0x104
|
||||||
#define RIGHT 0x105
|
#define RIGHT 0x105
|
||||||
|
#define ENTER 0xA
|
||||||
|
|
||||||
void ncurses_init();
|
void ncurses_init();
|
||||||
void windows_init();
|
void windows_init();
|
||||||
|
@ -22,7 +23,7 @@ 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 add_message(uint8_t *author, uint8_t *recipient, uint8_t *content, uint32_t length, time_t creation);
|
||||||
void show_chat(uint8_t *recipient);
|
void show_chat(uint8_t *recipient);
|
||||||
void add_username(char *username);
|
void add_username(char *username);
|
||||||
void ncurses_deinit();
|
void deinit();
|
||||||
void ui();
|
void ui();
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -6,8 +6,7 @@
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
|
|
||||||
typedef struct user {
|
typedef struct user {
|
||||||
uint8_t *name;
|
uint8_t name[MAX_NAME];
|
||||||
wchar_t *icon;
|
|
||||||
int color;
|
int color;
|
||||||
} user;
|
} user;
|
||||||
|
|
||||||
|
@ -21,7 +20,7 @@ ArrayList *arraylist_init(size_t capacity);
|
||||||
void arraylist_free(ArrayList *list);
|
void arraylist_free(ArrayList *list);
|
||||||
long arraylist_search(ArrayList *list, uint8_t *username);
|
long arraylist_search(ArrayList *list, uint8_t *username);
|
||||||
void arraylist_remove(ArrayList *list, long index);
|
void arraylist_remove(ArrayList *list, long index);
|
||||||
void arraylist_add(ArrayList *list, uint8_t *username, wchar_t *icon, int color, bool marked, bool force);
|
void arraylist_add(ArrayList *list, uint8_t *username, int color, bool marked, bool force);
|
||||||
char *get_line(ArrayList *list, long index, bool icons);
|
int get_user_color(ArrayList *list, uint8_t *username);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -1,8 +1,4 @@
|
||||||
/* Server */
|
/* Server */
|
||||||
#define PORT 20247
|
|
||||||
#define MAX_NAME 32 /* Max username length */
|
|
||||||
#define MAX_DATA_LENGTH 8192
|
|
||||||
|
|
||||||
/* Don't touch unless you know what you are doing */
|
/* Don't touch unless you know what you are doing */
|
||||||
#define MAX_THREADS 8
|
#define MAX_THREADS 8
|
||||||
#define MAX_CLIENTS_PER_THREAD 1024
|
#define MAX_CLIENTS_PER_THREAD 1024
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
#define SK_RAW_SIZE crypto_sign_SECRETKEYBYTES
|
#define SK_RAW_SIZE crypto_sign_SECRETKEYBYTES
|
||||||
#define PK_DATA_SIZE PK_RAW_SIZE + MAX_NAME + TIME_SIZE
|
#define PK_DATA_SIZE PK_RAW_SIZE + MAX_NAME + TIME_SIZE
|
||||||
#define PK_SIZE PK_DATA_SIZE + SIGN_SIZE /* Size with signature */
|
#define PK_SIZE PK_DATA_SIZE + SIGN_SIZE /* Size with signature */
|
||||||
#define SK_SIZE SK_DATA_SIZE
|
#define SK_SIZE SK_RAW_SIZE
|
||||||
#define SHARED_KEY_SIZE crypto_kx_SESSIONKEYBYTES
|
#define SHARED_KEY_SIZE crypto_kx_SESSIONKEYBYTES
|
||||||
|
|
||||||
typedef struct public_key {
|
typedef struct public_key {
|
||||||
|
|
|
@ -49,10 +49,14 @@
|
||||||
#define ZSM_STA_AUTHORISED 0xE
|
#define ZSM_STA_AUTHORISED 0xE
|
||||||
#define ZSM_STA_CLOSED_CONNECTION 0xF
|
#define ZSM_STA_CLOSED_CONNECTION 0xF
|
||||||
|
|
||||||
|
#define PORT 20247
|
||||||
|
#define MAX_NAME 32 /* Max username length */
|
||||||
|
#define MAX_DATA_LENGTH 8192
|
||||||
|
|
||||||
#define ADDRESS_SIZE MAX_NAME + 1 + 255 /* 1 for @, 255 for domain, defined in RFC 5321, Section 4.5.3.1.2 */
|
#define ADDRESS_SIZE MAX_NAME + 1 + 255 /* 1 for @, 255 for domain, defined in RFC 5321, Section 4.5.3.1.2 */
|
||||||
#define HASH_SIZE crypto_generichash_BYTES
|
#define HASH_SIZE crypto_generichash_BYTES
|
||||||
#define NONCE_SIZE crypto_aead_xchacha20poly1305_ietf_NPUBBYTES
|
#define NONCE_SIZE crypto_box_NONCEBYTES /* 24 */
|
||||||
#define ADDITIONAL_SIZE crypto_aead_xchacha20poly1305_ietf_ABYTES
|
#define ADDITIONAL_SIZE crypto_box_MACBYTES /* 16 */
|
||||||
#define MAX_MESSAGE_LENGTH MAX_DATA_LENGTH - MAX_NAME * 2 - NONCE_SIZE
|
#define MAX_MESSAGE_LENGTH MAX_DATA_LENGTH - MAX_NAME * 2 - NONCE_SIZE
|
||||||
|
|
||||||
typedef struct packet_t {
|
typedef struct packet_t {
|
||||||
|
@ -73,11 +77,12 @@ typedef struct message_t {
|
||||||
#include "key.h"
|
#include "key.h"
|
||||||
|
|
||||||
/* Utilities functions */
|
/* Utilities functions */
|
||||||
|
void print_packet(packet_t *pkt);
|
||||||
int recv_packet(packet_t *pkt, int fd, uint8_t required_type);
|
int recv_packet(packet_t *pkt, int fd, uint8_t required_type);
|
||||||
packet_t *create_packet(uint8_t status, uint8_t type, uint32_t length, uint8_t *data, uint8_t *signature);
|
packet_t *create_packet(uint8_t status, uint8_t type, uint32_t length, uint8_t *data, uint8_t *signature);
|
||||||
int send_packet(packet_t *msg, int fd);
|
int send_packet(packet_t *pkt, int fd);
|
||||||
void free_packet(packet_t *msg);
|
void free_packet(packet_t *pkt);
|
||||||
int verify_packet(packet_t *pkt, int fd);
|
int verify_packet(packet_t *pkt, int fd);
|
||||||
uint8_t *create_signature(uint8_t *data, uint32_t length, secret_key *sk);
|
uint8_t *create_signature(uint8_t *data, uint32_t length, uint8_t *sk);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
#ifndef SERVER_H_
|
#ifndef SERVER_H_
|
||||||
#define SERVER_H_
|
#define SERVER_H_
|
||||||
|
|
||||||
extern int debug;
|
|
||||||
|
|
||||||
#define CHALLENGE_SIZE 32
|
#define CHALLENGE_SIZE 32
|
||||||
#define MAX_CONNECTION_QUEUE 128 /* for listen() */
|
#define MAX_CONNECTION_QUEUE 128 /* for listen() */
|
||||||
#define MAX_EVENTS 64 /* Max events can be returned simulataneouly by epoll */
|
#define MAX_EVENTS 64 /* Max events can be returned simulataneouly by epoll */
|
||||||
|
|
|
@ -22,17 +22,15 @@ keypair_t *create_keypair(char *username)
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
memcpy(public_key, pk_raw, PK_RAW_SIZE);
|
memcpy(pk_data, pk_raw, PK_RAW_SIZE);
|
||||||
memcpy(public_key + PK_RAW_SIZE, username_padded, MAX_NAME);
|
memcpy(pk_data + PK_RAW_SIZE, username_padded, MAX_NAME);
|
||||||
memcpy(public_key + PK_RAW_SIZE + MAX_NAME, ¤t_time, TIME_SIZE);
|
memcpy(pk_data + PK_RAW_SIZE + MAX_NAME, ¤t_time, TIME_SIZE);
|
||||||
|
|
||||||
crypto_generichash(pk_hash, HASH_SIZE, pk_data, PK_DATA_SIZE, NULL, 0);
|
crypto_generichash(pk_hash, HASH_SIZE, pk_data, PK_DATA_SIZE, NULL, 0);
|
||||||
crypto_sign_detached(pk_sign, NULL, pk_hash, HASH_SIZE, sk_raw);
|
crypto_sign_detached(pk_sign, NULL, pk_hash, HASH_SIZE, sk);
|
||||||
|
|
||||||
memcpy(pk, pk_raw, PK_RAW_SIZE);
|
memcpy(pk, pk_data, PK_DATA_SIZE);
|
||||||
memcpy(pk + PK_RAW_SIZE, metadata, METADATA_SIZE);
|
memcpy(pk + PK_DATA_SIZE, pk_sign, SIGN_SIZE);
|
||||||
memcpy(pk + PK_RAW_SIZE + METADATA_SIZE, sign, SIGN_SIZE);
|
|
||||||
memcpy(sk, sk_raw, SK_RAW_SIZE);
|
|
||||||
|
|
||||||
/* USE DB INSTEAD OF FILES */
|
/* USE DB INSTEAD OF FILES */
|
||||||
char pk_path[PATH_MAX], sk_path[PATH_MAX];
|
char pk_path[PATH_MAX], sk_path[PATH_MAX];
|
||||||
|
@ -49,10 +47,10 @@ keypair_t *create_keypair(char *username)
|
||||||
memcpy(kp->pk.raw, pk_raw, PK_RAW_SIZE);
|
memcpy(kp->pk.raw, pk_raw, PK_RAW_SIZE);
|
||||||
memcpy(kp->pk.username, username_padded, MAX_NAME);
|
memcpy(kp->pk.username, username_padded, MAX_NAME);
|
||||||
kp->pk.creation = current_time;
|
kp->pk.creation = current_time;
|
||||||
memcpy(kp->pk.signature, sign, SIGN_SIZE);
|
memcpy(kp->pk.signature, pk_sign, SIGN_SIZE);
|
||||||
memcpy(kp->pk.full, pk, PK_SIZE);
|
memcpy(kp->pk.full, pk, PK_SIZE);
|
||||||
|
|
||||||
memcpy(kp->sk, sk_raw, SK_SIZE);
|
memcpy(kp->sk, sk, SK_SIZE);
|
||||||
|
|
||||||
return kp;
|
return kp;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,22 @@
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
#include "server/server.h"
|
#include "server/server.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Print contents of packet
|
||||||
|
*/
|
||||||
|
void print_packet(packet_t *pkt)
|
||||||
|
{
|
||||||
|
printf("Packet:\n");
|
||||||
|
printf("Status: %d\n", pkt->status);
|
||||||
|
printf("Type: %d\n", pkt->type);
|
||||||
|
printf("Length: %d\n", pkt->length);
|
||||||
|
if (pkt->length > 0) {
|
||||||
|
printf("Data:\n");
|
||||||
|
print_bin(pkt->data, pkt->length);
|
||||||
|
printf("Signature:\n");
|
||||||
|
print_bin(pkt->signature, SIGN_SIZE);
|
||||||
|
}
|
||||||
|
}
|
||||||
/*
|
/*
|
||||||
* Requires manually free packet data
|
* Requires manually free packet data
|
||||||
* pkt: packet to fill data in (must be created via create_packet)
|
* pkt: packet to fill data in (must be created via create_packet)
|
||||||
|
@ -31,13 +47,7 @@ int recv_packet(packet_t *pkt, int fd, uint8_t required_type)
|
||||||
memcpy(&pkt->type, &header[sizeof(pkt->status)], sizeof(pkt->type));
|
memcpy(&pkt->type, &header[sizeof(pkt->status)], sizeof(pkt->type));
|
||||||
memcpy(&pkt->length, &header[sizeof(pkt->status) + sizeof(pkt->type)], sizeof(pkt->length));
|
memcpy(&pkt->length, &header[sizeof(pkt->status) + sizeof(pkt->type)], sizeof(pkt->length));
|
||||||
|
|
||||||
if (debug) {
|
|
||||||
printf("==========PACKET RECEIVED========\n");
|
|
||||||
printf("Status: %d\n", pkt->status);
|
|
||||||
printf("Type: %d\n", pkt->type);
|
|
||||||
printf("Length: %d\n", pkt->length);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Validate the packet type and length */
|
/* Validate the packet type and length */
|
||||||
if (pkt->type > 0xFF || pkt->type < 0x0 || pkt->type != required_type) {
|
if (pkt->type > 0xFF || pkt->type < 0x0 || pkt->type != required_type) {
|
||||||
status = ZSM_STA_INVALID_TYPE;
|
status = ZSM_STA_INVALID_TYPE;
|
||||||
|
@ -78,18 +88,7 @@ int recv_packet(packet_t *pkt, int fd, uint8_t required_type)
|
||||||
memcpy(pkt->signature, payload + pkt->length, SIGN_SIZE);
|
memcpy(pkt->signature, payload + pkt->length, SIGN_SIZE);
|
||||||
/* Null terminate data so it can be print */
|
/* Null terminate data so it can be print */
|
||||||
pkt->data[pkt->length] = '\0';
|
pkt->data[pkt->length] = '\0';
|
||||||
|
|
||||||
if (debug) {
|
|
||||||
printf("Data:\n");
|
|
||||||
print_bin(pkt->data, pkt->length);
|
|
||||||
printf("Signature:\n");
|
|
||||||
print_bin(pkt->signature, SIGN_SIZE);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (debug) {
|
|
||||||
printf("==========END RECEIVING==========\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
return status;
|
return status;
|
||||||
|
|
||||||
failure:;
|
failure:;
|
||||||
|
@ -162,21 +161,6 @@ int send_packet(packet_t *pkt, int fd)
|
||||||
goto failure;
|
goto failure;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (debug) {
|
|
||||||
printf("==========PACKET SENT============\n");
|
|
||||||
printf("Status: %d\n", pkt->status);
|
|
||||||
printf("Type: %d\n", pkt->type);
|
|
||||||
printf("Length: %d\n", pkt->length);
|
|
||||||
if (pkt->length > 0) {
|
|
||||||
printf("Data:\n");
|
|
||||||
print_bin(pkt->data, pkt->length);
|
|
||||||
printf("Signature:\n");
|
|
||||||
print_bin(pkt->signature, SIGN_SIZE);
|
|
||||||
}
|
|
||||||
printf("==========END SENT===============\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
return status;
|
return status;
|
||||||
|
|
||||||
failure:
|
failure:
|
||||||
|
@ -208,9 +192,8 @@ void free_packet(packet_t *pkt)
|
||||||
* Wrapper for recv_packet to verify packet
|
* Wrapper for recv_packet to verify packet
|
||||||
* Reads packet from fd, stores in pkt
|
* Reads packet from fd, stores in pkt
|
||||||
*/
|
*/
|
||||||
int verify_packet(int fd)
|
int verify_packet(packet_t *pkt, int fd)
|
||||||
{
|
{
|
||||||
packet pkt;
|
|
||||||
int status = recv_packet(pkt, fd, ZSM_TYP_MESSAGE);
|
int status = recv_packet(pkt, fd, ZSM_TYP_MESSAGE);
|
||||||
if (status != ZSM_STA_SUCCESS) {
|
if (status != ZSM_STA_SUCCESS) {
|
||||||
close(fd);
|
close(fd);
|
||||||
|
@ -245,7 +228,7 @@ int verify_packet(int fd)
|
||||||
* Create signature for packet
|
* Create signature for packet
|
||||||
* When data, secret is null, length is 0, empty siganture is created
|
* When data, secret is null, length is 0, empty siganture is created
|
||||||
*/
|
*/
|
||||||
uint8_t *create_signature(uint8_t *data, uint32_t length, secret_key *sk)
|
uint8_t *create_signature(uint8_t *data, uint32_t length, uint8_t *sk)
|
||||||
{
|
{
|
||||||
uint8_t *signature = memalloc(SIGN_SIZE);
|
uint8_t *signature = memalloc(SIGN_SIZE);
|
||||||
if (data == NULL && length == 0 && sk == NULL) {
|
if (data == NULL && length == 0 && sk == NULL) {
|
||||||
|
@ -258,7 +241,7 @@ uint8_t *create_signature(uint8_t *data, uint32_t length, secret_key *sk)
|
||||||
crypto_generichash(hash, HASH_SIZE,
|
crypto_generichash(hash, HASH_SIZE,
|
||||||
data, length,
|
data, length,
|
||||||
NULL, 0);
|
NULL, 0);
|
||||||
crypto_sign_detached(signature, NULL, hash, HASH_SIZE, sk->raw);
|
crypto_sign_detached(signature, NULL, hash, HASH_SIZE, sk);
|
||||||
}
|
}
|
||||||
|
|
||||||
return signature;
|
return signature;
|
||||||
|
|
|
@ -130,7 +130,6 @@ void write_log(int type, const char *fmt, ...)
|
||||||
char *client_log = memalloc(PATH_MAX);
|
char *client_log = memalloc(PATH_MAX);
|
||||||
char *data_dir = replace_home(CLIENT_DATA_DIR);
|
char *data_dir = replace_home(CLIENT_DATA_DIR);
|
||||||
snprintf(client_log, PATH_MAX, "%s/%s", data_dir, "zen.log");
|
snprintf(client_log, PATH_MAX, "%s/%s", data_dir, "zen.log");
|
||||||
printf("log: %s\n", client_log);
|
|
||||||
free(data_dir);
|
free(data_dir);
|
||||||
if (access(client_log, W_OK) != 0) {
|
if (access(client_log, W_OK) != 0) {
|
||||||
/* If log file doesn't exist, most likely data dir won't exist too */
|
/* If log file doesn't exist, most likely data dir won't exist too */
|
||||||
|
|
|
@ -7,12 +7,7 @@
|
||||||
|
|
||||||
static int callback(void *ignore, int argc, char **argv, char **azColName)
|
static int callback(void *ignore, int argc, char **argv, char **azColName)
|
||||||
{
|
{
|
||||||
char *username = memalloc(MAX_NAME);
|
add_username(argv[0]);
|
||||||
strcpy(username, argv[0]);
|
|
||||||
/* Add only if it isn't talking yourself */
|
|
||||||
if (strncmp(username, USERNAME, MAX_NAME))
|
|
||||||
add_username(username);
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
663
src/zen/ui.c
Normal file
663
src/zen/ui.c
Normal file
|
@ -0,0 +1,663 @@
|
||||||
|
#include "config.h"
|
||||||
|
#include "packet.h"
|
||||||
|
#include "util.h"
|
||||||
|
#include "client/ui.h"
|
||||||
|
#include "client/db.h"
|
||||||
|
#include "client/user.h"
|
||||||
|
|
||||||
|
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_user = 0;
|
||||||
|
int current_window = 0;
|
||||||
|
int sockfd;
|
||||||
|
|
||||||
|
/* For tracking cursor position in content */
|
||||||
|
static int curs_pos = 0;
|
||||||
|
static char content[MAX_MESSAGE_LENGTH];
|
||||||
|
|
||||||
|
void send_message();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Free and close everything
|
||||||
|
*/
|
||||||
|
void deinit()
|
||||||
|
{
|
||||||
|
shutdown(sockfd, SHUT_WR);
|
||||||
|
close(sockfd);
|
||||||
|
arraylist_free(users);
|
||||||
|
arraylist_free(marked);
|
||||||
|
endwin();
|
||||||
|
}
|
||||||
|
|
||||||
|
void signal_handler(int signal)
|
||||||
|
{
|
||||||
|
switch (signal) {
|
||||||
|
case SIGPIPE:
|
||||||
|
error(0, "SIGPIPE received");
|
||||||
|
break;
|
||||||
|
case SIGABRT:
|
||||||
|
case SIGINT:
|
||||||
|
case SIGTERM:
|
||||||
|
deinit();
|
||||||
|
error(1, "Shutdown signal received");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Start ncurses
|
||||||
|
*/
|
||||||
|
void ncurses_init()
|
||||||
|
{
|
||||||
|
/* check if it is interactive shell */
|
||||||
|
if (!isatty(STDIN_FILENO)) {
|
||||||
|
error(1, "No tty detected. zen requires an interactive shell to run");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* initialize screen, don't print special chars,
|
||||||
|
* make ctrl + c work, don't show cursor
|
||||||
|
* enable arrow keys */
|
||||||
|
initscr();
|
||||||
|
noecho();
|
||||||
|
cbreak();
|
||||||
|
keypad(stdscr, TRUE);
|
||||||
|
/* check terminal has colors */
|
||||||
|
if (!has_colors()) {
|
||||||
|
endwin();
|
||||||
|
error(1, "Color is not supported in your terminal");
|
||||||
|
} else {
|
||||||
|
use_default_colors();
|
||||||
|
start_color();
|
||||||
|
}
|
||||||
|
/* colors */
|
||||||
|
init_pair(1, COLOR_BLACK, -1); /* */
|
||||||
|
init_pair(2, COLOR_RED, -1); /* */
|
||||||
|
init_pair(3, COLOR_GREEN, -1); /* active window */
|
||||||
|
init_pair(4, COLOR_YELLOW, -1); /* */
|
||||||
|
init_pair(5, COLOR_BLUE, -1); /* inactive window */
|
||||||
|
init_pair(6, COLOR_MAGENTA, -1); /* */
|
||||||
|
init_pair(7, COLOR_CYAN, -1); /* Selected user */
|
||||||
|
init_pair(8, COLOR_WHITE, -1); /* */
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Draw windows
|
||||||
|
*/
|
||||||
|
void windows_init()
|
||||||
|
{
|
||||||
|
int users_width = MAX_NAME / 2;
|
||||||
|
int chat_width = COLS - (MAX_NAME / 2);
|
||||||
|
|
||||||
|
/*------------------------------+
|
||||||
|
|-----border----||---border----||
|
||||||
|
|| || ||
|
||||||
|
|| content || content ||
|
||||||
|
|| (users) || (chat) ||
|
||||||
|
|| ||-------------||
|
||||||
|
|---------------||-textbox-----||
|
||||||
|
+==========panel===============*/
|
||||||
|
|
||||||
|
/* lines, cols, y, x */
|
||||||
|
panel = newwin(PANEL_HEIGHT, COLS, LINES - PANEL_HEIGHT, 0 );
|
||||||
|
users_border = newwin(LINES - PANEL_HEIGHT, users_width + 2, 0, 0 );
|
||||||
|
chat_border = newwin(LINES - PANEL_HEIGHT - TEXTBOX_HEIGHT, chat_width - 2, 0, users_width + 2);
|
||||||
|
textbox = newwin(TEXTBOX_HEIGHT, chat_width - 2, LINES - PANEL_HEIGHT - TEXTBOX_HEIGHT, users_width + 3);
|
||||||
|
|
||||||
|
/* lines, cols, y, x */
|
||||||
|
users_content = subwin(users_border, LINES - PANEL_HEIGHT - 2, users_width, 1, 1 );
|
||||||
|
chat_content = subwin(chat_border, LINES - PANEL_HEIGHT - 2 - TEXTBOX_HEIGHT, chat_width - 4, 1, users_width + 3);
|
||||||
|
|
||||||
|
/* draw border around windows */
|
||||||
|
refresh();
|
||||||
|
draw_border(users_border, true);
|
||||||
|
draw_border(chat_border, false);
|
||||||
|
|
||||||
|
scrollok(textbox, true);
|
||||||
|
scrollok(users_content, true);
|
||||||
|
scrollok(chat_content, true);
|
||||||
|
refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Draw the border of the window depending if it's active or not
|
||||||
|
*/
|
||||||
|
void draw_border(WINDOW *window, bool active)
|
||||||
|
{
|
||||||
|
/* turn on color depends on active */
|
||||||
|
if (active) {
|
||||||
|
wattron(window, COLOR_PAIR(3));
|
||||||
|
} else {
|
||||||
|
wattron(window, COLOR_PAIR(5));
|
||||||
|
}
|
||||||
|
|
||||||
|
box(window, 0, 0);
|
||||||
|
|
||||||
|
/* turn color off after turning it on */
|
||||||
|
if (active) {
|
||||||
|
wattroff(window, COLOR_PAIR(3));
|
||||||
|
} else {
|
||||||
|
wattroff(window, COLOR_PAIR(5));
|
||||||
|
}
|
||||||
|
wrefresh(window); /* Refresh the window to see the colored border and title */
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Print line to the panel
|
||||||
|
*/
|
||||||
|
void wpprintw(const char *fmt, ...)
|
||||||
|
{
|
||||||
|
va_list args;
|
||||||
|
va_start(args, fmt);
|
||||||
|
wclear(panel);
|
||||||
|
vw_printw(panel, fmt, args);
|
||||||
|
va_end(args);
|
||||||
|
wrefresh(panel);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Highlight current line by reversing the color
|
||||||
|
*/
|
||||||
|
void draw_users()
|
||||||
|
{
|
||||||
|
long overflow = 0;
|
||||||
|
/* Check if the current selected user is not shown in rendered text */
|
||||||
|
if (current_user > LINES - 3) {
|
||||||
|
/* overflown */
|
||||||
|
overflow = current_user - (LINES - 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Calculate number of users to show */
|
||||||
|
long range = users->length;
|
||||||
|
/* Stop drawing if there is no users */
|
||||||
|
if (range == 0) {
|
||||||
|
wprintw(chat_content, "No users. Start a converstation.");
|
||||||
|
wrefresh(chat_content);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (range > LINES - 3) {
|
||||||
|
/* if there are more users than lines available to display
|
||||||
|
* shrink range to avaiable lines to display with
|
||||||
|
* overflow to keep the number of iterations to be constant */
|
||||||
|
range = LINES - 3 + overflow;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Clears content before printing */
|
||||||
|
wclear(users_content);
|
||||||
|
|
||||||
|
/* To keep track the line to print after overflow */
|
||||||
|
long line_count = 0;
|
||||||
|
for (long i = overflow; i < range; i++) {
|
||||||
|
/* Check for currently selected user */
|
||||||
|
if ((overflow == 0 && i == current_user) || (overflow != 0 && i == current_user)) {
|
||||||
|
/* current selected user should have color reversed */
|
||||||
|
wattron(users_content, A_REVERSE);
|
||||||
|
|
||||||
|
/* check for marked users */
|
||||||
|
long num_marked = marked->length;
|
||||||
|
if (num_marked > 0) {
|
||||||
|
/* Determine length of formatted string */
|
||||||
|
int m_len = snprintf(NULL, 0, "[%ld] selected", num_marked);
|
||||||
|
char *selected = memalloc(m_len + 1);
|
||||||
|
|
||||||
|
snprintf(selected, m_len + 1, "[%ld] selected", num_marked);
|
||||||
|
wpprintw("(%ld/%ld) %s", current_user + 1, users->length, selected);
|
||||||
|
} else {
|
||||||
|
wpprintw("(%ld/%ld)", current_user + 1, users->length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* print the actual filename and stats */
|
||||||
|
user seluser = users->items[i];
|
||||||
|
size_t name_len = strlen(seluser.name);
|
||||||
|
|
||||||
|
/* If length of name is longer than half of allowed size in window,
|
||||||
|
* trim it to end with .. to show the it is too long to be displayed
|
||||||
|
*/
|
||||||
|
if (name_len >= (MAX_NAME / 2) - 2) {
|
||||||
|
name_len = (MAX_NAME / 2) - 2;
|
||||||
|
}
|
||||||
|
char *line = memalloc(name_len);
|
||||||
|
memcpy(line, seluser.name, name_len);
|
||||||
|
|
||||||
|
int color = users->items[i].color;
|
||||||
|
|
||||||
|
/* check is user marked for action */
|
||||||
|
bool is_marked = arraylist_search(marked, users->items[i].name) != -1;
|
||||||
|
if (is_marked) {
|
||||||
|
/* show user is selected */
|
||||||
|
wattron(users_content, COLOR_PAIR(7));
|
||||||
|
} else {
|
||||||
|
/* print username with default color */
|
||||||
|
wattron(users_content, COLOR_PAIR(color));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (overflow > 0) {
|
||||||
|
mvwprintw(users_content, line_count, 0, "%s", line);
|
||||||
|
} else {
|
||||||
|
mvwprintw(users_content, i, 0, "%s", line);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* turn off color after printing */
|
||||||
|
if (is_marked) {
|
||||||
|
wattroff(users_content, COLOR_PAIR(7));
|
||||||
|
} else {
|
||||||
|
wattroff(users_content, COLOR_PAIR(color));
|
||||||
|
}
|
||||||
|
|
||||||
|
wattroff(users_content, A_REVERSE);
|
||||||
|
free(line);
|
||||||
|
line_count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
wrefresh(users_content);
|
||||||
|
wrefresh(panel);
|
||||||
|
/* show chat conversation every time cursor changes */
|
||||||
|
show_chat(users->items[current_user].name);
|
||||||
|
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
|
||||||
|
* if flag is 1, print date as well
|
||||||
|
* user_color is the color defined above at ncurses_init
|
||||||
|
*/
|
||||||
|
void print_message(int flag, message_t *msg)
|
||||||
|
{
|
||||||
|
struct tm *timeinfo = localtime(&msg->creation);
|
||||||
|
char timestr[21];
|
||||||
|
if (flag) {
|
||||||
|
strftime(timestr, sizeof(timestr), "%b %d %Y %H:%M:%S", timeinfo);
|
||||||
|
} else {
|
||||||
|
strftime(timestr, sizeof(timestr), "%H:%M:%S", timeinfo);
|
||||||
|
}
|
||||||
|
wprintw(chat_content, "%s ", timestr);
|
||||||
|
|
||||||
|
wattron(chat_content, A_BOLD);
|
||||||
|
int user_color = get_user_color(users, msg->author);
|
||||||
|
wattron(chat_content, COLOR_PAIR(user_color));
|
||||||
|
wprintw(chat_content, "<%s> ", msg->author);
|
||||||
|
wattroff(chat_content, A_BOLD);
|
||||||
|
wattroff(chat_content, COLOR_PAIR(user_color));
|
||||||
|
|
||||||
|
int i = 0;
|
||||||
|
int n = strlen(msg->content);
|
||||||
|
int in_bold = 0, in_italic = 0, in_underline = 0, in_block = 0;
|
||||||
|
int last_active_color = -1;
|
||||||
|
|
||||||
|
while (i < n) {
|
||||||
|
/* Bold */
|
||||||
|
if (msg->content[i] == '*' && msg->content[i + 1] == '*') {
|
||||||
|
if (!in_bold) {
|
||||||
|
/* Look ahead for the matching closing delimiter */
|
||||||
|
int closing_pos = i + 2;
|
||||||
|
while (closing_pos < n && !(msg->content[closing_pos] == '*' && msg->content[closing_pos + 1] == '*')) {
|
||||||
|
closing_pos++;
|
||||||
|
}
|
||||||
|
if (closing_pos < n) {
|
||||||
|
wattron(chat_content, A_BOLD);
|
||||||
|
in_bold = 1;
|
||||||
|
} else {
|
||||||
|
/* Treat as regular text if closing delimiter */
|
||||||
|
waddch(chat_content, msg->content[i++]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
wattroff(chat_content, A_BOLD);
|
||||||
|
in_bold = 0;
|
||||||
|
}
|
||||||
|
/* Skip */
|
||||||
|
i += 2;
|
||||||
|
|
||||||
|
/* Italic */
|
||||||
|
} else if (msg->content[i] == '*') {
|
||||||
|
if (!in_italic) {
|
||||||
|
/* Look ahead for the matching closing delimiter */
|
||||||
|
int closing_pos = i + 1;
|
||||||
|
while (closing_pos < n && msg->content[closing_pos] != '*') {
|
||||||
|
closing_pos++;
|
||||||
|
}
|
||||||
|
if (closing_pos < n) {
|
||||||
|
wattron(chat_content, A_ITALIC);
|
||||||
|
in_italic = 1;
|
||||||
|
} else {
|
||||||
|
/* Treat as regular text if closing delimiter */
|
||||||
|
waddch(chat_content, msg->content[i++]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
wattroff(chat_content, A_ITALIC);
|
||||||
|
in_italic = 0;
|
||||||
|
}
|
||||||
|
/* Skip */
|
||||||
|
i += 1;
|
||||||
|
|
||||||
|
/* Underline */
|
||||||
|
} else if (msg->content[i] == '_') {
|
||||||
|
if (!in_underline) {
|
||||||
|
/* Look ahead for the matching closing delimiter */
|
||||||
|
int closing_pos = i + 1;
|
||||||
|
while (closing_pos < n && msg->content[closing_pos] != '_') {
|
||||||
|
closing_pos++;
|
||||||
|
}
|
||||||
|
if (closing_pos < n) {
|
||||||
|
wattron(chat_content, A_UNDERLINE);
|
||||||
|
in_underline = 1;
|
||||||
|
} else {
|
||||||
|
/* Treat as regular text if closing delimiter */
|
||||||
|
waddch(chat_content, msg->content[i++]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
wattroff(chat_content, A_UNDERLINE);
|
||||||
|
in_underline = 0;
|
||||||
|
}
|
||||||
|
/* Skip */
|
||||||
|
i += 1;
|
||||||
|
|
||||||
|
/* Block */
|
||||||
|
} else if (msg->content[i] == '`') {
|
||||||
|
if (!in_block) {
|
||||||
|
/* Look ahead for the matching closing delimiter */
|
||||||
|
int closing_pos = i + 1;
|
||||||
|
while (closing_pos < n && msg->content[closing_pos] != '`') {
|
||||||
|
closing_pos++;
|
||||||
|
}
|
||||||
|
if (closing_pos < n) {
|
||||||
|
wattron(chat_content, A_STANDOUT);
|
||||||
|
in_block = 1;
|
||||||
|
} else {
|
||||||
|
/* Treat as regular text if closing delimiter */
|
||||||
|
waddch(chat_content, msg->content[i++]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
wattroff(chat_content, A_STANDOUT);
|
||||||
|
in_block = 0;
|
||||||
|
}
|
||||||
|
/* Skip */
|
||||||
|
i += 1;
|
||||||
|
|
||||||
|
/* Allow escape sequence for genuine backslash */
|
||||||
|
} else if (msg->content[i] == '\\' && msg->content[i + 1] == '\\') {
|
||||||
|
/* Print a literal backslash */
|
||||||
|
waddch(chat_content, '\\');
|
||||||
|
/* Skip both backslashes */
|
||||||
|
i += 2;
|
||||||
|
|
||||||
|
/* Color, new line and tab */
|
||||||
|
} else if (msg->content[i] == '\\') {
|
||||||
|
/* Skip the backslash and check the next character */
|
||||||
|
i++;
|
||||||
|
/* Handle color codes \1 to \8 */
|
||||||
|
if (msg->content[i] >= '1' && msg->content[i] <= '8') {
|
||||||
|
/* Convert char to int */
|
||||||
|
int new_color = msg->content[i] - '0';
|
||||||
|
if (new_color == last_active_color) {
|
||||||
|
/* Turn off current color */
|
||||||
|
wattroff(chat_content, COLOR_PAIR(last_active_color));
|
||||||
|
/* Reset last active color */
|
||||||
|
last_active_color = -1;
|
||||||
|
} else {
|
||||||
|
if (last_active_color != -1) {
|
||||||
|
/* Turn off previous color */
|
||||||
|
wattroff(chat_content, COLOR_PAIR(last_active_color));
|
||||||
|
}
|
||||||
|
last_active_color = new_color;
|
||||||
|
/* Turn on new color */
|
||||||
|
wattron(chat_content, COLOR_PAIR(new_color));
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
/* Handle new line */
|
||||||
|
} else if (msg->content[i] == 'n') {
|
||||||
|
waddch(chat_content, '\n');
|
||||||
|
/* Skip the 'n' */
|
||||||
|
i++;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
/* Invalid sequence, just print the backslash and character */
|
||||||
|
waddch(chat_content, '\\');
|
||||||
|
waddch(chat_content, msg->content[i]);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
/* Print regular character */
|
||||||
|
waddch(chat_content, msg->content[i]);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* Ensure attributes are turned off after printing */
|
||||||
|
wattroff(chat_content, A_BOLD);
|
||||||
|
wattroff(chat_content, A_ITALIC);
|
||||||
|
wattroff(chat_content, A_UNDERLINE);
|
||||||
|
wattroff(chat_content, A_STANDOUT);
|
||||||
|
for (int i = 1; i < 8; i++) {
|
||||||
|
wattroff(chat_content, COLOR_PAIR(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Get chat conversation into buffer and show it to chat window
|
||||||
|
*/
|
||||||
|
void show_chat(uint8_t *recipient)
|
||||||
|
{
|
||||||
|
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 message to print is older than previous message by a day,
|
||||||
|
* enable flag in print_message to include date */
|
||||||
|
int print_date = 0;
|
||||||
|
if (i > 0 && messages[i - 1].content != NULL && message.creation >= messages[i - 1].creation + 86400) {
|
||||||
|
print_date = 1;
|
||||||
|
}
|
||||||
|
if (strncmp(message.author, USERNAME, MAX_NAME) == 0 &&
|
||||||
|
strncmp(message.recipient, recipient, MAX_NAME) == 0) {
|
||||||
|
print_message(print_date, &message);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strncmp(message.author, recipient, MAX_NAME) == 0 &&
|
||||||
|
strncmp(message.recipient, USERNAME, MAX_NAME) == 0) {
|
||||||
|
print_message(print_date, &message);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
wrefresh(chat_content);
|
||||||
|
/* after printing move cursor back to textbox */
|
||||||
|
wmove(textbox, 0, curs_pos + 2);
|
||||||
|
wrefresh(textbox);
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
* Require heap allocated username
|
||||||
|
*/
|
||||||
|
void add_username(char *username)
|
||||||
|
{
|
||||||
|
int randomco = rand() % 8;
|
||||||
|
arraylist_add(users, username, randomco, false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void get_chatbox_content(int ch)
|
||||||
|
{
|
||||||
|
if (ch == KEY_BACKSPACE || ch == 127) {
|
||||||
|
if (curs_pos > 0) {
|
||||||
|
curs_pos--;
|
||||||
|
content[curs_pos] = '\0';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* Append it to the content if it is normal character */
|
||||||
|
else if (curs_pos < MAX_MESSAGE_LENGTH - 1) {
|
||||||
|
/* Filter readable ASCII */
|
||||||
|
if (ch > 31 && ch < 127) {
|
||||||
|
content[curs_pos++] = ch;
|
||||||
|
content[curs_pos] = '\0';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Display the current content */
|
||||||
|
mvwprintw(textbox, 0, 0, "> %s", content);
|
||||||
|
wrefresh(textbox);
|
||||||
|
}
|
||||||
|
|
||||||
|
void send_message()
|
||||||
|
{
|
||||||
|
uint8_t *recipient = users->items[current_user].name;
|
||||||
|
|
||||||
|
keypair_t *kp_from = get_keypair(USERNAME);
|
||||||
|
keypair_t *kp_to = get_keypair(recipient);
|
||||||
|
|
||||||
|
int status = ZSM_STA_SUCCESS;
|
||||||
|
|
||||||
|
uint8_t shared_key[SHARED_KEY_SIZE];
|
||||||
|
if (crypto_kx_client_session_keys(shared_key, NULL, kp_from->pk.raw,
|
||||||
|
kp_from->sk, kp_to->pk.raw) != 0) {
|
||||||
|
/* Recipient public key is suspicious */
|
||||||
|
write_log(LOG_ERROR, "Error performing key exchange with %s\n", recipient);
|
||||||
|
}
|
||||||
|
|
||||||
|
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->pk.username, MAX_NAME);
|
||||||
|
memcpy(data + MAX_NAME, kp_to->pk.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\n");
|
||||||
|
}
|
||||||
|
add_message(USERNAME, recipient, content, content_len, time(NULL));
|
||||||
|
free_packet(pkt);
|
||||||
|
show_chat(recipient);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Main loop of user interface
|
||||||
|
*/
|
||||||
|
void ui(int *fd)
|
||||||
|
{
|
||||||
|
signal(SIGPIPE, signal_handler);
|
||||||
|
signal(SIGABRT, signal_handler);
|
||||||
|
signal(SIGINT, signal_handler);
|
||||||
|
signal(SIGTERM, signal_handler);
|
||||||
|
ncurses_init();
|
||||||
|
windows_init();
|
||||||
|
sockfd = *fd;
|
||||||
|
users = arraylist_init(LINES);
|
||||||
|
marked = arraylist_init(100);
|
||||||
|
sqlite_init();
|
||||||
|
draw_users();
|
||||||
|
refresh();
|
||||||
|
while (1) {
|
||||||
|
if (current_window == CHAT_WINDOW) {
|
||||||
|
wclear(textbox);
|
||||||
|
mvwprintw(textbox, 0, 0, "> %s", content);
|
||||||
|
wrefresh(textbox);
|
||||||
|
wmove(textbox, 0, curs_pos + 2);
|
||||||
|
/* Set cursor to visible */
|
||||||
|
curs_set(2);
|
||||||
|
} else {
|
||||||
|
/* Set cursor to invisible */
|
||||||
|
curs_set(0);
|
||||||
|
}
|
||||||
|
int ch = getch();
|
||||||
|
switch (ch) {
|
||||||
|
/* go up by k or up arrow */
|
||||||
|
case UP:
|
||||||
|
if (current_window == USERS_WINDOW) {
|
||||||
|
if (current_user > 0)
|
||||||
|
current_user--;
|
||||||
|
|
||||||
|
draw_users();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
/* go down by j or down arrow */
|
||||||
|
case DOWN:
|
||||||
|
if (current_window == USERS_WINDOW) {
|
||||||
|
if (current_user < (users->length - 1))
|
||||||
|
current_user++;
|
||||||
|
|
||||||
|
draw_users();
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CLEAR_INPUT:
|
||||||
|
if (current_window == CHAT_WINDOW) {
|
||||||
|
curs_pos = 0;
|
||||||
|
content[0] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
case ENTER:
|
||||||
|
if (current_window == CHAT_WINDOW) {
|
||||||
|
content[curs_pos++] = ch;
|
||||||
|
content[curs_pos++] = '\0';
|
||||||
|
send_message();
|
||||||
|
/* Reset for new input */
|
||||||
|
curs_pos = 0;
|
||||||
|
|
||||||
|
/* Set content[0] for printing purposes */
|
||||||
|
content[0] = '\0';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
|
||||||
|
default:
|
||||||
|
if (current_window == CHAT_WINDOW)
|
||||||
|
get_chatbox_content(ch);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
deinit();
|
||||||
|
return;
|
||||||
|
}
|
|
@ -1,3 +1,6 @@
|
||||||
|
/* Arraylist implementation */
|
||||||
|
#include <ncurses.h>
|
||||||
|
|
||||||
#include "packet.h"
|
#include "packet.h"
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
#include "client/user.h"
|
#include "client/user.h"
|
||||||
|
@ -14,13 +17,6 @@ ArrayList *arraylist_init(size_t capacity)
|
||||||
|
|
||||||
void arraylist_free(ArrayList *list)
|
void arraylist_free(ArrayList *list)
|
||||||
{
|
{
|
||||||
for (size_t i = 0; i < list->length; i++) {
|
|
||||||
if (list->items[i].name != NULL)
|
|
||||||
free(list->items[i].name);
|
|
||||||
if (list->items[i].icon != NULL)
|
|
||||||
free(list->items[i].icon);
|
|
||||||
}
|
|
||||||
|
|
||||||
free(list->items);
|
free(list->items);
|
||||||
free(list);
|
free(list);
|
||||||
}
|
}
|
||||||
|
@ -43,9 +39,6 @@ void arraylist_remove(ArrayList *list, long index)
|
||||||
if (index >= list->length)
|
if (index >= list->length)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
free(list->items[index].name);
|
|
||||||
free(list->items[index].icon);
|
|
||||||
|
|
||||||
for (long i = index; i < list->length - 1; i++)
|
for (long i = index; i < list->length - 1; i++)
|
||||||
list->items[i] = list->items[i + 1];
|
list->items[i] = list->items[i + 1];
|
||||||
|
|
||||||
|
@ -55,9 +48,11 @@ void arraylist_remove(ArrayList *list, long index)
|
||||||
/*
|
/*
|
||||||
* Force will not remove duplicate marked users, instead it just skip adding
|
* Force will not remove duplicate marked users, instead it just skip adding
|
||||||
*/
|
*/
|
||||||
void arraylist_add(ArrayList *list, uint8_t *username, wchar_t *icon, int color, bool marked, bool force)
|
void arraylist_add(ArrayList *list, uint8_t *username, int color, bool marked, bool force)
|
||||||
{
|
{
|
||||||
user new_user = { username, icon, color };
|
user new_user;
|
||||||
|
strcpy(new_user.name, username);
|
||||||
|
new_user.color = color;
|
||||||
|
|
||||||
if (list->capacity != list->length) {
|
if (list->capacity != list->length) {
|
||||||
if (marked) {
|
if (marked) {
|
||||||
|
@ -86,34 +81,13 @@ void arraylist_add(ArrayList *list, uint8_t *username, wchar_t *icon, int color,
|
||||||
list->length++;
|
list->length++;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
int get_user_color(ArrayList *list, uint8_t *username)
|
||||||
* Construct a formatted line for display
|
|
||||||
*/
|
|
||||||
char *get_line(ArrayList *list, long index, bool icons)
|
|
||||||
{
|
{
|
||||||
user seluser = list->items[index];
|
for (int i = 0; i < list->length; i++) {
|
||||||
|
if (strncmp(username, list->items[i].name, MAX_NAME) == 0) {
|
||||||
size_t name_len = strlen(seluser.name);
|
return list->items[i].color;
|
||||||
size_t length;
|
}
|
||||||
|
}
|
||||||
if (icons) {
|
/* Red as default color */
|
||||||
length = name_len + 10; /* 8 for icon, 1 for space and 1 for null */
|
return COLOR_RED;
|
||||||
} else {
|
|
||||||
length = name_len;
|
|
||||||
}
|
|
||||||
|
|
||||||
char *line = memalloc(length);
|
|
||||||
line[0] = '\0';
|
|
||||||
|
|
||||||
if (icons) {
|
|
||||||
char *tmp = memalloc(9);
|
|
||||||
snprintf(tmp, 8, "%ls", seluser.icon);
|
|
||||||
|
|
||||||
strcat(line, tmp);
|
|
||||||
strcat(line, " ");
|
|
||||||
free(tmp);
|
|
||||||
}
|
|
||||||
strcat(line, seluser.name);
|
|
||||||
|
|
||||||
return line;
|
|
||||||
}
|
}
|
||||||
|
|
771
src/zen/zen.c
771
src/zen/zen.c
|
@ -1,641 +1,184 @@
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "packet.h"
|
#include "packet.h"
|
||||||
|
#include "key.h"
|
||||||
|
#include "notification.h"
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
#include "client/ui.h"
|
#include "client/ui.h"
|
||||||
#include "client/db.h"
|
#include "client/db.h"
|
||||||
#include "client/user.h"
|
#include "server/server.h"
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
/* For tracking cursor position in content */
|
|
||||||
static int curs_pos = 0;
|
|
||||||
static char content[MAX_MESSAGE_LENGTH];
|
|
||||||
|
|
||||||
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()
|
|
||||||
{
|
|
||||||
/* check if it is interactive shell */
|
|
||||||
if (!isatty(STDIN_FILENO)) {
|
|
||||||
error(1, "No tty detected. zen requires an interactive shell to run");
|
|
||||||
}
|
|
||||||
|
|
||||||
/* initialize screen, don't print special chars,
|
|
||||||
* make ctrl + c work, don't show cursor
|
|
||||||
* enable arrow keys */
|
|
||||||
initscr();
|
|
||||||
noecho();
|
|
||||||
cbreak();
|
|
||||||
keypad(stdscr, TRUE);
|
|
||||||
/* check terminal has colors */
|
|
||||||
if (!has_colors()) {
|
|
||||||
endwin();
|
|
||||||
error(1, "Color is not supported in your terminal");
|
|
||||||
} else {
|
|
||||||
use_default_colors();
|
|
||||||
start_color();
|
|
||||||
}
|
|
||||||
/* colors */
|
|
||||||
init_pair(1, COLOR_BLACK, -1); /* */
|
|
||||||
init_pair(2, COLOR_RED, -1); /* */
|
|
||||||
init_pair(3, COLOR_GREEN, -1); /* */
|
|
||||||
init_pair(4, COLOR_YELLOW, -1); /* */
|
|
||||||
init_pair(5, COLOR_BLUE, -1); /* */
|
|
||||||
init_pair(6, COLOR_MAGENTA, -1); /* */
|
|
||||||
init_pair(7, COLOR_CYAN, -1); /* */
|
|
||||||
init_pair(8, COLOR_WHITE, -1); /* */
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Draw windows
|
* Authenticate with server by signing a challenge
|
||||||
*/
|
*/
|
||||||
void windows_init()
|
int authenticate_server(int *sockfd)
|
||||||
{
|
{
|
||||||
int users_width = 32;
|
keypair_t *kp = get_keypair(USERNAME);
|
||||||
int chat_width = COLS - 32;
|
/* create empty packet */
|
||||||
|
packet_t *pkt = create_packet(0, 0, 0, NULL, NULL);
|
||||||
/*------------------------------+
|
int status;
|
||||||
|-----border----||---border----||
|
if ((status = recv_packet(pkt, *sockfd, ZSM_TYP_AUTH) != ZSM_STA_SUCCESS)) {
|
||||||
|| || ||
|
return status;
|
||||||
|| content || content ||
|
}
|
||||||
|| (users) || (chat) ||
|
uint8_t *challenge = pkt->data;
|
||||||
|| ||-------------||
|
|
||||||
|---------------||-textbox-----||
|
|
||||||
+==========panel===============*/
|
|
||||||
|
|
||||||
/* lines, cols, y, x */
|
uint8_t *sig = memalloc(SIGN_SIZE);
|
||||||
panel = newwin(PANEL_HEIGHT, COLS, LINES - PANEL_HEIGHT, 0 );
|
crypto_sign_detached(sig, NULL, challenge, CHALLENGE_SIZE, kp->sk);
|
||||||
users_border = newwin(LINES - PANEL_HEIGHT, users_width + 2, 0, 0 );
|
|
||||||
chat_border = newwin(LINES - PANEL_HEIGHT - TEXTBOX_HEIGHT, chat_width - 2, 0, users_width + 2);
|
|
||||||
textbox = newwin(TEXTBOX_HEIGHT, chat_width - 2, LINES - PANEL_HEIGHT - TEXTBOX_HEIGHT, users_width + 3);
|
|
||||||
|
|
||||||
/* lines, cols, y, x */
|
uint8_t *pk_full = memalloc(PK_SIZE);
|
||||||
users_content = subwin(users_border, LINES - PANEL_HEIGHT - 2, users_width, 1, 1 );
|
memcpy(pk_full, kp->pk.full, PK_SIZE);
|
||||||
chat_content = subwin(chat_border, LINES - PANEL_HEIGHT - 2 - TEXTBOX_HEIGHT, chat_width - 4, 1, users_width + 3);
|
|
||||||
|
|
||||||
/* draw border around windows */
|
|
||||||
refresh();
|
|
||||||
draw_border(users_border, true);
|
|
||||||
draw_border(chat_border, false);
|
|
||||||
|
|
||||||
scrollok(textbox, true);
|
pkt->status = 1;
|
||||||
scrollok(users_content, true);
|
pkt->type = ZSM_TYP_AUTH;
|
||||||
scrollok(chat_content, true);
|
pkt->length = SIGN_SIZE;
|
||||||
refresh();
|
pkt->data = pk_full;
|
||||||
}
|
pkt->signature = sig;
|
||||||
|
|
||||||
/*
|
if ((status = send_packet(pkt, *sockfd)) != ZSM_STA_SUCCESS) {
|
||||||
* Draw the border of the window depending if it's active or not,
|
/* fd already closed */
|
||||||
*/
|
error(0, "Could not authenticate with server, status: %d", status);
|
||||||
void draw_border(WINDOW *window, bool active)
|
free_packet(pkt);
|
||||||
{
|
return ZSM_STA_ERROR_AUTHENTICATE;
|
||||||
int width;
|
|
||||||
if (window == users_border) {
|
|
||||||
width = 34;
|
|
||||||
} else {
|
|
||||||
width = COLS - 34;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* turn on color depends on active */
|
if ((status = recv_packet(pkt, *sockfd, ZSM_TYP_INFO)) != ZSM_STA_SUCCESS) {
|
||||||
if (active) {
|
return status;
|
||||||
wattron(window, COLOR_PAIR(3));
|
};
|
||||||
} else {
|
status = pkt->status;
|
||||||
wattron(window, COLOR_PAIR(5));
|
|
||||||
}
|
|
||||||
|
|
||||||
box(window, 0, 0);
|
|
||||||
|
|
||||||
/* turn color off after turning it on */
|
|
||||||
if (active) {
|
|
||||||
wattroff(window, COLOR_PAIR(3));
|
|
||||||
} else {
|
|
||||||
wattroff(window, COLOR_PAIR(5));
|
|
||||||
}
|
|
||||||
wrefresh(window); /* Refresh the window to see the colored border and title */
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Print line to the panel
|
|
||||||
*/
|
|
||||||
void wpprintw(const char *fmt, ...)
|
|
||||||
{
|
|
||||||
va_list args;
|
|
||||||
va_start(args, fmt);
|
|
||||||
wclear(panel);
|
|
||||||
vw_printw(panel, fmt, args);
|
|
||||||
va_end(args);
|
|
||||||
wrefresh(panel);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Highlight current line by reversing the color
|
|
||||||
*/
|
|
||||||
void highlight_current_line()
|
|
||||||
{
|
|
||||||
long overflow = 0;
|
|
||||||
if (current_selection > LINES - 4) {
|
|
||||||
/* overflown */
|
|
||||||
overflow = current_selection - (LINES - 4);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* calculate range of files to show */
|
|
||||||
long range = users->length;
|
|
||||||
/* not highlight if no files in directory */
|
|
||||||
if (range == 0 && errno == 0) {
|
|
||||||
wprintw(chat_content, "No users. Start a converstation.");
|
|
||||||
wrefresh(chat_content);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (range > LINES - 3) {
|
|
||||||
/* if there are more files than lines available to display
|
|
||||||
* shrink range to avaiable lines to display with
|
|
||||||
* overflow to keep the number of iterations to be constant */
|
|
||||||
range = LINES - 3 + overflow;
|
|
||||||
}
|
|
||||||
|
|
||||||
wclear(users_content);
|
|
||||||
long line_count = 0;
|
|
||||||
for (long i = overflow; i < range; i++) {
|
|
||||||
if ((overflow == 0 && i == current_selection) || (overflow != 0 && i == current_selection)) {
|
|
||||||
wattron(users_content, A_REVERSE);
|
|
||||||
|
|
||||||
/* check for marked user */
|
|
||||||
long num_marked = marked->length;
|
|
||||||
if (num_marked > 0) {
|
|
||||||
/* Determine length of formatted string */
|
|
||||||
int m_len = snprintf(NULL, 0, "[%ld] selected", num_marked);
|
|
||||||
char *selected = memalloc(m_len + 1);
|
|
||||||
|
|
||||||
snprintf(selected, m_len + 1, "[%ld] selected", num_marked);
|
|
||||||
wpprintw("(%ld/%ld) %s", current_selection + 1, users->length, selected);
|
|
||||||
} else {
|
|
||||||
wpprintw("(%ld/%ld)", current_selection + 1, users->length);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/* print the actual filename and stats */
|
|
||||||
char *line = get_line(users, i, show_icons);
|
|
||||||
int color = users->items[i].color;
|
|
||||||
/* check is user marked for action */
|
|
||||||
bool is_marked = arraylist_search(marked, users->items[i].name) != -1;
|
|
||||||
if (is_marked) {
|
|
||||||
/* show user is selected */
|
|
||||||
wattron(users_content, COLOR_PAIR(7));
|
|
||||||
} else {
|
|
||||||
/* print the whole directory with default colors */
|
|
||||||
wattron(users_content, COLOR_PAIR(color));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (overflow > 0)
|
|
||||||
mvwprintw(users_content, line_count, 0, "%s", line);
|
|
||||||
else
|
|
||||||
mvwprintw(users_content, i, 0, "%s", line);
|
|
||||||
|
|
||||||
if (is_marked) {
|
|
||||||
wattroff(users_content, COLOR_PAIR(7));
|
|
||||||
} else {
|
|
||||||
wattroff(users_content, COLOR_PAIR(color));
|
|
||||||
}
|
|
||||||
|
|
||||||
wattroff(users_content, A_REVERSE);
|
|
||||||
//free(line);
|
|
||||||
line_count++;
|
|
||||||
}
|
|
||||||
|
|
||||||
wrefresh(users_content);
|
|
||||||
wrefresh(panel);
|
|
||||||
/* show chat conversation every time cursor changes */
|
|
||||||
show_chat(users->items[current_selection].name);
|
|
||||||
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
|
|
||||||
* if flag is 1, print date as well
|
|
||||||
* user_color is the color defined above at ncurses_init
|
|
||||||
*/
|
|
||||||
void print_message(int flag, message_t *msg, int user_color)
|
|
||||||
{
|
|
||||||
struct tm *timeinfo = localtime(&msg->creation);
|
|
||||||
char timestr[21];
|
|
||||||
if (flag) {
|
|
||||||
strftime(timestr, sizeof(timestr), "%b %d %Y %H:%M:%S", timeinfo);
|
|
||||||
} else {
|
|
||||||
strftime(timestr, sizeof(timestr), "%H:%M:%S", timeinfo);
|
|
||||||
}
|
|
||||||
wprintw(chat_content, "%s ", timestr);
|
|
||||||
|
|
||||||
wattron(chat_content, A_BOLD);
|
|
||||||
wattron(chat_content, COLOR_PAIR(user_color));
|
|
||||||
wprintw(chat_content, "<%s> ", msg->author);
|
|
||||||
wattroff(chat_content, A_BOLD);
|
|
||||||
wattroff(chat_content, COLOR_PAIR(user_color));
|
|
||||||
|
|
||||||
int i = 0;
|
|
||||||
int n = strlen(msg->content);
|
|
||||||
int in_bold = 0, in_italic = 0, in_underline = 0, in_block = 0;
|
|
||||||
int last_active_color = -1;
|
|
||||||
|
|
||||||
while (i < n) {
|
|
||||||
/* Bold */
|
|
||||||
if (msg->content[i] == '*' && msg->content[i + 1] == '*') {
|
|
||||||
if (!in_bold) {
|
|
||||||
/* Look ahead for the matching closing delimiter */
|
|
||||||
int closing_pos = i + 2;
|
|
||||||
while (closing_pos < n && !(msg->content[closing_pos] == '*' && msg->content[closing_pos + 1] == '*')) {
|
|
||||||
closing_pos++;
|
|
||||||
}
|
|
||||||
if (closing_pos < n) {
|
|
||||||
wattron(chat_content, A_BOLD);
|
|
||||||
in_bold = 1;
|
|
||||||
} else {
|
|
||||||
/* Treat as regular text if closing delimiter */
|
|
||||||
waddch(chat_content, msg->content[i++]);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
wattroff(chat_content, A_BOLD);
|
|
||||||
in_bold = 0;
|
|
||||||
}
|
|
||||||
/* Skip */
|
|
||||||
i += 2;
|
|
||||||
|
|
||||||
/* Italic */
|
|
||||||
} else if (msg->content[i] == '*') {
|
|
||||||
if (!in_italic) {
|
|
||||||
/* Look ahead for the matching closing delimiter */
|
|
||||||
int closing_pos = i + 1;
|
|
||||||
while (closing_pos < n && msg->content[closing_pos] != '*') {
|
|
||||||
closing_pos++;
|
|
||||||
}
|
|
||||||
if (closing_pos < n) {
|
|
||||||
wattron(chat_content, A_ITALIC);
|
|
||||||
in_italic = 1;
|
|
||||||
} else {
|
|
||||||
/* Treat as regular text if closing delimiter */
|
|
||||||
waddch(chat_content, msg->content[i++]);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
wattroff(chat_content, A_ITALIC);
|
|
||||||
in_italic = 0;
|
|
||||||
}
|
|
||||||
/* Skip */
|
|
||||||
i += 1;
|
|
||||||
|
|
||||||
/* Underline */
|
|
||||||
} else if (msg->content[i] == '_') {
|
|
||||||
if (!in_underline) {
|
|
||||||
/* Look ahead for the matching closing delimiter */
|
|
||||||
int closing_pos = i + 1;
|
|
||||||
while (closing_pos < n && msg->content[closing_pos] != '_') {
|
|
||||||
closing_pos++;
|
|
||||||
}
|
|
||||||
if (closing_pos < n) {
|
|
||||||
wattron(chat_content, A_UNDERLINE);
|
|
||||||
in_underline = 1;
|
|
||||||
} else {
|
|
||||||
/* Treat as regular text if closing delimiter */
|
|
||||||
waddch(chat_content, msg->content[i++]);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
wattroff(chat_content, A_UNDERLINE);
|
|
||||||
in_underline = 0;
|
|
||||||
}
|
|
||||||
/* Skip */
|
|
||||||
i += 1;
|
|
||||||
|
|
||||||
/* Block */
|
|
||||||
} else if (msg->content[i] == '`') {
|
|
||||||
if (!in_block) {
|
|
||||||
/* Look ahead for the matching closing delimiter */
|
|
||||||
int closing_pos = i + 1;
|
|
||||||
while (closing_pos < n && msg->content[closing_pos] != '`') {
|
|
||||||
closing_pos++;
|
|
||||||
}
|
|
||||||
if (closing_pos < n) {
|
|
||||||
wattron(chat_content, A_STANDOUT);
|
|
||||||
in_block = 1;
|
|
||||||
} else {
|
|
||||||
/* Treat as regular text if closing delimiter */
|
|
||||||
waddch(chat_content, msg->content[i++]);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
wattroff(chat_content, A_STANDOUT);
|
|
||||||
in_block = 0;
|
|
||||||
}
|
|
||||||
/* Skip */
|
|
||||||
i += 1;
|
|
||||||
|
|
||||||
/* Allow escape sequence for genuine backslash */
|
|
||||||
} else if (msg->content[i] == '\\' && msg->content[i + 1] == '\\') {
|
|
||||||
/* Print a literal backslash */
|
|
||||||
waddch(chat_content, '\\');
|
|
||||||
/* Skip both backslashes */
|
|
||||||
i += 2;
|
|
||||||
|
|
||||||
/* Color, new line and tab */
|
|
||||||
} else if (msg->content[i] == '\\') {
|
|
||||||
/* Skip the backslash and check the next character */
|
|
||||||
i++;
|
|
||||||
/* Handle color codes \1 to \8 */
|
|
||||||
if (msg->content[i] >= '1' && msg->content[i] <= '8') {
|
|
||||||
/* Convert char to int */
|
|
||||||
int new_color = msg->content[i] - '0';
|
|
||||||
if (new_color == last_active_color) {
|
|
||||||
/* Turn off current color */
|
|
||||||
wattroff(chat_content, COLOR_PAIR(last_active_color));
|
|
||||||
/* Reset last active color */
|
|
||||||
last_active_color = -1;
|
|
||||||
} else {
|
|
||||||
if (last_active_color != -1) {
|
|
||||||
/* Turn off previous color */
|
|
||||||
wattroff(chat_content, COLOR_PAIR(last_active_color));
|
|
||||||
}
|
|
||||||
last_active_color = new_color;
|
|
||||||
/* Turn on new color */
|
|
||||||
wattron(chat_content, COLOR_PAIR(new_color));
|
|
||||||
}
|
|
||||||
i++;
|
|
||||||
/* Handle new line */
|
|
||||||
} else if (msg->content[i] == 'n') {
|
|
||||||
waddch(chat_content, '\n');
|
|
||||||
/* Skip the 'n' */
|
|
||||||
i++;
|
|
||||||
|
|
||||||
} else {
|
|
||||||
/* Invalid sequence, just print the backslash and character */
|
|
||||||
waddch(chat_content, '\\');
|
|
||||||
waddch(chat_content, msg->content[i]);
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
/* Print regular character */
|
|
||||||
waddch(chat_content, msg->content[i]);
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/* Ensure attributes are turned off after printing */
|
|
||||||
wattroff(chat_content, A_BOLD);
|
|
||||||
wattroff(chat_content, A_ITALIC);
|
|
||||||
wattroff(chat_content, A_UNDERLINE);
|
|
||||||
wattroff(chat_content, A_STANDOUT);
|
|
||||||
for (int i = 1; i < 8; i++) {
|
|
||||||
wattroff(chat_content, COLOR_PAIR(i));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Get chat conversation into buffer and show it to chat window
|
|
||||||
*/
|
|
||||||
void show_chat(uint8_t *recipient)
|
|
||||||
{
|
|
||||||
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 message to print is older than previous message by a day,
|
|
||||||
* enable flag in print_message to include date */
|
|
||||||
int print_date = 0;
|
|
||||||
if (i > 0 && messages[i - 1].content != NULL && message.creation >= messages[i - 1].creation + 86400) {
|
|
||||||
print_date = 1;
|
|
||||||
}
|
|
||||||
if (strncmp(message.author, USERNAME, MAX_NAME) == 0 &&
|
|
||||||
strncmp(message.recipient, recipient, MAX_NAME) == 0) {
|
|
||||||
print_message(print_date, &message, 1);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (strncmp(message.author, recipient, MAX_NAME) == 0 &&
|
|
||||||
strncmp(message.recipient, USERNAME, MAX_NAME) == 0) {
|
|
||||||
print_message(print_date, &message, 2);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
wrefresh(chat_content);
|
|
||||||
/* after printing move cursor back to textbox */
|
|
||||||
wmove(textbox, 0, curs_pos + 2);
|
|
||||||
wrefresh(textbox);
|
|
||||||
}
|
|
||||||
/*
|
|
||||||
* Require heap allocated username
|
|
||||||
*/
|
|
||||||
void add_username(char *username)
|
|
||||||
{
|
|
||||||
wchar_t *icon_str = memalloc(2 * sizeof(wchar_t));
|
|
||||||
wcsncpy(icon_str, L"", 2);
|
|
||||||
|
|
||||||
arraylist_add(users, username, icon_str, 7, false, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
void get_chatbox_content(int ch)
|
|
||||||
{
|
|
||||||
if (ch == KEY_BACKSPACE || ch == 127) {
|
|
||||||
if (curs_pos > 0) {
|
|
||||||
curs_pos--;
|
|
||||||
content[curs_pos] = '\0';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/* Input done */
|
|
||||||
else if (ch == '\n') {
|
|
||||||
content[curs_pos++] = ch;
|
|
||||||
content[curs_pos++] = '\0';
|
|
||||||
send_message();
|
|
||||||
/* Reset for new input */
|
|
||||||
curs_pos = 0;
|
|
||||||
|
|
||||||
/* Set content[0] for printing purposes */
|
|
||||||
content[0] = '\0';
|
|
||||||
}
|
|
||||||
/* Append it to the content if it is normal character */
|
|
||||||
else if (curs_pos < MAX_MESSAGE_LENGTH - 1) {
|
|
||||||
/* Filter readable ASCII */
|
|
||||||
if (ch > 31 && ch < 127) {
|
|
||||||
content[curs_pos++] = ch;
|
|
||||||
content[curs_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;
|
|
||||||
|
|
||||||
keypair_t *kp_from = get_keypair(USERNAME);
|
|
||||||
keypair_t *kp_to = get_keypair(recipient);
|
|
||||||
|
|
||||||
int status = ZSM_STA_SUCCESS;
|
|
||||||
|
|
||||||
uint8_t shared_key[SHARED_KEY_SIZE];
|
|
||||||
if (crypto_kx_client_session_keys(shared_key, NULL, kp_from->pk.raw,
|
|
||||||
kp_from->sk.raw, kp_to->pk.raw) != 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);
|
free_packet(pkt);
|
||||||
show_chat(recipient);
|
return (status == ZSM_STA_AUTHORISED ? ZSM_STA_SUCCESS : ZSM_STA_ERROR_AUTHENTICATE);
|
||||||
}
|
|
||||||
|
|
||||||
void ncurses_deinit()
|
|
||||||
{
|
|
||||||
arraylist_free(users);
|
|
||||||
arraylist_free(marked);
|
|
||||||
endwin();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Main loop of user interface
|
* Starting ui
|
||||||
*/
|
*/
|
||||||
int main(int argc, char **argv)
|
void *ui_worker(void *arg)
|
||||||
{
|
{
|
||||||
signal(SIGPIPE, signal_handler);
|
int *sockfd = (int *) arg;
|
||||||
signal(SIGABRT, signal_handler);
|
ui(sockfd);
|
||||||
signal(SIGINT, signal_handler);
|
return NULL;
|
||||||
signal(SIGTERM, signal_handler);
|
}
|
||||||
connect_server();
|
|
||||||
ncurses_init();
|
/*
|
||||||
windows_init();
|
* For receiving packets from server
|
||||||
users = arraylist_init(LINES);
|
*/
|
||||||
marked = arraylist_init(100);
|
void *receive_worker(void *arg)
|
||||||
show_icons = true;
|
{
|
||||||
sqlite_init();
|
int *sockfd = (int *) arg;
|
||||||
highlight_current_line();
|
|
||||||
refresh();
|
|
||||||
int ch;
|
|
||||||
while (1) {
|
while (1) {
|
||||||
if (current_window == CHAT_WINDOW) {
|
packet_t pkt;
|
||||||
wclear(textbox);
|
int status = verify_packet(&pkt, *sockfd);
|
||||||
mvwprintw(textbox, 0, 0, "> %s", content);
|
if (status != ZSM_STA_SUCCESS) {
|
||||||
wrefresh(textbox);
|
if (status == ZSM_STA_CLOSED_CONNECTION) {
|
||||||
wmove(textbox, 0, curs_pos + 2);
|
deinit();
|
||||||
/* Set cursor to visible */
|
error(1, "Server closed connection");
|
||||||
curs_set(2);
|
}
|
||||||
} else {
|
error(0, "Error verifying packet");
|
||||||
curs_set(0);
|
}
|
||||||
|
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);
|
||||||
|
|
||||||
|
keypair_t *kp_from = get_keypair(from);
|
||||||
|
keypair_t *kp_to = get_keypair(to);
|
||||||
|
|
||||||
|
uint8_t shared_key[SHARED_KEY_SIZE];
|
||||||
|
if (crypto_kx_client_session_keys(shared_key, NULL, kp_from->pk.raw,
|
||||||
|
kp_from->sk, kp_to->pk.raw) != 0) {
|
||||||
|
/* Suspicious server public key, bail out */
|
||||||
|
write_log(LOG_ERROR, "Error performing key exchange with %s\n", from);
|
||||||
}
|
}
|
||||||
ch = getch();
|
|
||||||
switch (ch) {
|
|
||||||
/* go up by k or up arrow */
|
|
||||||
case UP:
|
|
||||||
if (current_window == USERS_WINDOW) {
|
|
||||||
if (current_selection > 0)
|
|
||||||
current_selection--;
|
|
||||||
|
|
||||||
highlight_current_line();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
/* go down by j or down arrow */
|
|
||||||
case DOWN:
|
|
||||||
if (current_window == USERS_WINDOW) {
|
|
||||||
if (current_selection < (users->length - 1))
|
|
||||||
current_selection++;
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case CLEAR_INPUT:
|
|
||||||
if (current_window == CHAT_WINDOW) {
|
|
||||||
curs_pos = 0;
|
|
||||||
content[0] = '\0';
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
if (current_window == CHAT_WINDOW)
|
|
||||||
get_chatbox_content(ch);
|
|
||||||
|
|
||||||
|
/* 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) {
|
||||||
|
write_log(LOG_ERROR, "Unable to decrypt data from %s\n", from);
|
||||||
|
} else {
|
||||||
|
/* Terminate decrypted data so we don't print random bytes */
|
||||||
|
decrypted[data_len] = '\0';
|
||||||
|
/* TODO: Use mutext before add messgae */
|
||||||
|
add_message(from, to, decrypted, data_len, time(NULL));
|
||||||
|
show_chat(from);
|
||||||
|
send_notification(from, decrypted);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ncurses_deinit();
|
|
||||||
return 0;
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
if (sodium_init() < 0) {
|
||||||
|
write_log(LOG_ERROR, "Error initializing libsodium\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Init libnotify with app name */
|
||||||
|
if (notify_init("zen") < 0) {
|
||||||
|
error(1, "Error initializing libnotify");
|
||||||
|
}
|
||||||
|
|
||||||
|
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
|
||||||
|
if (sockfd < 0) {
|
||||||
|
error(1, "Error on opening socket");
|
||||||
|
}
|
||||||
|
|
||||||
|
struct hostent *server = gethostbyname(DOMAIN);
|
||||||
|
if (server == NULL) {
|
||||||
|
error(1, "No such host %s", DOMAIN);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct sockaddr_in server_addr;
|
||||||
|
memset(&server_addr, 0, sizeof(server_addr));
|
||||||
|
server_addr.sin_family = AF_INET;
|
||||||
|
server_addr.sin_port = htons(PORT);
|
||||||
|
memcpy(&server_addr.sin_addr.s_addr, server->h_addr, server->h_length);
|
||||||
|
|
||||||
|
/* free(server); Can't be freed seems */
|
||||||
|
if (connect(sockfd, (struct sockaddr *) &server_addr, sizeof(server_addr))
|
||||||
|
< 0) {
|
||||||
|
error(1, "Error on connect");
|
||||||
|
close(sockfd);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
write_log(LOG_INFO, "Connected to server at %s\n", DOMAIN);
|
||||||
|
if (authenticate_server(&sockfd) != ZSM_STA_SUCCESS) {
|
||||||
|
/* Fatal */
|
||||||
|
error(1, "Error authenticating with server");
|
||||||
|
} else {
|
||||||
|
write_log(LOG_INFO, "Authenticated to server as %s\n", USERNAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Create threads for sending and receiving messages */
|
||||||
|
pthread_t ui_thread, receive_thread;
|
||||||
|
|
||||||
|
if (pthread_create(&ui_thread, NULL, ui_worker, &sockfd) != 0) {
|
||||||
|
close(sockfd);
|
||||||
|
error(1, "Failed to create send thread");
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pthread_create(&receive_thread, NULL, receive_worker, &sockfd) != 0) {
|
||||||
|
close(sockfd);
|
||||||
|
error(1, "Failed to create receive thread");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Wait for threads to finish */
|
||||||
|
pthread_join(ui_thread, NULL);
|
||||||
|
pthread_join(receive_thread, NULL);
|
||||||
|
|
||||||
|
close(sockfd);
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
192
src/zen/zmd.c
192
src/zen/zmd.c
|
@ -1,192 +0,0 @@
|
||||||
#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"
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Authenticate with server by signing a challenge
|
|
||||||
*/
|
|
||||||
int authenticate_server(keypair_t *kp)
|
|
||||||
{
|
|
||||||
/* create empty packet */
|
|
||||||
packet_t *pkt = create_packet(0, 0, 0, NULL, NULL);
|
|
||||||
int status;
|
|
||||||
if ((status = recv_packet(pkt, sockfd, ZSM_TYP_AUTH) != ZSM_STA_SUCCESS)) {
|
|
||||||
return status;
|
|
||||||
}
|
|
||||||
uint8_t *challenge = pkt->data;
|
|
||||||
|
|
||||||
uint8_t *sig = memalloc(SIGN_SIZE);
|
|
||||||
crypto_sign_detached(sig, NULL, challenge, CHALLENGE_SIZE, kp->sk.raw);
|
|
||||||
|
|
||||||
uint8_t *pk_full = memalloc(PK_SIZE);
|
|
||||||
memcpy(pk_full, kp->pk.full, PK_SIZE);
|
|
||||||
|
|
||||||
pkt->status = 1;
|
|
||||||
pkt->type = ZSM_TYP_AUTH;
|
|
||||||
pkt->length = SIGN_SIZE;
|
|
||||||
pkt->data = pk_full;
|
|
||||||
pkt->signature = sig;
|
|
||||||
if ((status = send_packet(pkt, sockfd)) != ZSM_STA_SUCCESS) {
|
|
||||||
/* fd already closed */
|
|
||||||
error(0, "Could not authenticate with server, status: %d", status);
|
|
||||||
free_packet(pkt);
|
|
||||||
return ZSM_STA_ERROR_AUTHENTICATE;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((status = recv_packet(pkt, sockfd, ZSM_TYP_INFO)) != ZSM_STA_SUCCESS) {
|
|
||||||
return status;
|
|
||||||
};
|
|
||||||
status = pkt->status;
|
|
||||||
free_packet(pkt);
|
|
||||||
return (status == ZSM_STA_AUTHORISED ? ZSM_STA_SUCCESS : ZSM_STA_ERROR_AUTHENTICATE);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Starting ui
|
|
||||||
*/
|
|
||||||
void *ui_worker(void *arg)
|
|
||||||
{
|
|
||||||
ui(sockfd);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* For receiving packets from server
|
|
||||||
*/
|
|
||||||
void *receive_worker(void *arg)
|
|
||||||
{
|
|
||||||
keypair_t *kp = (keypair_t *) arg;
|
|
||||||
|
|
||||||
while (1) {
|
|
||||||
packet_t pkt;
|
|
||||||
int status = verify_packet(&pkt, sockfd);
|
|
||||||
if (status != ZSM_STA_SUCCESS) {
|
|
||||||
if (status == ZSM_STA_CLOSED_CONNECTION) {
|
|
||||||
ncurses_deinit();
|
|
||||||
error(1, "Server closed connection");
|
|
||||||
}
|
|
||||||
error(0, "Error verifying packet");
|
|
||||||
}
|
|
||||||
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);
|
|
||||||
|
|
||||||
keypair_t *kp_from = get_keypair(from);
|
|
||||||
keypair_t *kp_to = get_keypair(to);
|
|
||||||
|
|
||||||
uint8_t shared_key[SHARED_KEY_SIZE];
|
|
||||||
if (crypto_kx_client_session_keys(shared_key, NULL, kp_from->pk.raw,
|
|
||||||
kp_from->sk.raw, kp_to->pk.raw) != 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");
|
|
||||||
} else {
|
|
||||||
/* Terminate decrypted data so we don't print random bytes */
|
|
||||||
decrypted[data_len] = '\0';
|
|
||||||
/* TODO: Use mutext before add messgae */
|
|
||||||
add_message(from, to, decrypted, data_len, time(NULL));
|
|
||||||
show_chat(from);
|
|
||||||
send_notification(from, decrypted);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
int main()
|
|
||||||
{
|
|
||||||
if (sodium_init() < 0) {
|
|
||||||
write_log(LOG_ERROR, "Error initializing libsodium\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Init libnotify with app name */
|
|
||||||
if (notify_init("zen") < 0) {
|
|
||||||
error(1, "Error initializing libnotify");
|
|
||||||
}
|
|
||||||
|
|
||||||
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
|
|
||||||
if (sockfd < 0) {
|
|
||||||
error(1, "Error on opening socket");
|
|
||||||
}
|
|
||||||
|
|
||||||
struct hostent *server = gethostbyname(DOMAIN);
|
|
||||||
if (server == NULL) {
|
|
||||||
error(1, "No such host %s", DOMAIN);
|
|
||||||
}
|
|
||||||
|
|
||||||
struct sockaddr_in server_addr;
|
|
||||||
memset(&server_addr, 0, sizeof(server_addr));
|
|
||||||
server_addr.sin_family = AF_INET;
|
|
||||||
server_addr.sin_port = htons(PORT);
|
|
||||||
memcpy(&server_addr.sin_addr.s_addr, server->h_addr, server->h_length);
|
|
||||||
|
|
||||||
/* free(server); Can't be freed seems */
|
|
||||||
if (connect(sockfd, (struct sockaddr *) &server_addr, sizeof(server_addr))
|
|
||||||
< 0) {
|
|
||||||
error(1, "Error on connect");
|
|
||||||
close(sockfd);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
write_log(LOG_INFO, "Connected to server at %s\n", DOMAIN);
|
|
||||||
|
|
||||||
/*
|
|
||||||
keypair_t *kpp = create_keypair("palanix");
|
|
||||||
keypair_t *kpn = create_keypair("night");
|
|
||||||
*/
|
|
||||||
keypair_t *kp = get_keypair(USERNAME);
|
|
||||||
|
|
||||||
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 %s\n", USERNAME);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Create threads for sending and receiving messages */
|
|
||||||
pthread_t ui_thread, receive_thread;
|
|
||||||
|
|
||||||
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_worker, kp) != 0) {
|
|
||||||
close(sockfd);
|
|
||||||
error(1, "Failed to create receive thread");
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Wait for threads to finish */
|
|
||||||
pthread_join(ui_thread, NULL);
|
|
||||||
pthread_join(receive_thread, NULL);
|
|
||||||
|
|
||||||
close(sockfd);
|
|
||||||
return 0;
|
|
||||||
}
|
|
|
@ -32,8 +32,11 @@ int init_db()
|
||||||
return SQLITE_OK;
|
return SQLITE_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Insert a public key with the given username into the database as a BLOB
|
/*
|
||||||
int insert_publickey(const char *username, const unsigned char *pubkey, size_t pubkey_len) {
|
* Insert a public key with the given username into the database as a BLOB
|
||||||
|
*/
|
||||||
|
int insert_publickey(const char *username, const unsigned char *pubkey, size_t pubkey_len)
|
||||||
|
{
|
||||||
sqlite3_stmt *stmt;
|
sqlite3_stmt *stmt;
|
||||||
const char *sql = "INSERT OR REPLACE INTO keys(username, publickey) VALUES(?, ?)";
|
const char *sql = "INSERT OR REPLACE INTO keys(username, publickey) VALUES(?, ?)";
|
||||||
|
|
||||||
|
@ -51,8 +54,9 @@ int insert_publickey(const char *username, const unsigned char *pubkey, size_t p
|
||||||
return rc == SQLITE_DONE ? 0 : rc;
|
return rc == SQLITE_DONE ? 0 : rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Retrieve a public key from the database by username
|
/* Retrieve a public key from the database by username */
|
||||||
unsigned char *retrieve_publickey(const char *username, size_t *pubkey_len) {
|
unsigned char *retrieve_publickey(const char *username, size_t *pubkey_len)
|
||||||
|
{
|
||||||
sqlite3_stmt *stmt;
|
sqlite3_stmt *stmt;
|
||||||
const char *sql = "SELECT publickey FROM keys WHERE username = ?";
|
const char *sql = "SELECT publickey FROM keys WHERE username = ?";
|
||||||
unsigned char *publickey = NULL;
|
unsigned char *publickey = NULL;
|
||||||
|
@ -77,16 +81,17 @@ unsigned char *retrieve_publickey(const char *username, size_t *pubkey_len) {
|
||||||
return publickey;
|
return publickey;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle REQUEST command: generate a new key pair and store the public key with username
|
/* Handle REQUEST command: generate a new key pair and store the public key with username */
|
||||||
void handle_request(int clientfd, const char *username) {
|
void handle_request(int clientfd, const char *username)
|
||||||
|
{
|
||||||
unsigned char pk[crypto_box_PUBLICKEYBYTES];
|
unsigned char pk[crypto_box_PUBLICKEYBYTES];
|
||||||
unsigned char sk[crypto_box_SECRETKEYBYTES];
|
unsigned char sk[crypto_box_SECRETKEYBYTES];
|
||||||
|
|
||||||
crypto_box_keypair(pk, sk); // Generate key pair
|
crypto_box_keypair(pk, sk); /* Generate key pair */
|
||||||
|
|
||||||
// Store the public key with the username in the database
|
/* Store the public key with the username in the database */
|
||||||
if (insert_publickey(username, pk, crypto_box_PUBLICKEYBYTES) == 0) {
|
if (insert_publickey(username, pk, crypto_box_PUBLICKEYBYTES) == 0) {
|
||||||
// Convert the public key to hexadecimal string for client display
|
/* Convert the public key to hexadecimal string for client display */
|
||||||
char publickey_hex[crypto_box_PUBLICKEYBYTES * 2 + 1];
|
char publickey_hex[crypto_box_PUBLICKEYBYTES * 2 + 1];
|
||||||
for (int i = 0; i < crypto_box_PUBLICKEYBYTES; i++) {
|
for (int i = 0; i < crypto_box_PUBLICKEYBYTES; i++) {
|
||||||
sprintf(publickey_hex + i * 2, "%02x", pk[i]);
|
sprintf(publickey_hex + i * 2, "%02x", pk[i]);
|
||||||
|
@ -98,13 +103,14 @@ void handle_request(int clientfd, const char *username) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle RETRIEVE command: get public key by username
|
/* Handle RETRIEVE command: get public key by username */
|
||||||
void handle_retrieve(int clientfd, const char *username) {
|
void handle_retrieve(int clientfd, const char *username)
|
||||||
|
{
|
||||||
size_t publickey_len;
|
size_t publickey_len;
|
||||||
unsigned char *publickey = retrieve_pubkey(username, &pubkey_len);
|
unsigned char *publickey = retrieve_pubkey(username, &pubkey_len);
|
||||||
|
|
||||||
if (publickey) {
|
if (publickey) {
|
||||||
// Convert the public key (BLOB) to hexadecimal string for client display
|
/* Convert the public key (BLOB) to hexadecimal string for client display */
|
||||||
char publickey_hex[pubkey_len * 2 + 1];
|
char publickey_hex[pubkey_len * 2 + 1];
|
||||||
for (size_t i = 0; i < publickey_len; i++) {
|
for (size_t i = 0; i < publickey_len; i++) {
|
||||||
sprintf(publickey_hex + i * 2, "%02x", pubkey[i]);
|
sprintf(publickey_hex + i * 2, "%02x", pubkey[i]);
|
||||||
|
@ -117,7 +123,8 @@ void handle_retrieve(int clientfd, const char *username) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void handle_client(int clientfd) {
|
void handle_client(int clientfd)
|
||||||
|
{
|
||||||
char buffer[BUFSIZE];
|
char buffer[BUFSIZE];
|
||||||
ssize_t bytes_received;
|
ssize_t bytes_received;
|
||||||
|
|
||||||
|
@ -126,11 +133,11 @@ void handle_client(int clientfd) {
|
||||||
|
|
||||||
if (strncmp(buffer, "REQUEST", 7) == 0) {
|
if (strncmp(buffer, "REQUEST", 7) == 0) {
|
||||||
char username[BUFSIZE];
|
char username[BUFSIZE];
|
||||||
sscanf(buffer + 8, "%s", username); // Get the username from the command
|
sscanf(buffer + 8, "%s", username); /* Get the username from the command */
|
||||||
handle_request(clientfd, username);
|
handle_request(clientfd, username);
|
||||||
} else if (strncmp(buffer, "RETRIEVE", 8) == 0) {
|
} else if (strncmp(buffer, "RETRIEVE", 8) == 0) {
|
||||||
char username[BUFSIZE];
|
char username[BUFSIZE];
|
||||||
sscanf(buffer + 9, "%s", username); // Get the username from the command
|
sscanf(buffer + 9, "%s", username); /* Get the username from the command */
|
||||||
handle_retrieve(clientfd, username);
|
handle_retrieve(clientfd, username);
|
||||||
} else if (strncmp(buffer, "EXIT", 4) == 0) {
|
} else if (strncmp(buffer, "EXIT", 4) == 0) {
|
||||||
send(clientfd, "BYE\n", 4, 0);
|
send(clientfd, "BYE\n", 4, 0);
|
||||||
|
@ -143,7 +150,8 @@ void handle_client(int clientfd) {
|
||||||
close(clientfd);
|
close(clientfd);
|
||||||
}
|
}
|
||||||
|
|
||||||
int main() {
|
int main()
|
||||||
|
{
|
||||||
if (sodium_init() < 0) {
|
if (sodium_init() < 0) {
|
||||||
fprintf(stderr, "Failed to initialize sodium\n");
|
fprintf(stderr, "Failed to initialize sodium\n");
|
||||||
return -1;
|
return -1;
|
||||||
|
|
|
@ -4,9 +4,9 @@
|
||||||
#include "notification.h"
|
#include "notification.h"
|
||||||
#include "server/server.h"
|
#include "server/server.h"
|
||||||
|
|
||||||
int debug;
|
|
||||||
thread_t threads[MAX_THREADS];
|
thread_t threads[MAX_THREADS];
|
||||||
int num_thread = 0;
|
int num_thread = 0;
|
||||||
|
int debug = 0;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Authenticate client before starting communication
|
* Authenticate client before starting communication
|
||||||
|
@ -20,8 +20,9 @@ int authenticate_client(int clientfd, uint8_t *username)
|
||||||
/* Sending fake signature as structure requires it */
|
/* Sending fake signature as structure requires it */
|
||||||
uint8_t *fake_sig = create_signature(NULL, 0, NULL);
|
uint8_t *fake_sig = create_signature(NULL, 0, NULL);
|
||||||
|
|
||||||
packet_t *pkt = create_packet(1, ZSM_TYP_AUTH, CHALLENGE_SIZE,
|
packet_t *pkt = create_packet(1, ZSM_TYP_AUTH, CHALLENGE_SIZE, challenge,
|
||||||
challenge, fake_sig);
|
fake_sig);
|
||||||
|
|
||||||
if (send_packet(pkt, clientfd) != ZSM_STA_SUCCESS) {
|
if (send_packet(pkt, clientfd) != ZSM_STA_SUCCESS) {
|
||||||
error(0, "Could not authenticate client");
|
error(0, "Could not authenticate client");
|
||||||
goto failure;
|
goto failure;
|
||||||
|
@ -39,7 +40,8 @@ int authenticate_client(int clientfd, uint8_t *username)
|
||||||
memcpy(pk_bin, pkt->data, PK_RAW_SIZE);
|
memcpy(pk_bin, pkt->data, PK_RAW_SIZE);
|
||||||
memcpy(pk_username, pkt->data + PK_RAW_SIZE, MAX_NAME);
|
memcpy(pk_username, pkt->data + PK_RAW_SIZE, MAX_NAME);
|
||||||
|
|
||||||
if (crypto_sign_verify_detached(pkt->signature, challenge, CHALLENGE_SIZE, pk_bin) != 0) {
|
if (crypto_sign_verify_detached(pkt->signature, challenge, CHALLENGE_SIZE,
|
||||||
|
pk_bin) != 0) {
|
||||||
free_packet(pkt);
|
free_packet(pkt);
|
||||||
error(0, "Incorrect signature, could not authenticate client");
|
error(0, "Incorrect signature, could not authenticate client");
|
||||||
goto failure;
|
goto failure;
|
||||||
|
@ -115,6 +117,7 @@ void *thread_worker(void *arg)
|
||||||
/* TODO: Mutex lock when handle packet */
|
/* TODO: Mutex lock when handle packet */
|
||||||
packet_t pkt;
|
packet_t pkt;
|
||||||
int status = verify_packet(&pkt, client->fd);
|
int status = verify_packet(&pkt, client->fd);
|
||||||
|
if (debug) print_packet(&pkt);
|
||||||
if (status != ZSM_STA_SUCCESS) {
|
if (status != ZSM_STA_SUCCESS) {
|
||||||
if (status == ZSM_STA_CLOSED_CONNECTION) {
|
if (status == ZSM_STA_CLOSED_CONNECTION) {
|
||||||
/* TODO: Remove client from thread, epollctldel, close fd */
|
/* TODO: Remove client from thread, epollctldel, close fd */
|
||||||
|
@ -257,6 +260,7 @@ int main(int argc, char **argv)
|
||||||
/* Assign fd to client in a thread */
|
/* Assign fd to client in a thread */
|
||||||
client->fd = clientfd;
|
client->fd = clientfd;
|
||||||
strcpy(client->username, username);
|
strcpy(client->username, username);
|
||||||
|
printf("%s connected\n", username);
|
||||||
thread->num_clients++;
|
thread->num_clients++;
|
||||||
|
|
||||||
/* Rotate num_thread back to start if it is larder than MAX_THREADS */
|
/* Rotate num_thread back to start if it is larder than MAX_THREADS */
|
Loading…
Reference in a new issue