#include "outbox.h" // Submodules #include "ffdb/fs_list.h" #include "collections/array.h" #include "http/client/client.h" #include "ap/object.h" #include "rsa-signature-2017/rsa_signature_2017.h" // Model #include "model/server.h" #include "model/crypto/keys.h" #include "model/crypto/http_sign.h" #include "model/account.h" #include "model/activity.h" #include "model/outbox_envelope.h" // Stdlib #include #include #include #include static bool process_envelope( struct outbox_envelope* env ) { bool result = false; char* postdata = NULL; struct crypto_keys* keys = NULL; FILE* f = NULL; struct ap_object* act = NULL; struct account* to_account = NULL; if( env->sent ) { return false; } if( env->retry_after > time(NULL) ) { return false; } if( env->account_id == 0 ) { goto discard; } printf( "Processing outbox/%d.json\n", env->id ); printf( "account_id=%d\n", env->account_id ); printf( "activity_id=%d\n", env->activity_id ); // Get outbox URL to_account = account_from_id( env->account_id ); // Load crypto keys keys = crypto_keys_new(); if( !crypto_keys_load_private( keys, "data/owner/private.pem" ) ) { printf( "Failed to load private key\n" ); return false; } // Load target account act = activity_from_local_id( env->activity_id ); if( !act ) { printf( "! No activity\n" ); goto failed; } // Create signature ap_activity_create_rsa_signature_2017( act, keys ); // Create post data size_t size; { FILE* f2 = open_memstream( &postdata, &size ); ap_object_write_to_FILE( act, f2 ); fclose(f2); } // Force null termination postdata = realloc( postdata, size + 1 ); postdata[size] = '\0'; printf( "post: %s\n", postdata ); const char* inbox = to_account->inbox; if( env->shared_inbox ) { inbox = env->shared_inbox; } struct http_signature hs; if( !http_signature_make( inbox, keys, &hs, postdata ) ) { printf( "! Failed to make HTTP signature\n" ); goto failed; } char date_header[512]; snprintf( date_header, sizeof(date_header), "Date: %s", hs.date ); char sign_header[512]; snprintf( sign_header, sizeof(sign_header), "Signature: keyId=\"https://%s/owner/actor#mainKey\",headers=\"(request-target) host date content-length digest\",signature=\"%s\"", g_server->domain, hs.signature ); char user_agent[512]; snprintf( user_agent, sizeof(user_agent), "User-Agent: curl (Apogee/0.1; +https://%s/)", g_server->domain ); char digest_header[512]; snprintf( digest_header, sizeof(digest_header), "Digest: %s", hs.digest ); char content_length_header[512]; snprintf( content_length_header, sizeof(content_length_header), "Content-Length: %d", hs.content_length ); printf( "Performing post to %s\n", inbox ); long status_code = -1; const void* request[] = { HTTP_REQ_URL, inbox, HTTP_REQ_HEADER, user_agent, HTTP_REQ_HEADER, date_header, HTTP_REQ_HEADER, sign_header, HTTP_REQ_HEADER, content_length_header, HTTP_REQ_HEADER, digest_header, HTTP_REQ_HEADER, "Content-Type: application/activity+json", HTTP_REQ_POSTDATA, postdata, HTTP_REQ_RESULT_STATUS, &status_code, NULL }; // POST to inbox printf( "\n\nRequest result:\n" ); fflush(stdout); http_client_do(request); printf( "\n\n" ); fflush(stdout); http_signature_free( &hs ); if( status_code == 200 || status_code == 202 ) { printf( "Submitted successfully\n" ); goto discard; } else { printf( "\nServer returned status code %ld\n", status_code ); if( env->retry_after ) { env->retries += 1; } if( env->retries > 10 ) { // Force discard after 10 delivery attempts // TODO: change this to a configuration option goto discard; } env->retry_after = time(NULL) + 60 * ( env->retries + 1 ) * ( env->retries + 1 ); outbox_envelope_save(env); } goto failed; cleanup: ap_object_free(act); account_free(to_account); free(postdata); crypto_keys_free(keys); if( f ) { fclose(f); } return result; failed: result = false; goto cleanup; discard: env->sent = true; outbox_envelope_save(env); result = true; goto cleanup; } static bool process_pending() { int head = fs_list_get("data/outbox/HEAD"); int tail = fs_list_get("data/outbox/TAIL"); bool result = false; for( int i = tail + 1; i <= head; ++i ) { struct outbox_envelope* env = outbox_envelope_from_id( i ); if( !env ) { // Envelope file doesn't exist, advance tail fs_list_set( "data/outbox/TAIL", i ); tail += 1; } else if( env ) { if( env->sent && i == tail+1 ) { // Envelope already sent, advance tail fs_list_set( "data/outbox/TAIL", i ); tail += 1; } else if( process_envelope(env) ) { printf( "Done with outbox/%d.json\n", i ); result = true; } outbox_envelope_free(env); } fflush(stdout); } return result; } bool cleanup_box( const char* box ); extern bool terminate; void process_outbox() { while( !terminate ) { bool activity = false; activity |= process_pending(); activity |= cleanup_box("data/outbox"); if( !activity ) { fflush(stdout); sleep(1); } } }