#include #include #include #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; }; typedef enum { SSB_PACKET_TYPE_BUFFER = 0, SSB_PACKET_TYPE_STRING = 1, SSB_PACKET_TYPE_JSON = 2, } SsbPacketType; typedef enum { SSB_PACKET_FLAGS_BUFFER = 0, SSB_PACKET_FLAGS_STRING = 1, SSB_PACKET_FLAGS_JSON = 2, SSB_PACKET_FLAGS_END = 4, SSB_PACKET_FLAGS_STREAM = 8, } SsbPacketFlags; typedef enum { SSB_RPC_ASYNC, SSB_RPC_SOURCE, SSB_RPC_SINK, SSB_RPC_DUPLEX } SsbRPCType; typedef enum { SSB_STREAM_STATE_OPEN, SSB_STREAM_STATE_ENDED_OK, SSB_STREAM_STATE_ENDED_ERROR, } SsbStreamState; #define MAX_MESSAGE_SIZE 32768 #define BOXS_MAXLEN 4096 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; typedef struct { guint32 len; gint32 request_id; } PacketHeader; typedef struct { guint16 len; guint8 mac[16]; } BoxsHeader; 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 }; enum { SIGNAL_INITIALISED, SIGNAL_CONNECTED, SIGNAL_LAST }; static guint ssb_scuttler_signals[SIGNAL_LAST] = {0 }; 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 emit_initialised(SsbScuttler *scuttler, gboolean success) { g_signal_emit(scuttler, ssb_scuttler_signals[SIGNAL_INITIALISED], 0, success); } 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); emit_initialised(scuttler, FALSE); 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); emit_initialised(scuttler, FALSE); 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"); emit_initialised(scuttler, FALSE); 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); emit_initialised(scuttler, FALSE); 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; emit_initialised(scuttler, 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; } // was: write_all static gboolean ssb_scuttler_send_plain(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; } // was: read_all 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; } // was: shs_connect 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_plain(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_plain(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 void emit_connected(SsbScuttler *scuttler, gboolean success) { g_signal_emit(scuttler, ssb_scuttler_signals[SIGNAL_CONNECTED], 0, success); } // extracted from main 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 increment_nonce(guchar nonce[24]) { int i; for (i = 23; (i >= 0) && (nonce[i] == 0xff); i--) { nonce[i] = 0; } if (i >= 0) { nonce[i]++; } } // was: bs_write_packet static gboolean ssb_scuttler_write_packet(SsbScuttler *scuttler, const guchar *data, guint16 data_len, GError **error) { gsize boxed_len = data_len + 34; guchar boxed[boxed_len]; GError *err = NULL; increment_nonce(scuttler->nonce2); if (crypto_secretbox_easy(boxed + 18, data, data_len, scuttler->nonce2, scuttler->encrypt_key) < 0) { g_set_error(error, SSB_SCUTTLER_ERROR, SSB_SCUTTLER_ERROR_SEND, "Failed to box packet data"); return FALSE; } BoxsHeader header; header.len = g_htons(data_len); memcpy(header.mac, boxed + 18, 16); if (crypto_secretbox_easy(boxed, (guchar *)&header, 18, scuttler->nonce1, scuttler->encrypt_key) < 0) { g_set_error(error, SSB_SCUTTLER_ERROR, SSB_SCUTTLER_ERROR_SEND, "Failed to box packet header"); return FALSE; } increment_nonce(scuttler->nonce1); increment_nonce(scuttler->nonce1); increment_nonce(scuttler->nonce2); if (!ssb_scuttler_send_plain(scuttler, boxed, 34, &err)) { g_propagate_error(error, err); return FALSE; } return TRUE; } // was: bs_write static gboolean ssb_scuttler_send_bytes(SsbScuttler *scuttler, const guchar *data, gsize data_len, GError **error) { GError *err = NULL; while(data_len > 0) { gsize len = data_len > BOXS_MAXLEN ? BOXS_MAXLEN : data_len; if (!ssb_scuttler_write_packet(scuttler, data, len, &err)) { g_propagate_error(error, err); return FALSE; } data_len -= len; data += len; } return TRUE; } // was: ps_write static gboolean ssb_scuttler_send_packet(SsbScuttler *scuttler, SsbPacketType type, const gchar *data, gsize data_len, int request_id, gboolean stream, gboolean end, GError **error) { gsize out_len = data_len + 9; guchar out_buf[out_len]; PacketHeader header = { g_htonl(data_len), g_htonl(request_id) }; GError *err = NULL; out_buf[0] = (stream << 3) | (end << 2) | (type & 3); memcpy(out_buf + 1, &header, 8); memcpy(out_buf + 9, data, data_len); if (!ssb_scuttler_send_bytes(scuttler, out_buf, out_len, &err)) { g_propagate_error(error, err); return FALSE; } return TRUE; } // was: muxrpc_call static void ssb_scuttler_muxrpc_call(SsbScuttler *scuttler, const gchar *method, const gchar *argument, SsbRPCType type, const gchar *typestr, int req_id, GError **error) { gchar *request; gsize request_len; gboolean is_request = (type == SSB_RPC_ASYNC); JsonObject *req_object = json_object_new(); JsonNode *req_node = json_node_new(JSON_NODE_OBJECT); GError *err = NULL; json_object_set_string_member(req_object, "name", method); json_object_set_string_member(req_object, "args", argument); if (!is_request) { json_object_set_string_member(req_object, "type", typestr); } json_node_take_object(req_node, req_object); request = json_to_string(req_node, FALSE); json_node_free(req_node); if ((request_len = strlen(request)) > MAX_MESSAGE_SIZE) { g_set_error(error, SSB_SCUTTLER_ERROR, SSB_SCUTTLER_ERROR_REQUEST_TOO_LARGE, "Request too large"); return; } if (!ssb_scuttler_send_packet(scuttler, SSB_PACKET_TYPE_JSON, request, request_len, req_id, !is_request, FALSE, &err)) { g_propagate_error(error, err); return; } g_free(request); } 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; /** * SsbScuttler::initialised: * @success: if %TRUE, initialisation succeeded * * Emitted when the SsbScuttler object is initialised. */ ssb_scuttler_signals[SIGNAL_INITIALISED] = g_signal_new( "initialised", G_TYPE_FROM_CLASS(klass), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, g_cclosure_marshal_VOID__BOOLEAN, G_TYPE_NONE, 1, G_TYPE_BOOLEAN); /** * SsbScuttler::connected: * @success: if %TRUE, the connection and handshake has succeeded * * Emitted when the SsbScuttler object is connected to the SBOT server. */ ssb_scuttler_signals[SIGNAL_CONNECTED] = g_signal_new( "connected", G_TYPE_FROM_CLASS(klass), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, g_cclosure_marshal_VOID__BOOLEAN, G_TYPE_NONE, 1, G_TYPE_BOOLEAN); } 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) { GMainContext *scuttle_context; GSource *second_hook; GError *err = NULL; if (sodium_init() < 0) { g_critical("Can not initialise sodium"); return NULL; } // 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); } }