ssb-gtk/ssb-gtk/sbot.c

852 lines
24 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include <glib.h>
#include <json-glib/json-glib.h>
#include <sodium.h>
#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_RPC_ASYNC,
SSB_RPC_SOURCE,
SSB_RPC_SINK,
SSB_RPC_DUPLEX
} SsbRPCType;
typedef enum {
SSB_PACKET_TYPE_BUFFER = 0,
SSB_PACKET_TYPE_STRING = 1,
SSB_PACKET_TYPE_JSON = 2,
} SsbPacketType;
#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
};
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_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;
}
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_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 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]++;
}
}
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;
}
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;
}
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;
}
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;
}zq
if (!ssb_scuttler_send_packet(scuttler, request, request_len, SSB_PACKET_TYPE_JSON, 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;
}
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 dont 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, lets 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);
}
}