You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
273 lines
6.8 KiB
C
273 lines
6.8 KiB
C
#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 <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <stddef.h>
|
|
#include <sys/stat.h>
|
|
|
|
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
|
|
}
|