#include "account.h" // Submodules #include "json/json.h" #include "json/layout.h" #include "http_client/client.h" #include "ffdb/fs_list.h" #include "ffdb/hash_index.h" // Model #include "model/server.h" #include "model/ap/account.h" #include "model/crypto/keys.h" // Stdlib #include #include #include #include #include static const char* safe( const char* value, const char* other ) { if( !value ) { return other; } return value; } static const char* b(bool value) { return value ? "true" : "false"; } static struct json_enum account_types_enum[] = { { "owner", at_owner }, { "bot", at_bot }, { "activity_pub", at_remote_activity_pub }, { "rss", at_rss_feed }, { NULL }, }; static struct json_object_field account_layout[] = { { "handle", offsetof( struct account, handle ), true, &json_field_string }, { "server", offsetof( struct account, server ), true, &json_field_string }, { "display_name", offsetof( struct account, display_name ), true, &json_field_string }, { "avatar", offsetof( struct account, avatar.url ), true, &json_field_string }, { "avatar_static", offsetof( struct account, avatar.static_url ), true, &json_field_string }, { "account_type", offsetof( struct account, account_type ), true, &json_field_enum, account_types_enum }, { "account_url", offsetof( struct account, account_url ), true, &json_field_string }, { "inbox", offsetof( struct account, inbox ), false, &json_field_string }, { NULL }, }; struct account* account_from_id( unsigned int id ) { char filename[512]; snprintf( filename, 512, "data/accounts/%d.json", id ); struct account* a = malloc(sizeof(struct account)); memset( a, 0, sizeof(struct account) ); a->id = id; if( !json_read_object_layout_from_file( filename, account_layout, a ) ) { account_free(a); return NULL; } return a; } static bool index_uri_to_account_id( const char* uri, int account_id ) { return hash_index_set( "data/accounts/uri_index/", uri, account_id ); } static int lookup_account_id_from_uri( const char* uri ) { int result = 0; if( !hash_index_get( "data/accounts/uri_index/", uri, &result ) ) { return -1; } return result; } struct account* account_from_uri( const char* uri ) { struct account* result = NULL; // Handle owner as special case char buffer[512]; snprintf( buffer, 512, "https://%s/owner/actor", g_server_name ); if( 0 == strcmp(buffer,uri) ) { return account_from_id(0); } // TODO: handle bots int account_id = lookup_account_id_from_uri( uri ); if( account_id == -1 ) { return NULL; } return account_from_id( account_id ); } void account_sync_from_acitvity_pub( unsigned int account_id ) { char filename[512]; snprintf( filename, 512, "data/accounts/%d/ap.json", account_id ); struct ap_account* ap = ap_account_from_file( filename ); //printf( "ap = " ); ap_account_debug_dump(ap); struct account* a = malloc(sizeof(struct account)); memset(a,0,sizeof(*a)); a->id = account_id; a->handle = strdup(ap->preferredUsername); a->display_name = strdup(ap->name); a->avatar.url = strdup(ap->avatar); a->avatar.static_url = strdup(ap->avatar); a->bot = ( ap->type != apacct_Person ); a->account_type = at_remote_activity_pub; a->account_url = strdup(ap->url); a->inbox = strdup(ap->inbox); if( 0 == strncmp( ap->id, "https://", 8 ) ) { char* server_name = strdup(&ap->id[8]); char* discard; strtok_r(server_name,"/",&discard); a->server = server_name; } // Extract out the public key char* id = strdup(ap->public_key.id); char* key_id = NULL; strtok_r( id, "#", &key_id ); snprintf( filename, sizeof(filename), "data/accounts/%d/%s.pem", a->id, key_id ); FILE* key_pem = fopen(filename,"w"); fprintf( key_pem, "%s", ap->public_key.pem ); fclose(key_pem); account_save(a); ap_account_free(ap); account_free(a); } struct crypto_keys* account_get_public_key( struct account* a, const char* key_name ) { char filename[512]; snprintf( filename, sizeof(filename), "data/accounts/%d/%s.pem", a->id, key_name ); FILE* f = fopen( filename, "r" ); if( !f ) { printf( "Failed to open file %s\n", filename ); return NULL; } fclose(f); struct crypto_keys* keys = crypto_keys_new(); if( crypto_keys_load_public( keys, filename ) ) { return keys; } printf( "Failed to load public key from %s\n", filename ); crypto_keys_free(keys); return NULL; } struct crypto_keys* account_get_private_key( struct account* a ) { return NULL; } static void create_account_skeleton( int account_id ) { // Make sure the account directory exists char dirname[512]; snprintf( dirname, 512, "data/accounts/%d", account_id ); mkdir( dirname, 0755 ); snprintf( dirname, 512, "data/accounts/%d/timeline", account_id ); mkdir( dirname, 0755 ); char filename[512]; snprintf( filename, 512, "data/accounts/%d/timeline/HEAD", account_id ); fs_list_set( filename, 0 ); } struct account* account_fetch_from_uri( const char* uri ) { int account_id = lookup_account_id_from_uri( uri ); if( -1 == account_id ) { account_id = fs_list_get( "data/accounts/HEAD" ) + 1; fs_list_set( "data/accounts/HEAD", account_id ); index_uri_to_account_id( uri, account_id ); } printf( "account_id = %d\n", account_id ); create_account_skeleton(account_id); // Fetch the ActivityPub actor data if we don't already have it char filename[512]; snprintf( filename, 512, "data/accounts/%d/ap.json", account_id ); FILE* f = fopen(filename,"r"); if( !f ) { char tmp_filename[512]; snprintf( tmp_filename, 512, "%s.tmp", filename ); printf( "tmp_filename = %s\n", tmp_filename ); f = fopen(tmp_filename,"w"); if( !f ) { printf( "Unable to open %s\n", tmp_filename ); return NULL; } const void* request[] = { HTTP_REQ_URL, uri, HTTP_REQ_HEADER, "Accept: application/json", HTTP_REQ_OUTFILE, f, NULL, }; if( !http_client_do( request ) ) { printf( "Unable to fetch %s\n", uri ); return NULL; } fclose(f); rename(tmp_filename,filename); } account_sync_from_acitvity_pub( account_id ); return account_from_id(account_id); } void account_free( struct account* a ) { if( !a ) { return; } free(a->handle); free(a->server); free(a->inbox); free(a->display_name); free(a->account_url); free(a->avatar.url); free(a->avatar.static_url); free(a); } void account_save( struct account* a ) { char filename[512]; snprintf( filename, 512, "data/accounts/%d.json", a->id ); json_write_object_layout_to_file( filename, "\t", account_layout, a ); } void account_add_follower( struct account* a, struct account* follower ) { printf( "TODO: implement account_add_follower()\n" ); } void account_remove_follower( struct account* a, struct account* follower ) { printf( "TODO: implement account_remove_follower()\n" ); } // TODO: move to controller/view void account_write_as_json( struct account* a, FILE* f ) { #define RENDER #include "src/model/account.json.inc" #undef RENDER }