diff --git a/meson.build b/meson.build index 2fc30ff..04d3d6f 100644 --- a/meson.build +++ b/meson.build @@ -5,9 +5,13 @@ i18n = import('i18n') glib_required = '>= 2.40' gtk_required = '>= 3.20' +json_glib_required = '>= 1.4.0' +libsodium_required = '>= 1.0.0' glib = dependency('glib-2.0', version: glib_required) gtk = dependency('gtk+-3.0', version: gtk_required) +json_glib = dependency('json-glib-1.0', version: json_glib_required) +libsodium = dependency('libsodium', version: libsodium_required) subdir('data') subdir('ssb-gtk') diff --git a/ssb-gtk/meson.build b/ssb-gtk/meson.build index 2b41041..3d9f83b 100644 --- a/ssb-gtk/meson.build +++ b/ssb-gtk/meson.build @@ -6,5 +6,5 @@ sources = [ ] executable('ssb-gtk', sources, ssb_resources, - dependencies: [glib, gtk], + dependencies: [glib, gtk, json_glib, libsodium], install: true) diff --git a/ssb-gtk/sbot.c b/ssb-gtk/sbot.c index 2ecdb11..d0f506c 100644 --- a/ssb-gtk/sbot.c +++ b/ssb-gtk/sbot.c @@ -1,22 +1,678 @@ #include +#include +#include -gboolean do_scuttling = TRUE; +#include "sbot.h" + +struct _SsbScuttler { + GObject parent_instance; + + // If TRUE, the scuttler is already initialised + gboolean initialised; + + // The SSB directory + gchar *ssb_dir; + + guchar *app_key; + guchar *private_key; + guchar *encrypt_key; + guchar *decrypt_key; + guchar nonce1[24]; + guchar nonce2[24]; + guchar rx_nonce[24]; + gsize rx_buf_pos; + gsize rx_buf_len; + gboolean noauth; + gboolean wrote_goodbye; + GSocketClient *socket_client; + GSocketConnection *connection; +}; + +G_DEFINE_QUARK(ssb_scttler_error_quark, ssb_scuttler_error); +G_DEFINE_TYPE(SsbScuttler, ssb_scuttler, G_TYPE_INITIALLY_UNOWNED); + +typedef struct { + gboolean valid; + guint line; + guint column; +} ParseData; + +static const guchar zeros[24] = {0}; + +static const guchar ssb_cap[] = { + 0xd4, 0xa1, 0xcb, 0x88, 0xa6, 0x6f, 0x02, 0xf8, + 0xdb, 0x63, 0x5c, 0xe2, 0x64, 0x41, 0xcc, 0x5d, + 0xac, 0x1b, 0x08, 0x42, 0x0c, 0xea, 0xac, 0x23, + 0x08, 0x39, 0xb7, 0x55, 0x84, 0x5a, 0x9f, 0xfb +}; + +GMainLoop *scuttle_loop = NULL; +SsbScuttler *singleton = NULL; + +static gchar * +read_config(const gchar *base_dir, const gchar *file_name, GError **error) { + gchar *config_file_name; + GFile *config_file; + GFileInfo *config_info; + goffset config_size; + GFileInputStream *config_stream; + GError *err = NULL; + gchar *config_data = NULL; + + config_file_name = g_strdup_printf("%s/%s", base_dir, file_name); + config_file = g_file_new_for_path(config_file_name); + g_free(config_file_name); + + if ((config_info = g_file_query_info(config_file, G_FILE_ATTRIBUTE_STANDARD_SIZE, G_FILE_QUERY_INFO_NONE, NULL, &err)) == NULL) { + g_propagate_error(error, err); + + goto ret_err1; + } + + config_size = g_file_info_get_size(config_info); + + config_data = g_new0(gchar, config_size); + + if ((config_stream = g_file_read(config_file, NULL, &err)) == NULL) { + g_propagate_error(error, err); + g_clear_pointer(&config_data, g_free); + + goto ret_err2; + } + + if (!g_input_stream_read_all(G_INPUT_STREAM(config_stream), config_data, config_size, NULL, NULL, &err)) { + g_propagate_error(error, err); + g_clear_pointer(&config_data, g_free); + + goto ret_err3; + } + + if (!g_input_stream_close(G_INPUT_STREAM(config_stream), NULL, &err)) { + g_propagate_error(error, err); + g_clear_pointer(&config_data, g_free); + + goto ret_err3; + } + + ret_err3: + g_object_unref(config_info); + + ret_err2: + g_object_unref(config_stream); + + ret_err1: + g_object_unref(config_file); + + return config_data; +} + +static void +json_error(JsonParser *parser, GError *error, ParseData *parse_data) +{ + if ((error->domain != JSON_PARSER_ERROR) || (error->code != JSON_PARSER_ERROR_INVALID_BAREWORD)) { + parse_data->valid = FALSE; + } else { + parse_data->valid = TRUE; + parse_data->line = json_parser_get_current_line(parser) - 1; + parse_data->column = json_parser_get_current_pos(parser) - 1; + } +} + +static JsonNode * +parse_commented_json(gchar *json_data, GError **error) +{ + ParseData parse_data; + JsonParser *parser; + GError *err = NULL; + JsonNode *result = NULL; + + parse_data.valid = FALSE; + parser = json_parser_new(); + g_signal_connect(parser, "error", G_CALLBACK(json_error), &parse_data); + + while (!json_parser_load_from_data(parser, json_data, -1, &err)) { + gchar **config_lines; + + if (parse_data.valid != TRUE) { + g_propagate_error(error, err); + + break; + } + + // XXX: Will this work under OS/X and Windows? + config_lines = g_strsplit(json_data, "\n", 0); + + if (config_lines[parse_data.line][parse_data.column] == '#') { + guint i; + guint start_pos = 0; + guint end_pos; + + for (i = 0; i <= parse_data.line; i++) { + if (i < parse_data.line) { + start_pos += strlen(config_lines[i]) + 1; + } + + end_pos = start_pos; + end_pos += strlen(config_lines[i]); + } + + start_pos += parse_data.column; + + for (i = start_pos; i < end_pos; i++) { + json_data[i] = ' '; + } + + g_clear_error(&err); + parse_data.valid = FALSE; + } + + g_strfreev(config_lines); + } + + result = json_parser_steal_root(parser); + + g_object_unref(parser); + + return result; +} + +static void +initialise(SsbScuttler *scuttler, gchar *ssb_dir) +{ + gchar *config_data = NULL; + GError *err = NULL; + JsonNode *json_content = NULL; + + g_return_if_fail(scuttler != NULL); + + if (scuttler->initialised == TRUE) { + return; + } + + if ((config_data = read_config(ssb_dir, "config", &err)) != NULL) { + if ((json_content = parse_commented_json(config_data, &err)) == NULL) { + g_critical("Could not parse configuration data"); + + g_free(config_data); + + return; + } + + g_free(config_data); + g_clear_pointer(&json_content, json_node_free); + } + + if ((config_data = read_config(ssb_dir, "secret", &err)) != NULL) { + JsonObject *secret_object; + gchar *private_key = NULL; + guchar *decoded_private_key = NULL; + size_t private_key_len; + gsize key_len; + + if ((json_content = parse_commented_json(config_data, &err)) == NULL) { + g_critical("Could not parse secret data"); + + g_free(config_data); + + return; + } + + secret_object = json_node_get_object(json_content); + + if ((private_key = g_strdup(json_object_get_string_member(secret_object, "private"))) == NULL) { + g_critical("Could not read private key, can not continue"); + + return; + } + + private_key_len = strlen(private_key); + + if ((strlen(private_key) > 8) && + (strcmp(private_key + private_key_len - 8, ".ed25519") == 0)) { + private_key[private_key_len - 8] = 0; + } + + if ((decoded_private_key = g_base64_decode(private_key, &key_len)) == NULL) { + g_critical("Could not decode private key, can not continue"); + g_free(private_key); + + return; + } + + g_free(private_key); + g_free(config_data); + g_clear_pointer(&json_content, json_node_free); + + g_clear_pointer(&(scuttler->private_key), g_free); + scuttler->private_key = decoded_private_key; + } + + scuttler->app_key = g_new0(guchar, sizeof(ssb_cap)); + memcpy(scuttler->app_key, ssb_cap, sizeof(ssb_cap)); + + scuttler->initialised = TRUE; +} + +/** + * ensure_scuttler(): + * @ssb_dir: (nullable): the SSB directory + * + * Ensure the scuttler singleton is initialised with @ssb_dir as its base directory. + */ +static inline gboolean +ensure_scuttler(gchar *ssb_dir) +{ + if (singleton != NULL) { + if ((ssb_dir != NULL) && g_strcmp0(ssb_dir, singleton->ssb_dir)) { + return FALSE; + } + + return TRUE; + } + + singleton = g_object_new(SSB_TYPE_SCUTTLER, NULL); + + if (ssb_dir != NULL) { + initialise(singleton, ssb_dir); + } + + return TRUE; +} + +static gboolean +second_hook_cb() +{ + g_print("Scuttling…\n"); + + return TRUE; +} + +static gboolean +ssb_scuttler_send(SsbScuttler *scuttler, gpointer data, gsize data_len, GError **error) +{ + GOutputStream *stream = g_io_stream_get_output_stream(G_IO_STREAM(scuttler->connection)); + GError *err = NULL; + + if (!g_output_stream_write_all(stream, data, data_len, NULL, NULL, &err)) { + g_propagate_error(error, err); + + return FALSE; + } + + return TRUE; +} + +static gboolean +ssb_scuttler_read(SsbScuttler *scuttler, gpointer buffer, gsize buffer_len, GError **error) +{ + GInputStream *stream = g_io_stream_get_input_stream(G_IO_STREAM(scuttler->connection)); + GError *err = NULL; + + if (g_input_stream_read(stream, buffer, buffer_len, NULL, &err) < 0) { + g_propagate_error(error, err); + + return FALSE; + } + + return TRUE; +} + +static gboolean +ssb_scuttler_shs_connect(SsbScuttler *scuttler, GError **error) +{ + guchar kx_pk[crypto_box_PUBLICKEYBYTES]; + guchar kx_sk[crypto_box_SECRETKEYBYTES]; + guchar local_app_mac[crypto_box_PUBLICKEYBYTES]; + guchar remote_app_mac[crypto_box_PUBLICKEYBYTES]; + guchar remote_kx_pk[crypto_box_PUBLICKEYBYTES]; + guchar buf[2 * crypto_box_PUBLICKEYBYTES]; + guchar secret[crypto_box_SECRETKEYBYTES]; + guchar remote_pk_curve[crypto_box_PUBLICKEYBYTES]; + guchar a_bob[crypto_box_PUBLICKEYBYTES]; + guchar secret2a[crypto_box_PUBLICKEYBYTES * 3]; + guchar secret2[crypto_box_PUBLICKEYBYTES]; + guchar shash[crypto_box_PUBLICKEYBYTES]; + guchar signed1[3 * crypto_box_PUBLICKEYBYTES]; + guchar sig[2 * crypto_box_PUBLICKEYBYTES]; + guchar hello[3 * crypto_box_PUBLICKEYBYTES]; + guchar boxed_auth[112]; + guchar boxed_response[80]; + guchar local_sk_curve[crypto_box_SECRETKEYBYTES]; + guchar b_alice[crypto_box_SECRETKEYBYTES]; + guchar secret3a[4 * crypto_box_SECRETKEYBYTES]; + guchar secret3[crypto_box_SECRETKEYBYTES]; + guchar signed2[160]; + guchar enc_key_hashed[crypto_box_PUBLICKEYBYTES + crypto_box_SECRETKEYBYTES]; + guchar dec_key_hashed[crypto_box_PUBLICKEYBYTES + crypto_box_SECRETKEYBYTES]; + GError *err = NULL; + + if (crypto_box_keypair(kx_pk, kx_sk) < 0) { + g_set_error(error, SSB_SCUTTLER_ERROR, SSB_SCUTTLER_ERROR_KEYGEN, "Could not generate auth keypair"); + + return FALSE; + } + + if (crypto_auth(local_app_mac, kx_pk, crypto_box_PUBLICKEYBYTES, scuttler->app_key) < 0) { + g_set_error(error, SSB_SCUTTLER_ERROR, SSB_SCUTTLER_ERROR_KEYAUTH, "Failed to generate app mac"); + + return FALSE; + } + + // Send challenge + memcpy(buf, local_app_mac, crypto_box_PUBLICKEYBYTES); + memcpy(buf + crypto_box_PUBLICKEYBYTES, kx_pk, crypto_box_PUBLICKEYBYTES); + + if (!ssb_scuttler_send(scuttler, buf, sizeof(buf), &err)) { + g_propagate_error(error, err); + + return FALSE; + } + + // Receive challenge response + if (!ssb_scuttler_read(scuttler, buf, sizeof(buf), &err)) { + g_propagate_error(error, err); + + return FALSE; + } + + memcpy(remote_app_mac, buf, crypto_box_PUBLICKEYBYTES); + memcpy(remote_kx_pk, buf + crypto_box_PUBLICKEYBYTES, crypto_box_PUBLICKEYBYTES); + + if (crypto_auth_verify(buf, remote_kx_pk, crypto_box_PUBLICKEYBYTES, scuttler->app_key) < 0) { + g_set_error(error, SSB_SCUTTLER_ERROR, SSB_SCUTTLER_ERROR_KEYVERIFY, "Wrong protocol (version?)"); + + return FALSE; + } + + // Send auth + if (crypto_scalarmult(secret, kx_sk, remote_kx_pk) < 0) { + g_set_error(error, SSB_SCUTTLER_ERROR, SSB_SCUTTLER_ERROR_AUTH, "Failed to derive shared secret"); + + return FALSE; + } + + if (crypto_sign_ed25519_pk_to_curve25519(remote_pk_curve, scuttler->private_key + crypto_box_SECRETKEYBYTES) < 0) { + g_set_error(error, SSB_SCUTTLER_ERROR, SSB_SCUTTLER_ERROR_AUTH, "Failed to curvify remote public key"); + + return FALSE; + } + + if (crypto_scalarmult(a_bob, kx_sk, remote_pk_curve) < 0) { + g_set_error(error, SSB_SCUTTLER_ERROR, SSB_SCUTTLER_ERROR_AUTH, "Failed to derive a_bob"); + + return FALSE; + } + + memcpy(secret2a, scuttler->app_key, crypto_box_PUBLICKEYBYTES); + memcpy(secret2a + crypto_box_PUBLICKEYBYTES, secret, crypto_box_PUBLICKEYBYTES); + memcpy(secret2a + 2 * crypto_box_PUBLICKEYBYTES, a_bob, crypto_box_PUBLICKEYBYTES); + + if (crypto_hash_sha256(secret2, secret2a, sizeof(secret2a)) < 0) { + g_set_error(error, SSB_SCUTTLER_ERROR, SSB_SCUTTLER_ERROR_AUTH, "Failed to hash secret2"); + + return FALSE; + } + + if (crypto_hash_sha256(shash, secret, sizeof(secret)) < 0) { + g_set_error(error, SSB_SCUTTLER_ERROR, SSB_SCUTTLER_ERROR_AUTH, "Failed to hash secret"); + + return FALSE; + } + + memcpy(signed1, scuttler->app_key, crypto_box_PUBLICKEYBYTES); + memcpy(signed1 + crypto_box_PUBLICKEYBYTES, scuttler->private_key + crypto_box_SECRETKEYBYTES, crypto_box_PUBLICKEYBYTES); + memcpy(signed1 + 2 * crypto_box_PUBLICKEYBYTES, shash, crypto_box_PUBLICKEYBYTES); + + if (crypto_sign_detached(sig, NULL, signed1, sizeof(signed1), scuttler->private_key) < 0) { + g_set_error(error, SSB_SCUTTLER_ERROR, SSB_SCUTTLER_ERROR_AUTH, "Failed to sign inner hello"); + + return FALSE; + } + + memcpy(hello, sig, 2 * crypto_box_PUBLICKEYBYTES); + memcpy(hello + 2 * crypto_box_PUBLICKEYBYTES, scuttler->private_key + crypto_box_SECRETKEYBYTES, crypto_box_PUBLICKEYBYTES); + + if (crypto_secretbox_easy(boxed_auth, hello, sizeof(hello), zeros, secret2) < 0) { + g_set_error(error, SSB_SCUTTLER_ERROR, SSB_SCUTTLER_ERROR_AUTH, "Failed to box hello"); + + return FALSE; + } + + if (!ssb_scuttler_send(scuttler, boxed_auth, sizeof(boxed_auth), &err)) { + g_propagate_error(error, err); + + return FALSE; + } + + // Verify the auth response + + if (!ssb_scuttler_read(scuttler, boxed_response, sizeof(boxed_response), &err)) { + g_propagate_error(error, err); + + return FALSE; + } + + if (crypto_sign_ed25519_sk_to_curve25519(local_sk_curve, scuttler->private_key) < 0) { + g_set_error(error, SSB_SCUTTLER_ERROR, SSB_SCUTTLER_ERROR_AUTH, "Failed to curvify local secret key"); + + return FALSE; + } + + if (crypto_scalarmult(b_alice, local_sk_curve, remote_kx_pk) < 0) { + g_set_error(error, SSB_SCUTTLER_ERROR, SSB_SCUTTLER_ERROR_AUTH, "Failed to derive b_alice"); + + return FALSE; + } + + memcpy(secret3a, scuttler->app_key, 32); + memcpy(secret3a + crypto_box_SECRETKEYBYTES, secret, crypto_box_SECRETKEYBYTES); + memcpy(secret3a + 2 * crypto_box_SECRETKEYBYTES, a_bob, crypto_box_PUBLICKEYBYTES); + memcpy(secret3a + 2 * crypto_box_SECRETKEYBYTES + crypto_box_PUBLICKEYBYTES, b_alice, crypto_box_SECRETKEYBYTES); + + if (crypto_hash_sha256(secret3, secret3a, sizeof(secret3a)) < 0) { + g_set_error(error, SSB_SCUTTLER_ERROR, SSB_SCUTTLER_ERROR_AUTH, "Failed to hash secret3"); + + return FALSE; + } + + if (crypto_secretbox_open_easy(sig, boxed_response, sizeof(boxed_response), zeros, secret3) < 0) { + g_set_error(error, SSB_SCUTTLER_ERROR, SSB_SCUTTLER_ERROR_AUTH, "Failed to unbox the okay"); + + return FALSE; + } + + memcpy(signed2, scuttler->app_key, crypto_box_PUBLICKEYBYTES); + memcpy(signed2 + crypto_box_PUBLICKEYBYTES, hello, 96); + memcpy(signed2 + 128, shash, 32); + + if (crypto_sign_verify_detached(sig, signed2, sizeof(signed2), scuttler->private_key + crypto_box_SECRETKEYBYTES) < 0) { + g_set_error(error, SSB_SCUTTLER_ERROR, SSB_SCUTTLER_ERROR_AUTH, "Server not authenticated"); + + return FALSE; + } + + if (crypto_hash_sha256(secret, secret3, crypto_box_PUBLICKEYBYTES) < 0) { + g_set_error(error, SSB_SCUTTLER_ERROR, SSB_SCUTTLER_ERROR_AUTH, "Failed to hash secret3"); + + return FALSE; + } + + memcpy(enc_key_hashed, secret, crypto_box_SECRETKEYBYTES); + memcpy(enc_key_hashed + crypto_box_SECRETKEYBYTES, scuttler->private_key + crypto_box_SECRETKEYBYTES, crypto_box_PUBLICKEYBYTES); + + g_clear_pointer(&(scuttler->encrypt_key), g_free); + scuttler->encrypt_key = g_new0(guchar, crypto_box_SECRETKEYBYTES); + + if (crypto_hash_sha256(scuttler->encrypt_key, enc_key_hashed, 64) < 0) { + g_set_error(error, SSB_SCUTTLER_ERROR, SSB_SCUTTLER_ERROR_AUTH, "Failed to hash the encrypt key"); + + return FALSE; + } + + g_clear_pointer(&(scuttler->decrypt_key), g_free); + scuttler->decrypt_key = g_new0(guchar, crypto_box_SECRETKEYBYTES); + + memcpy(dec_key_hashed, secret, crypto_box_SECRETKEYBYTES); + memcpy(dec_key_hashed + crypto_box_SECRETKEYBYTES, scuttler->private_key + crypto_box_SECRETKEYBYTES, crypto_box_PUBLICKEYBYTES); + + if (crypto_hash_sha256(scuttler->decrypt_key, dec_key_hashed, 64) < 0) { + g_set_error(error, SSB_SCUTTLER_ERROR, SSB_SCUTTLER_ERROR_AUTH, "Failed to hash the decrypt_key"); + + return FALSE; + } + + memcpy(scuttler->nonce1, remote_app_mac, 24); + memcpy(scuttler->nonce2, remote_app_mac, 24); + memcpy(scuttler->rx_nonce, local_app_mac, 24); + scuttler->rx_buf_pos = 0; + scuttler->rx_buf_len = 0; + scuttler->noauth = FALSE; + scuttler->wrote_goodbye = FALSE; + + return TRUE; +} + +static gboolean +ssb_scuttler_connect(SsbScuttler *scuttler, GError **error) +{ + GInetAddress *address; + GSocketAddress *destination; + GError *err = NULL; + + scuttler->socket_client = g_socket_client_new(); + + // TODO: Implement the UNIX socket version, too! Also, read this stuff from the + // config/command line! + address = g_inet_address_new_from_string("127.0.0.1"); + destination = g_inet_socket_address_new(address, 8008); + g_object_unref(address); + + scuttler->connection = g_socket_client_connect(scuttler->socket_client, + G_SOCKET_CONNECTABLE(destination), + NULL, + &err); + g_object_unref(destination); + + // TODO: This is only required if noauth is not set + if (!ssb_scuttler_shs_connect(scuttler, &err)) { + g_propagate_error(error, err); + + return FALSE; + } + + return TRUE; +} + +static void +ssb_scuttler_dispose(GObject *gobject) +{ + SsbScuttler *scuttler = SSB_SCUTTLER(gobject); + GError *err = NULL; + + if (scuttler->connection && !g_io_stream_is_closed(G_IO_STREAM(scuttler->connection))) { + g_io_stream_close(G_IO_STREAM(scuttler->connection), NULL, &err); + scuttler->connection = NULL; + } + + g_clear_pointer(&(scuttler->ssb_dir), g_free); + g_clear_pointer(&(scuttler->app_key), g_free); + g_clear_pointer(&(scuttler->private_key), g_free); +} + +static void +ssb_scuttler_finalize(GObject *gobject) +{ + SsbScuttler *scuttler = SSB_SCUTTLER(gobject); + + g_clear_pointer(&(scuttler->socket_client), g_object_unref); + g_clear_pointer(&(scuttler->ssb_dir), g_free); + g_clear_pointer(&(scuttler->encrypt_key), g_free); + +} + +static void +ssb_scuttler_class_init(SsbScuttlerClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS(klass); + + gobject_class->dispose = ssb_scuttler_dispose; + gobject_class->finalize = ssb_scuttler_finalize; +} + +static void +ssb_scuttler_init(SsbScuttler *scuttler) +{ + scuttler->initialised = FALSE; + scuttler->ssb_dir = NULL; + scuttler->socket_client = NULL; + scuttler->connection = NULL; + scuttler->app_key = NULL; + scuttler->private_key = NULL; + scuttler->encrypt_key = NULL; +} gpointer scuttle(gchar *ssb_dir) { - gchar *config_file = g_strdup_printf("%s/config", ssb_dir); - g_print("Read config file %s\n", config_file); - g_free(config_file); + GMainContext *scuttle_context; + GSource *second_hook; + GError *err = NULL; - g_print("Starting scuttle\n"); + if (sodium_init() < 0) { + g_critical("Can not initialise sodium"); - while (do_scuttling) { - g_usleep(G_USEC_PER_SEC); - g_print("Scuttle…\n"); + return NULL; } - g_print("Scuttling stopped\n"); + // Since scuttler is a singleton global to this file, we don’t need the return value of this + // function now. + if (G_UNLIKELY(!ensure_scuttler(ssb_dir))) { + g_critical("Can not reinitialise scuttler with a new SSB directory."); + + return NULL; + } + + g_object_ref_sink(singleton); + + scuttle_context = g_main_context_new(); + scuttle_loop = g_main_loop_new(scuttle_context, FALSE); + + // TODO: This is for debugging purposes only, let’s remove it as soon as the scuttler works + second_hook = g_timeout_source_new_seconds(1); + g_source_set_callback(second_hook, + G_SOURCE_FUNC(second_hook_cb), + g_main_loop_ref(scuttle_loop), + (GDestroyNotify)g_main_loop_unref); + g_source_attach(second_hook, scuttle_context); + + // Set scuttle_context as the default context for this thread + g_main_context_push_thread_default(scuttle_context); + + if (!ssb_scuttler_connect(singleton, &err)) + { + g_critical("Could not connect: %s", err->message); + + return NULL; + } + + g_debug("Starting scuttle"); + + g_main_loop_run(scuttle_loop); + g_main_context_pop_thread_default(scuttle_context); + g_main_loop_unref(scuttle_loop); + g_object_unref(singleton); + + g_debug("Scuttling stopped"); + + scuttle_loop = NULL; return NULL; } + +void +stop_scuttling(void) +{ + if (scuttle_loop) { + g_main_loop_quit(scuttle_loop); + } +} diff --git a/ssb-gtk/sbot.h b/ssb-gtk/sbot.h index 60acd2f..207efc0 100644 --- a/ssb-gtk/sbot.h +++ b/ssb-gtk/sbot.h @@ -1,10 +1,26 @@ #ifndef __SBOT_H__ # define __SBOT_H__ -# include +# include -extern gboolean do_scuttling; +# define SSB_TYPE_SCUTTLER ssb_scuttler_get_type() +G_DECLARE_FINAL_TYPE(SsbScuttler, ssb_scuttler, SSB, SCUTTLER, GInitiallyUnowned) + +typedef enum { + SSB_SCUTTLER_ERROR_KEYGEN, + SSB_SCUTTLER_ERROR_KEYAUTH, + SSB_SCUTTLER_ERROR_KEYVERIFY, + SSB_SCUTTLER_ERROR_AUTH, +} SsbScuttlerError; + +G_BEGIN_DECLS gpointer scuttle(gchar *ssb_dir); +void stop_scuttling(void); + +# define SSB_SCUTTLER_ERROR (ssb_scuttler_error_quark()) +GQuark ssb_scuttler_error_quark(void); + +G_END_DECLS #endif /* __SBOT_H__ */ diff --git a/ssb-gtk/ssb-app.c b/ssb-gtk/ssb-app.c index 06998c3..1663e8d 100644 --- a/ssb-gtk/ssb-app.c +++ b/ssb-gtk/ssb-app.c @@ -115,7 +115,7 @@ ssb_app_shutdown(GApplication *gapp) { SsbApp *app = SSB_APP(gapp); - do_scuttling = FALSE; + stop_scuttling(); (void)g_thread_join(app->scuttle_thread);