From 91bdd23a726e243015e4e843c1f261d9ed0881df Mon Sep 17 00:00:00 2001 From: Florian Stinglmayr Date: Mon, 8 Jul 2019 23:29:08 +0200 Subject: [PATCH] web sockets are hard --- libdc/CMakeLists.txt | 4 + libdc/include/dc/api.h | 13 ++ libdc/include/dc/gateway.h | 56 +++++++ libdc/include/dc/loop.h | 6 + libdc/src/api-auth.c | 23 +++ libdc/src/api-user.c | 93 ++++++++++++ libdc/src/api.c | 150 +++++++++---------- libdc/src/gateway.c | 295 +++++++++++++++++++++++++++++++++++++ libdc/src/loop.c | 37 ++++- libdc/src/masking.c | 63 ++++++++ ncdc/include/ncdc/ncdc.h | 1 + ncdc/src/login.c | 15 +- 12 files changed, 670 insertions(+), 86 deletions(-) create mode 100644 libdc/include/dc/gateway.h create mode 100644 libdc/src/api-user.c create mode 100644 libdc/src/gateway.c create mode 100644 libdc/src/masking.c diff --git a/libdc/CMakeLists.txt b/libdc/CMakeLists.txt index aff9c81..b1dd184 100644 --- a/libdc/CMakeLists.txt +++ b/libdc/CMakeLists.txt @@ -12,6 +12,7 @@ SET(SOURCES "include/dc/api.h" "include/dc/apisync.h" "include/dc/channel.h" + "include/dc/gateway.h" "include/dc/guild.h" "include/dc/loop.h" "include/dc/message.h" @@ -22,10 +23,13 @@ SET(SOURCES "src/api-auth.c" "src/api-channel.c" "src/api-friends.c" + "src/api-user.c" "src/apisync.c" "src/channel.c" + "src/gateway.c" "src/guild.c" "src/loop.c" + "src/masking.c" "src/message.c" "src/refable.c" "src/util.c" diff --git a/libdc/include/dc/api.h b/libdc/include/dc/api.h index e2c7e21..b9d9518 100644 --- a/libdc/include/dc/api.h +++ b/libdc/include/dc/api.h @@ -5,6 +5,7 @@ #include #include #include +#include #include @@ -35,6 +36,11 @@ json_t *dc_api_call_sync(dc_api_t api, char const *token, char const *verb, char const *method, json_t *j); +/** + * Establish a GATEWAY to the discord servers. + */ +dc_gateway_t dc_api_establish_gateway(dc_api_t api, dc_account_t login); + /** * Authenticate a given user account. The user account should have * email, and password set. If the auth succeeds the account will have @@ -44,6 +50,13 @@ json_t *dc_api_call_sync(dc_api_t api, char const *token, */ bool dc_api_authenticate(dc_api_t api, dc_account_t account); +/** + * Log the account in. Will first call dc_api_authenticate(), then + * dc_api_get_userinfo(), then dc_api_get_friends(). If + * any of these steps fail, it returns false. + */ +bool dc_api_login(dc_api_t api, dc_account_t account); + /** * Inverse of dc_api_authenticate(). Logs the given user out, destroying the * login token in the process. diff --git a/libdc/include/dc/gateway.h b/libdc/include/dc/gateway.h new file mode 100644 index 0000000..b6ad9b9 --- /dev/null +++ b/libdc/include/dc/gateway.h @@ -0,0 +1,56 @@ +#ifndef DC_GATEWAY_H +#define DC_GATEWAY_H + +#include +#include + +#include + +#include + +struct dc_gateway_; +typedef struct dc_gateway_ *dc_gateway_t; + +typedef enum { + GATEWAY_OPCODE_HEARTBEAT = 1, + GATEWAY_OPCODE_IDENTIFY = 2, + GATEWAY_OPCODE_HELLO = 10, +} dc_gateway_opcode_t; + +dc_gateway_t dc_gateway_new(void); + +void dc_gateway_set_login(dc_gateway_t gw, dc_account_t login); + +/** + * Set all required CURL handles. The object will delete the easy handle + * and make sure it is removed from the multi handle upon unref. Do not + * free the multi handle before you remove the gateway. + */ +void dc_gateway_set_curl(dc_gateway_t gw, CURLM *multi, CURL *easy); + +CURL *dc_gateway_curl(dc_gateway_t gw); + +/** + * Returns a CURL slist that lasts as long as the handle itself lasts + */ +struct curl_slist * dc_gateway_slist(dc_gateway_t gw); + +/** + * To be used as a WRITEFUNCTION for a curl handle. Don't forget to set the + * gateway handle as a WRITEDATA too, otherwise this will have no effect. + */ +size_t dc_gateway_writefunc(char *ptr, size_t sz, size_t nmemb, void *data); + +/** + * Process the queue of data that came from the websocket. + */ +void dc_gateway_process(dc_gateway_t gw); + +/** + * utility function to make a websocket frame + */ +uint8_t * +dc_gateway_makeframe(uint8_t const *d, size_t data_len, + uint8_t type, size_t *outlen); + +#endif diff --git a/libdc/include/dc/loop.h b/libdc/include/dc/loop.h index 51fc876..ab4561c 100644 --- a/libdc/include/dc/loop.h +++ b/libdc/include/dc/loop.h @@ -2,6 +2,7 @@ #define DC_LOOP_H #include +#include #include #include @@ -39,6 +40,11 @@ struct event_base *dc_loop_event_base(dc_loop_t l); */ void dc_loop_add_api(dc_loop_t loop, dc_api_t api); +/** + * Add a gateway to be handled with the rest. + */ +void dc_loop_add_gateway(dc_loop_t loop, dc_gateway_t gw); + /** * Loop once, and process one message in the queues of the event * base, and one message from the queue of the CURL multi events. diff --git a/libdc/src/api-auth.c b/libdc/src/api-auth.c index 084d63a..9fa6834 100644 --- a/libdc/src/api-auth.c +++ b/libdc/src/api-auth.c @@ -28,6 +28,29 @@ bool dc_api_logout(dc_api_t api, dc_account_t account) return true; } +bool dc_api_login(dc_api_t api, dc_account_t account) +{ + if (!dc_api_authenticate(api, account)) { + goto cleanup; + } + + if (!dc_api_get_userinfo(api, account, account)) { + goto cleanup; + } + + if (!dc_api_get_friends(api, account)) { + goto cleanup; + } + + return true; + +cleanup: + + dc_account_set_token(account, NULL); + + return false; +} + bool dc_api_authenticate(dc_api_t api, dc_account_t account) { json_t *j = json_object(), *reply = NULL, *token = NULL; diff --git a/libdc/src/api-user.c b/libdc/src/api-user.c new file mode 100644 index 0000000..4c6fa76 --- /dev/null +++ b/libdc/src/api-user.c @@ -0,0 +1,93 @@ +#include +#include "internal.h" + +bool dc_api_get_userinfo(dc_api_t api, dc_account_t login, + dc_account_t user) +{ + char *url = NULL; + json_t *reply = NULL, *val = NULL; + bool ret = false; + + return_if_true(api == NULL, false); + return_if_true(login == NULL, false); + return_if_true(user == NULL, false); + + if (user == login) { + url = strdup("users/@me"); + } else { + asprintf(&url, "users/%s", dc_account_id(user)); + } + + reply = dc_api_call_sync(api, "GET", dc_account_token(login), url, NULL); + goto_if_true(reply == NULL, cleanup); + + val = json_object_get(reply, "username"); + goto_if_true(val == NULL || !json_is_string(val), cleanup); + dc_account_set_username(user, json_string_value(val)); + + val = json_object_get(reply, "discriminator"); + goto_if_true(val == NULL || !json_is_string(val), cleanup); + dc_account_set_discriminator(user, json_string_value(val)); + + val = json_object_get(reply, "id"); + goto_if_true(val == NULL || !json_is_string(val), cleanup); + dc_account_set_id(user, json_string_value(val)); + + ret = true; + +cleanup: + + free(url); + json_decref(reply); + + return ret; +} + +bool dc_api_get_userguilds(dc_api_t api, dc_account_t login, GPtrArray **out) +{ + char const *url = "users/@me/guilds"; + json_t *reply = NULL, *c = NULL, *val = NULL; + size_t i = 0; + bool ret = false; + GPtrArray *guilds = g_ptr_array_new_with_free_func( + (GDestroyNotify)dc_unref + ); + + return_if_true(api == NULL, false); + return_if_true(login == NULL, false); + + reply = dc_api_call_sync(api, "GET", dc_account_token(login), url, NULL); + goto_if_true(reply == NULL, cleanup); + + goto_if_true(!json_is_array(reply), cleanup); + + json_array_foreach(reply, i, c) { + dc_guild_t g = dc_guild_new(); + + val = json_object_get(c, "id"); + goto_if_true(val == NULL || !json_is_string(val), cleanup); + dc_guild_set_id(g, json_string_value(val)); + + val = json_object_get(c, "name"); + goto_if_true(val == NULL || !json_is_string(val), cleanup); + dc_guild_set_name(g, json_string_value(val)); + + g_ptr_array_add(guilds, g); + } + + *out = guilds; + guilds = NULL; + + ret = true; + +cleanup: + + json_decref(reply); + + if (guilds) { + g_ptr_array_unref(guilds); + guilds = NULL; + } + + return ret; +} diff --git a/libdc/src/api.c b/libdc/src/api.c index ac2e7f5..6f7dbad 100644 --- a/libdc/src/api.c +++ b/libdc/src/api.c @@ -2,7 +2,8 @@ #include #include "internal.h" -#define DISCORD_URL "https://discordapp.com/api/v6" +#define DISCORD_URL "https://discordapp.com/api/v6" +#define DISCORD_GATEWAY "https://gateway.discord.gg/?encoding=json&v=6" #define DISCORD_USERAGENT "Mozilla/5.0 (X11; Linux x86_64; rv:67.0) Gecko/20100101 Firefox/67.0" @@ -276,93 +277,88 @@ bool dc_api_error(json_t *j, int *code, char const **message) return error; } -bool dc_api_get_userinfo(dc_api_t api, dc_account_t login, - dc_account_t user) +static size_t stall_connection(char *buffer, size_t size, size_t nitems, + void *userdata) { - char *url = NULL; - json_t *reply = NULL, *val = NULL; - bool ret = false; + CURL *easy = (CURL *)userdata; - return_if_true(api == NULL, false); - return_if_true(login == NULL, false); - return_if_true(user == NULL, false); - - if (user == login) { - url = strdup("users/@me"); - } else { - asprintf(&url, "users/%s", dc_account_id(user)); + if (strncmp(buffer, "\r\n", size) == 0) { + curl_easy_setopt(easy, CURLOPT_CONNECT_ONLY, 1); + //curl_easy_pause(easy, CURLPAUSE_ALL); + curl_easy_setopt(easy, CURLOPT_FORBID_REUSE, 1); } - reply = dc_api_call_sync(api, "GET", dc_account_token(login), url, NULL); - goto_if_true(reply == NULL, cleanup); + return size * nitems; +} - val = json_object_get(reply, "username"); - goto_if_true(val == NULL || !json_is_string(val), cleanup); - dc_account_set_username(user, json_string_value(val)); +dc_gateway_t dc_api_establish_gateway(dc_api_t api, dc_account_t login) +{ + return_if_true(api == NULL, NULL); + return_if_true(api->curl == NULL, NULL); + return_if_true(login == NULL || !dc_account_has_token(login), NULL); - val = json_object_get(reply, "discriminator"); - goto_if_true(val == NULL || !json_is_string(val), cleanup); - dc_account_set_discriminator(user, json_string_value(val)); + CURL *c = NULL; + struct curl_slist *list = NULL; + /* BE THE BROKEN OR THE BREAKER + */ + dc_gateway_t gw = NULL; + dc_gateway_t ret = NULL; - val = json_object_get(reply, "id"); - goto_if_true(val == NULL || !json_is_string(val), cleanup); - dc_account_set_id(user, json_string_value(val)); + c = curl_easy_init(); + goto_if_true(c == NULL, cleanup); - ret = true; + gw = dc_gateway_new(); + goto_if_true(gw == NULL, cleanup); + + curl_easy_setopt(c, CURLOPT_URL, DISCORD_GATEWAY); + + list = dc_gateway_slist(gw); + curl_slist_append(list, "Content-Type: application/json"); + curl_slist_append(list, "Accept: application/json"); + curl_slist_append(list, "User-Agent: " DISCORD_USERAGENT); + curl_slist_append(list, "Pragma: no-cache"); + curl_slist_append(list, "Cache-Control: no-cache"); + curl_slist_append(list, "Sec-WebSocket-Key: cbYK1Jm6cpk3Rua"); + curl_slist_append(list, "Sec-WebSocket-Version: 13"); + curl_slist_append(list, "Upgrade: websocket"); + + curl_easy_setopt(c, CURLOPT_HEADERFUNCTION, stall_connection); + curl_easy_setopt(c, CURLOPT_HEADERDATA, c); + + curl_easy_setopt(c, CURLOPT_HTTPHEADER, list); + + curl_easy_setopt(c, CURLOPT_TCP_KEEPALIVE, 1L); + curl_easy_setopt(c, CURLOPT_TCP_KEEPIDLE, 120L); + curl_easy_setopt(c, CURLOPT_TCP_KEEPINTVL, 60L); + + curl_easy_setopt(c, CURLOPT_FORBID_REUSE, 1L); + curl_easy_setopt(c, CURLOPT_FRESH_CONNECT, 1L); + + curl_easy_setopt(c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); + curl_easy_setopt(c, CURLOPT_FOLLOWLOCATION, 1L); + + curl_easy_setopt(c, CURLOPT_WRITEFUNCTION, dc_gateway_writefunc); + curl_easy_setopt(c, CURLOPT_WRITEDATA, gw); + + dc_gateway_set_login(gw, login); + dc_gateway_set_curl(gw, api->curl, c); + + if (curl_multi_add_handle(api->curl, c) != CURLM_OK) { + goto cleanup; + } + + c = NULL; + + ret = gw; + gw = NULL; cleanup: - free(url); - json_decref(reply); - - return ret; -} - -bool dc_api_get_userguilds(dc_api_t api, dc_account_t login, GPtrArray **out) -{ - char const *url = "users/@me/guilds"; - json_t *reply = NULL, *c = NULL, *val = NULL; - size_t i = 0; - bool ret = false; - GPtrArray *guilds = g_ptr_array_new_with_free_func( - (GDestroyNotify)dc_unref - ); - - return_if_true(api == NULL, false); - return_if_true(login == NULL, false); - - reply = dc_api_call_sync(api, "GET", dc_account_token(login), url, NULL); - goto_if_true(reply == NULL, cleanup); - - goto_if_true(!json_is_array(reply), cleanup); - - json_array_foreach(reply, i, c) { - dc_guild_t g = dc_guild_new(); - - val = json_object_get(c, "id"); - goto_if_true(val == NULL || !json_is_string(val), cleanup); - dc_guild_set_id(g, json_string_value(val)); - - val = json_object_get(c, "name"); - goto_if_true(val == NULL || !json_is_string(val), cleanup); - dc_guild_set_name(g, json_string_value(val)); - - g_ptr_array_add(guilds, g); - } - - *out = guilds; - guilds = NULL; - - ret = true; - -cleanup: - - json_decref(reply); - - if (guilds) { - g_ptr_array_unref(guilds); - guilds = NULL; - } + if (c != NULL) { + curl_easy_cleanup(c); + } + + dc_unref(gw); return ret; } diff --git a/libdc/src/gateway.c b/libdc/src/gateway.c new file mode 100644 index 0000000..8faef00 --- /dev/null +++ b/libdc/src/gateway.c @@ -0,0 +1,295 @@ +#include +#include "internal.h" +#include + +struct dc_gateway_ +{ + dc_refable_t ref; + + GPtrArray *ops; + GPtrArray *out; + GByteArray *buffer; + + CURLM *multi; + CURL *easy; + struct curl_slist *slist; + + dc_account_t login; + + uint64_t heartbeat_interval; + time_t last_heartbeat; +}; + +static void dc_gateway_free(dc_gateway_t g) +{ + return_if_true(g == NULL,); + + if (g->buffer != NULL) { + g_byte_array_unref(g->buffer); + g->buffer = NULL; + } + + if (g->ops != NULL) { + g_ptr_array_unref(g->ops); + g->ops = NULL; + } + + if (g->out != NULL) { + g_ptr_array_unref(g->out); + g->out = NULL; + } + + if (g->easy != NULL) { + curl_multi_remove_handle(g->multi, g->easy); + curl_easy_cleanup(g->easy); + g->easy = NULL; + } + + if (g->slist != NULL) { + curl_slist_free_all(g->slist); + g->slist = NULL; + } + + dc_unref(g->login); + + free(g); +} + +dc_gateway_t dc_gateway_new(void) +{ + dc_gateway_t g = calloc(1, sizeof(struct dc_gateway_)); + return_if_true(g == NULL, NULL); + + g->ref.cleanup = (dc_cleanup_t)dc_gateway_free; + + g->buffer = g_byte_array_new(); + goto_if_true(g->buffer == NULL, error); + + g->ops = g_ptr_array_new_with_free_func((GDestroyNotify)json_decref); + goto_if_true(g->ops == NULL, error); + + g->out = g_ptr_array_new_with_free_func((GDestroyNotify)json_decref); + goto_if_true(g->out == NULL, error); + + g->slist = curl_slist_append(NULL, ""); + goto_if_true(g->slist == NULL, error); + + return dc_ref(g); + +error: + + dc_gateway_free(g); + return NULL; +} + +void dc_gateway_set_login(dc_gateway_t gw, dc_account_t login) +{ + return_if_true(gw == NULL,); + gw->login = dc_ref(login); +} + +void dc_gateway_set_curl(dc_gateway_t gw, CURLM *multi, CURL *easy) +{ + return_if_true(gw == NULL,); + gw->multi = multi; + gw->easy = easy; +} + +CURL *dc_gateway_curl(dc_gateway_t gw) +{ + return_if_true(gw == NULL, NULL); + return gw->easy; +} + +struct curl_slist * dc_gateway_slist(dc_gateway_t gw) +{ + return_if_true(gw == NULL, NULL); + return gw->slist; +} + +size_t dc_gateway_writefunc(char *ptr, size_t sz, size_t nmemb, void *data) +{ + dc_gateway_t g = (dc_gateway_t)data; + json_t *j = NULL; + size_t i = 0; + + FILE *f = fopen("websocket.txt", "a+"); + fprintf(f, ">> "); + fwrite(ptr, sz, nmemb, f); + fprintf(f, "\n"); + fclose(f); + + for (i = 0; *ptr != '{' && i < (sz *nmemb); ptr++, i++) + ; + + if (i < (sz * nmemb)) { + j = json_loadb(ptr, (sz*nmemb) - i, JSON_DISABLE_EOF_CHECK, NULL); + if (j != NULL) { + g_ptr_array_add(g->ops, j); + } + } + + return sz * nmemb; +} + +static json_t *dc_gateway_answer(dc_gateway_t gw) +{ + json_t *j = NULL; + char const *token = NULL; + + j = json_object(); + return_if_true(j == NULL, NULL); + + token = dc_account_token(gw->login); + if (token != NULL) { + json_object_set_new(j, "token", json_string(token)); + } + + return j; +} + +static void dc_gateway_queue(dc_gateway_t gw, int code, json_t *d) +{ + json_t *j = NULL; + + j = json_object(); + return_if_true(j == NULL,); + + if (d == NULL) { + d = dc_gateway_answer(gw); + } + + json_object_set_new(j, "t", json_null()); + json_object_set_new(j, "s", json_null()); + json_object_set_new(j, "d", d); + json_object_set_new(j, "op", json_integer(code)); + + g_ptr_array_add(gw->out, j); +} + +static void dc_gateway_queue_heartbeat(dc_gateway_t gw) +{ + dc_gateway_queue(gw, GATEWAY_OPCODE_HEARTBEAT, NULL); + gw->last_heartbeat = time(NULL); +} + +static bool dc_gateway_handle_hello(dc_gateway_t gw, json_t *d) +{ + json_t *val = NULL; + + val = json_object_get(d, "heartbeat_interval"); + return_if_true(val == NULL || !json_is_integer(val), false); + + /* send an identify first + */ + dc_gateway_queue(gw, GATEWAY_OPCODE_IDENTIFY, NULL); + + gw->heartbeat_interval = json_integer_value(val); + dc_gateway_queue_heartbeat(gw); + + return true; +} + +static bool dc_gateway_handle_op(dc_gateway_t gw, json_t *j) +{ + json_t *val = NULL; + dc_gateway_opcode_t op = 0; + + val = json_object_get(j, "op"); + return_if_true(val == NULL || !json_is_integer(val), false); + op = (dc_gateway_opcode_t)json_integer_value(val); + + val = json_object_get(j, "d"); + return_if_true(val == NULL || !json_is_object(val), false); + + switch (op) { + case GATEWAY_OPCODE_HELLO: dc_gateway_handle_hello(gw, val); break; + default: break; + } + + return true; +} + +#if 0 +static void dc_gateway_process_read(dc_gateway_t gw) +{ + char buf[100] = {0}; + size_t read = 0; + int ret = 0; + FILE *f = NULL; + json_t *j = NULL; + size_t where = 0; + + ret = curl_easy_recv(gw->easy, &buf, sizeof(buf), &read); + return_if_true(ret != CURLE_OK,); + + g_byte_array_append(gw->buffer, (uint8_t const*)buf, read); + + f = fmemopen(gw->buffer->data, gw->buffer->len, "r"); + return_if_true(f == NULL,); + + j = json_loadf(f, JSON_DISABLE_EOF_CHECK, NULL); + where = ftell(f); + + fclose(f); + f = NULL; + + if (j != NULL) { + g_ptr_array_add(gw->ops, j); + g_byte_array_remove_range(gw->buffer, 0, where); + } +} +#endif + +static void dc_gateway_process_in(dc_gateway_t gw) +{ + while (gw->ops->len > 0) { + json_t *j = g_ptr_array_index(gw->ops, 0); + dc_gateway_handle_op(gw, j); + g_ptr_array_remove_index(gw->ops, 0); + } +} + +static void dc_gateway_process_out(dc_gateway_t gw) +{ + char *str = NULL; + size_t slen = 0, outlen = 0, sent = 0; + uint8_t *mask = NULL; + + while (gw->out->len > 0) { + json_t *j = g_ptr_array_index(gw->out, 0); + + str = json_dumps(j, JSON_COMPACT); + + if (str != NULL) { + slen = strlen(str); + mask = dc_gateway_makeframe((uint8_t const *)str, slen, 0, &outlen); + curl_easy_send(gw->easy, mask, outlen, &sent); + + FILE *f = fopen("websocket.txt", "a+"); + fprintf(f, "<< %s\n", str); + fclose(f); + } + + free(str); + free(mask); + + g_ptr_array_remove_index(gw->out, 0); + } +} + +void dc_gateway_process(dc_gateway_t gw) +{ + time_t diff = 0; + + if (gw->heartbeat_interval > 0) { + diff = time(NULL) - gw->last_heartbeat; + if (diff >= (gw->heartbeat_interval / 1000)) { + dc_gateway_queue_heartbeat(gw); + } + } + + //dc_gateway_process_read(gw); + dc_gateway_process_in(gw); + dc_gateway_process_out(gw); +} diff --git a/libdc/src/loop.c b/libdc/src/loop.c index 04805b1..742c2b0 100644 --- a/libdc/src/loop.c +++ b/libdc/src/loop.c @@ -15,6 +15,7 @@ struct dc_loop_ bool multi_owner; GPtrArray *apis; + GPtrArray *gateways; }; static void dc_loop_free(dc_loop_t p) @@ -42,6 +43,11 @@ static void dc_loop_free(dc_loop_t p) p->apis = NULL; } + if (p->gateways != NULL) { + g_ptr_array_unref(p->gateways); + p->gateways = NULL; + } + free(p); } @@ -63,6 +69,18 @@ mcurl_handler(CURL *easy, curl_socket_t s, int what, void *userp, void *socketp) struct event *event = (struct event *)socketp; dc_loop_t loop = (dc_loop_t)userp; + for (size_t i = 0; i < loop->gateways->len; i++) { + dc_gateway_t gw = g_ptr_array_index(loop->gateways, i); + + if (dc_gateway_curl(gw) == easy) { + printf("gateway event: %s: %s%s\n", + (what == CURL_POLL_REMOVE ? "remove" : "add"), + ((what & CURL_POLL_IN) ? "r" : ""), + ((what & CURL_POLL_OUT) ? "w": "") + ); + } + } + if (what == CURL_POLL_REMOVE) { if (event != NULL) { event_del(event); @@ -148,9 +166,12 @@ dc_loop_t dc_loop_new_full(struct event_base *base, CURLM *multi) ptr->multi_owner = true; } - ptr->apis = g_ptr_array_new(); + ptr->apis = g_ptr_array_new_with_free_func((GDestroyNotify)dc_unref); goto_if_true(ptr->apis == NULL, fail); + ptr->gateways = g_ptr_array_new_with_free_func((GDestroyNotify)dc_unref); + goto_if_true(ptr->gateways == NULL, fail); + ptr->timer = evtimer_new(ptr->base, timer_handler, ptr); goto_if_true(ptr->timer == NULL, fail); @@ -188,7 +209,14 @@ void dc_loop_add_api(dc_loop_t l, dc_api_t a) dc_api_set_event_base(p, l->base); dc_api_set_curl_multi(p, l->multi); - g_ptr_array_add(l->apis, p); + g_ptr_array_add(l->apis, dc_ref(p)); +} + +void dc_loop_add_gateway(dc_loop_t l, dc_gateway_t gw) +{ + return_if_true(l == NULL || gw == NULL,); + + g_ptr_array_add(l->gateways, dc_ref(gw)); } void dc_loop_abort(dc_loop_t l) @@ -225,5 +253,10 @@ bool dc_loop_once(dc_loop_t l) } } + for (i = 0; i < l->gateways->len; i++) { + dc_gateway_t gw = g_ptr_array_index(l->gateways, i); + dc_gateway_process(gw); + } + return true; } diff --git a/libdc/src/masking.c b/libdc/src/masking.c new file mode 100644 index 0000000..90538fb --- /dev/null +++ b/libdc/src/masking.c @@ -0,0 +1,63 @@ +#include +#include "internal.h" + +static uint8_t * +websocket_mask(uint8_t key[4], uint8_t const *pload, size_t psize) +{ + size_t i = 0; + uint8_t *ret = calloc(psize, sizeof(char)); + + for (i = 0; i < psize; i++) { + ret[i] = pload[i] ^ key[i % 4]; + } + + return ret; +} + +uint8_t * +dc_gateway_makeframe(uint8_t const *d, size_t data_len, + uint8_t type, size_t *outlen) +{ + uint8_t *data = NULL; + uint8_t *full_data; + uint32_t len_size = 1; + uint8_t mkey[4] = { 0x12, 0x34, 0x56, 0x78 }; + + data = websocket_mask(mkey, d, data_len); + + if (data_len > 125) { + if (data_len <= UINT16_MAX) { + len_size += 2; + } else { + len_size += 8; + } + } + + full_data = calloc(1 + data_len + len_size + 4, sizeof(uint8_t)); + + if (type == 0) { + type = 129; + } + + full_data[0] = type; + + if (data_len <= 125) { + full_data[1] = data_len | 0x80; + } else if (data_len <= G_MAXUINT16) { + uint16_t be_len = GUINT16_TO_BE(data_len); + full_data[1] = 126 | 0x80; + memmove(full_data + 2, &be_len, 2); + } else { + guint64 be_len = GUINT64_TO_BE(data_len); + full_data[1] = 127 | 0x80; + memmove(full_data + 2, &be_len, 8); + } + + memmove(full_data + (1 + len_size), &mkey, 4); + memmove(full_data + (1 + len_size + 4), data, data_len); + + *outlen = 1 + data_len + len_size + 4; + free(data); + + return full_data; +} diff --git a/ncdc/include/ncdc/ncdc.h b/ncdc/include/ncdc/ncdc.h index 4484063..e27d019 100644 --- a/ncdc/include/ncdc/ncdc.h +++ b/ncdc/include/ncdc/ncdc.h @@ -47,6 +47,7 @@ extern GHashTable *accounts; extern dc_account_t current_account; extern dc_api_t api; +extern dc_loop_t loop; extern char *ncdc_private_dir; extern void *config; diff --git a/ncdc/src/login.c b/ncdc/src/login.c index 68d25f2..22dcfde 100644 --- a/ncdc/src/login.c +++ b/ncdc/src/login.c @@ -7,6 +7,7 @@ bool ncdc_cmd_login(ncdc_mainwindow_t n, size_t ac, wchar_t **av) char *arg = NULL; bool ret = false; dc_account_t acc = NULL; + dc_gateway_t gw = NULL; goto_if_true(ac <= 1, cleanup); @@ -29,22 +30,22 @@ bool ncdc_cmd_login(ncdc_mainwindow_t n, size_t ac, wchar_t **av) } } - if (!dc_api_authenticate(api, acc)) { + if (!dc_api_login(api, acc)) { LOG(n, L"login: %ls: authentication failed; wrong password?", av[1]); goto cleanup; } - if (!dc_api_get_userinfo(api, acc, acc)) { - LOG(n, L"login: %ls: failed to get basic user information", av[1]); + gw = dc_api_establish_gateway(api, acc); + if (gw == NULL) { + LOG(n, L"login: %ls: failed to establish gateway", av[1]); goto cleanup; } - if (!dc_api_get_friends(api, acc)) { - LOG(n, L"login: %ls: failed to load friends", av[1]); - goto cleanup; - } + dc_loop_add_gateway(loop, gw); + dc_unref(current_account); current_account = dc_ref(acc); + LOG(n, L"login: %ls: authentication successful", av[1]); ret = true;