ssb-gtk/ssb-gtk/sbot.c

963 lines
27 KiB
C

#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_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
};
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);
}
/**
* 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;
}
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;
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);
if (connection == NULL) {
g_task_return_error(task, err);
g_object_unref(task);
return;
}
g_clear_object(&(scuttler->connection));
scuttler->connection = connection;
// 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;
}
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;
}
// 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,
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);
if (sodium_init() < 0) {
g_critical("Can not initialise sodium");
return;
}
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);
/**
* 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);
}
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;
}
SsbScuttler *
ssb_scuttler_get(void)
{
if (singleton == NULL) {
return NULL;
}
return g_object_ref(singleton);
}