1306 lines
36 KiB
C
1306 lines
36 KiB
C
#include <glib.h>
|
|
#include <json-glib/json-glib.h>
|
|
#include <sodium.h>
|
|
|
|
#include "sbot.h"
|
|
|
|
#define MAX_MESSAGE_SIZE 32768
|
|
#define BOXS_MAXLEN 4096
|
|
|
|
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];
|
|
guchar rx_buf[BOXS_MAXLEN];
|
|
gsize rx_buf_pos;
|
|
gsize rx_buf_len;
|
|
gboolean noauth;
|
|
gboolean wrote_goodbye;
|
|
GSocketClient *socket_client;
|
|
GSocketConnection *connection;
|
|
guint request_id;
|
|
};
|
|
|
|
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;
|
|
|
|
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
|
|
};
|
|
|
|
enum {
|
|
PROP_0,
|
|
PROP_INITIALISED,
|
|
PROP_CONNECTED,
|
|
PROP_COUNT
|
|
};
|
|
|
|
static guint ssb_scuttler_signals[SIGNAL_LAST] = {0};
|
|
static GParamSpec *ssb_scuttler_properties[PROP_COUNT];
|
|
|
|
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, const 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);
|
|
}
|
|
|
|
typedef struct {
|
|
SsbScuttler *scuttler;
|
|
gpointer read_buffer;
|
|
} DataReadType;
|
|
|
|
static gboolean check_incoming_data(SsbScuttler *scuttler);
|
|
|
|
static void
|
|
data_read(GObject *source, GAsyncResult *result, gpointer user_data)
|
|
{
|
|
gssize bytes_read;
|
|
/* SsbScuttler *scuttler = ((DataReadType *)user_data)->scuttler; */
|
|
/* gpointer read_buffer = ((DataReadType *)user_data)->read_buffer; */
|
|
GError *err = NULL;
|
|
|
|
g_debug("g_input_stream_read_async() finished");
|
|
|
|
// We no longer need this struct, just the pointers within which are saved few lines above
|
|
g_slice_free(DataReadType, user_data);
|
|
|
|
if ((bytes_read = g_input_stream_read_finish(G_INPUT_STREAM(source), result, &err)) == -1) {
|
|
g_error("Error while reading from socket: %s", err->message);
|
|
|
|
return;
|
|
}
|
|
|
|
g_debug("Read %ld bytes", bytes_read);
|
|
}
|
|
|
|
static gboolean
|
|
read_inbound(GSocket *socket, GIOCondition condition, SsbScuttler *scuttler)
|
|
{
|
|
DataReadType *data = g_slice_new(DataReadType);
|
|
GInputStream *stream = g_io_stream_get_input_stream(G_IO_STREAM(scuttler->connection));
|
|
|
|
g_debug("Data is flowing in!");
|
|
|
|
data->scuttler = scuttler;
|
|
data->read_buffer = g_malloc(MAX_MESSAGE_SIZE);
|
|
|
|
g_input_stream_read_async(stream,
|
|
data->read_buffer, 2,
|
|
G_PRIORITY_DEFAULT,
|
|
NULL,
|
|
data_read, data);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/**
|
|
* ssb_scuttler_ensure():
|
|
* @ssb_dir: (nullable): the SSB directory
|
|
*
|
|
* Ensure the scuttler singleton is initialised with @ssb_dir as its base directory.
|
|
*/
|
|
gboolean
|
|
ssb_scuttler_ensure(const 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;
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
g_debug("SHS auth keypair generated");
|
|
|
|
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;
|
|
}
|
|
|
|
g_debug("Challenge sent");
|
|
|
|
// Receive challenge response
|
|
if (!ssb_scuttler_read(scuttler, buf, sizeof(buf), &err)) {
|
|
g_propagate_error(error, err);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
g_debug("Challenge response read");
|
|
|
|
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;
|
|
|
|
g_debug("SHS succeeded");
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
emit_connected(SsbScuttler *scuttler, gboolean success)
|
|
{
|
|
g_signal_emit(scuttler, ssb_scuttler_signals[SIGNAL_CONNECTED], 0, success);
|
|
}
|
|
|
|
static void
|
|
connection_finished(GObject *source, GAsyncResult *result, gpointer user_data)
|
|
{
|
|
GSocketClient *socket_client = G_SOCKET_CLIENT(source);
|
|
GTask *task = user_data;
|
|
SsbScuttler *scuttler = g_task_get_task_data(task);
|
|
GError *err = NULL;
|
|
GSocketConnection *connection = g_socket_client_connect_finish(socket_client, result, &err);
|
|
GSocket *socket;
|
|
GSource *socket_source;
|
|
|
|
if (connection == NULL) {
|
|
g_warning("Unable to connect to SBOT/* ");
|
|
|
|
g_task_return_error(task, err);
|
|
g_object_unref(task);
|
|
|
|
return;
|
|
}
|
|
|
|
g_clear_object(&(scuttler->connection));
|
|
scuttler->connection = connection;
|
|
|
|
g_debug("Connected to SBOT");
|
|
|
|
// TODO: This is only required if noauth is not set
|
|
if (!ssb_scuttler_shs_connect(scuttler, &err)) {
|
|
g_task_return_error(task, err);
|
|
g_object_unref(task);
|
|
|
|
return;
|
|
}
|
|
|
|
socket = g_socket_connection_get_socket(connection);
|
|
g_socket_set_blocking(socket, FALSE);
|
|
socket_source = g_socket_create_source(socket, G_IO_IN | G_IO_PRI, NULL);
|
|
g_source_set_callback(socket_source,
|
|
(GSourceFunc)read_inbound,
|
|
g_object_ref(scuttler), g_object_unref);
|
|
g_source_attach(socket_source, g_main_context_get_thread_default());
|
|
|
|
g_task_return_boolean(task, TRUE);
|
|
emit_connected(scuttler, TRUE);
|
|
g_object_unref(task);
|
|
}
|
|
|
|
void
|
|
ssb_scuttler_connect_async(SsbScuttler *scuttler,
|
|
GCancellable *cancellable,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
GTask *task;
|
|
GInetAddress *address;
|
|
GSocketAddress *destination;
|
|
|
|
g_return_if_fail(SSB_IS_SCUTTLER(scuttler));
|
|
g_return_if_fail((cancellable == NULL) || G_IS_CANCELLABLE(cancellable));
|
|
|
|
task = g_task_new(scuttler, cancellable, callback, user_data);
|
|
|
|
// TODO: Maybe there should be a better condition here…
|
|
if (scuttler->connection) {
|
|
g_task_return_boolean(task, TRUE);
|
|
g_object_unref(task);
|
|
|
|
return;
|
|
}
|
|
|
|
g_clear_object(&(scuttler->socket_client));
|
|
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);
|
|
|
|
g_task_set_task_data(task, g_object_ref(scuttler), (GDestroyNotify)g_object_unref);
|
|
|
|
g_socket_client_connect_async(scuttler->socket_client, G_SOCKET_CONNECTABLE(destination), cancellable, connection_finished, task);
|
|
g_object_unref(destination);
|
|
}
|
|
|
|
gboolean
|
|
ssb_scuttler_connect_finish(SsbScuttler *scuttler, GAsyncResult *result, GError **error)
|
|
{
|
|
g_return_val_if_fail(SSB_IS_SCUTTLER(scuttler), FALSE);
|
|
g_return_val_if_fail(g_task_is_valid(result, scuttler), FALSE);
|
|
|
|
return g_task_propagate_boolean(G_TASK(result), error);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
static gboolean
|
|
bs_read_packet(SsbScuttler *scuttler, gpointer buf, gsize *lenp, GError **error)
|
|
{
|
|
guchar boxed_header[34];
|
|
gsize len;
|
|
GError *err = NULL;
|
|
|
|
if (!ssb_scuttler_read(scuttler, boxed_header, 34, &err)) {
|
|
if (err->code == G_IO_ERROR_BROKEN_PIPE) {
|
|
g_propagate_error(error, err);
|
|
} else {
|
|
g_set_error(error,
|
|
SSB_SCUTTLER_ERROR, SSB_SCUTTLER_ERROR_READ,
|
|
"Failed to read boxed packet header");
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
BoxsHeader header;
|
|
|
|
if (crypto_secretbox_open_easy((guchar *
|
|
)&header, boxed_header, 34, scuttler->rx_nonce, scuttler->decrypt_key) < 0) {
|
|
g_set_error(error,
|
|
SSB_SCUTTLER_ERROR, SSB_SCUTTLER_ERROR_DECRYPT,
|
|
"Failed to unbox packet header");
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
increment_nonce(scuttler->rx_nonce);
|
|
|
|
if ((header.len == 0) && !memcmp(header.mac, zeros, 16)) {
|
|
g_set_error(error,
|
|
G_IO_ERROR, G_IO_ERROR_BROKEN_PIPE,
|
|
"Broken pipe");
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
len = g_ntohs(header.len);
|
|
|
|
if (len > BOXS_MAXLEN) {
|
|
g_set_error(error,
|
|
SSB_SCUTTLER_ERROR, SSB_SCUTTLER_ERROR_RESPONSE_TOO_LARGE,
|
|
"Received boxed packet too large");
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
guchar boxed_data[len + 16];
|
|
|
|
if (!ssb_scuttler_read(scuttler, boxed_data + 16, len, &err)) {
|
|
g_propagate_error(error, err);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
memcpy(boxed_data, header.mac, 16);
|
|
|
|
if (crypto_secretbox_open_easy(buf, boxed_data, len + 16, scuttler->rx_nonce, scuttler->decrypt_key) < 0) {
|
|
g_set_error(error,
|
|
SSB_SCUTTLER_ERROR, SSB_SCUTTLER_ERROR_CRYPT,
|
|
"Failed to unbx packet data");
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
increment_nonce(scuttler->rx_nonce);
|
|
|
|
if (lenp) {
|
|
*lenp = len;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
bs_read(SsbScuttler *scuttler, gpointer buf, gsize len)
|
|
{
|
|
size_t remaining;
|
|
GError *err = NULL;
|
|
|
|
while (len > 0) {
|
|
remaining = (scuttler->rx_buf_len > len) ? len : scuttler->rx_buf_len;
|
|
|
|
if (buf) {
|
|
memcpy(buf, scuttler->rx_buf + scuttler->rx_buf_pos, remaining);
|
|
}
|
|
|
|
scuttler->rx_buf_len -= remaining;
|
|
scuttler->rx_buf_pos += remaining;
|
|
len -= remaining;
|
|
buf += remaining;
|
|
|
|
if (len == 0) {
|
|
return TRUE;
|
|
}
|
|
|
|
if (bs_read_packet(scuttler, scuttler->rx_buf, &(scuttler->rx_buf_len), &err) < 0) {
|
|
return FALSE;
|
|
}
|
|
|
|
scuttler->rx_buf_pos = 0;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
ps_read_header(SsbScuttler *scuttler, gsize *len, guint32 *req_id, SsbPacketFlags *flags)
|
|
{
|
|
char buf[9];
|
|
PacketHeader header;
|
|
|
|
if (!bs_read(scuttler, buf, sizeof(buf))) {
|
|
return FALSE;
|
|
}
|
|
|
|
memcpy(&header, buf + 1, 8);
|
|
|
|
if (len) {
|
|
*len = g_ntohl(header.len);
|
|
}
|
|
|
|
if (req_id) {
|
|
*req_id = g_ntohl(header.request_id);
|
|
}
|
|
|
|
if (flags) {
|
|
*flags = buf[0];
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
ps_reject(SsbScuttler *scuttler, gsize len, guint32 req, SsbPacketFlags flags, GError **error)
|
|
{
|
|
g_debug("Ignoring packet");
|
|
|
|
if (bs_read_out(scuttler, len) < 0) {
|
|
g_set_error(error,
|
|
SSB_SCUTTLER_ERROR, SSB_SCUTTLER_ERROR_READ,
|
|
"bs_read_out");
|
|
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
ssb_scuttler_read_async(SsbScuttler *scuttler, guint32 req_id, GError **error)
|
|
{
|
|
guint32 req;
|
|
gsize len;
|
|
SsbPacketFlags flags;
|
|
|
|
while (TRUE) {
|
|
if (!ps_read_header(scuttler, &len, &req, &flags)) {
|
|
g_set_error(error, SSB_SCUTTLER_ERROR, SSB_SCUTTLER_ERROR_READ, "ps_read_header");
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
if (req == -req_id) {
|
|
break;
|
|
}
|
|
|
|
if ((req == 0) && (len == 0)) {
|
|
g_set_error(error, SSB_SCUTTLER_ERROR, SSB_SCUTTLER_ERROR_STREAM_END, "Unexpected end of parent stream");
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
ps_reject(scuttler, len, req, flags);
|
|
}
|
|
|
|
if (flags & SSB_PACKET_FLAGS_END) {
|
|
gint rc;
|
|
|
|
if ((rc = ps_read_error(scuttler, flags, len)) < 0) {
|
|
g_set_error(error, SSB_SCUTTLER_ERROR, SSB_SCUTTLER_ERROR_READ, "bs_read_error");
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
if (rc == 1) {
|
|
return 2; // TODO: What is this for?
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
if (bs_read_out(bs, buffer, len) < 0) {
|
|
g_set_error(error, SSB_SCUTTLER_ERROR, SSB_SCUTTLER_ERROR_READ, "bs_read_out");
|
|
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
// was: muxrpc_call
|
|
/**
|
|
* ssb_scuttler_muxrpc_call:
|
|
* @scuttler: an #SsbScuttler instance
|
|
* @method: the name of the RPC method to call
|
|
* @argument: the argument for the method
|
|
* @type: the type of the RPC call
|
|
* @typestr: TODO
|
|
* @req_id: the request ID to use in the RPC call
|
|
* @error: (nullable): placeholder for a #GError, or %NULL
|
|
*
|
|
* Start an RPC call to SBOT.
|
|
*
|
|
* This is a low-level call and unless a specific RPC method is not exposed via other #SsbScuttler
|
|
* method, it should not be called explicitly.
|
|
*/
|
|
void
|
|
ssb_scuttler_muxrpc_call(SsbScuttler *scuttler,
|
|
const gchar *method,
|
|
const gchar *argument,
|
|
SsbRPCType type,
|
|
const gchar *typestr,
|
|
GError **error)
|
|
{
|
|
gchar *request;
|
|
gsize request_len;
|
|
gint req_id;
|
|
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;
|
|
|
|
g_debug("Calling %s SBOT method", method);
|
|
|
|
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;
|
|
}
|
|
|
|
req_id = g_atomic_int_add(&(scuttler->request_id), 1);
|
|
|
|
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_debug("%s request sent", method);
|
|
|
|
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_get_property(GObject *gobject,
|
|
guint property_id,
|
|
GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
SsbScuttler *scuttler = SSB_SCUTTLER(gobject);
|
|
|
|
switch (property_id) {
|
|
case PROP_INITIALISED:
|
|
g_value_set_boolean(value, scuttler->initialised);
|
|
|
|
break;
|
|
|
|
case PROP_CONNECTED:
|
|
g_value_set_boolean(value, scuttler->connection != NULL);
|
|
|
|
break;
|
|
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, property_id, pspec);
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
ssb_scuttler_class_init(SsbScuttlerClass *klass)
|
|
{
|
|
GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
|
|
|
|
if (sodium_init() < 0) {
|
|
g_critical("Can not initialise sodium");
|
|
|
|
return;
|
|
}
|
|
|
|
gobject_class->dispose = ssb_scuttler_dispose;
|
|
gobject_class->finalize = ssb_scuttler_finalize;
|
|
gobject_class->get_property = ssb_scuttler_get_property;
|
|
|
|
/**
|
|
* 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);
|
|
|
|
/**
|
|
* SsbScuttler:initialised:
|
|
*
|
|
* If %TRUE, the scuttler is connected
|
|
*/
|
|
ssb_scuttler_properties[PROP_INITIALISED] = g_param_spec_boolean(
|
|
"initialised", "initialised", "initialised",
|
|
FALSE,
|
|
G_PARAM_STATIC_STRINGS | G_PARAM_READABLE);
|
|
|
|
ssb_scuttler_properties[PROP_CONNECTED] = g_param_spec_boolean(
|
|
"connected", "connected", "connected",
|
|
FALSE,
|
|
G_PARAM_STATIC_STRINGS | G_PARAM_READABLE);
|
|
|
|
g_object_class_install_properties(gobject_class, PROP_COUNT, ssb_scuttler_properties);
|
|
}
|
|
|
|
|
|
void
|
|
ssb_scuttler_whoami_async(SsbScuttler *scuttler,
|
|
GCancellable *cancellable,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
GTask *task;
|
|
GError *err = NULL;
|
|
|
|
g_return_if_fail(SSB_IS_SCUTTLER(scuttler));
|
|
g_return_if_fail((cancellable == NULL) || G_IS_CANCELLABLE(cancellable));
|
|
|
|
task = g_task_new(scuttler, cancellable, callback, user_data);
|
|
|
|
if (!scuttler->connection) {
|
|
g_task_return_new_error(task,
|
|
SSB_SCUTTLER_ERROR, SSB_SCUTTLER_ERROR_NOTCONNECTED,
|
|
"Scuttler is not connected");
|
|
g_object_unref(task);
|
|
|
|
return;
|
|
}
|
|
|
|
ssb_scuttler_muxrpc_call(scuttler, "whoami", NULL, SSB_RPC_ASYNC, NULL, &err);
|
|
}
|
|
|
|
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;
|
|
scuttler->request_id = 0;
|
|
}
|
|
|
|
SsbScuttler *
|
|
ssb_scuttler_get(void)
|
|
{
|
|
if (singleton == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
return g_object_ref(singleton);
|
|
}
|