Implement server setup wizard

master
teknomunk 6 months ago
parent ce178cd5a9
commit 9d99318265

1
.gitignore vendored

@ -13,3 +13,4 @@ apogee
apogee.debug apogee.debug
/*.log /*.log
/*.json /*.json
obj/

@ -16,13 +16,17 @@ USE AT YOUR OWN RISK.
* ActivityPub federation - follow interesting people on the many instances of the fediverse. * ActivityPub federation - follow interesting people on the many instances of the fediverse.
# Dependencies # Dependencies
## Runtime
* libCURL * libCURL
* OpenSSL * OpenSSL
* Globaly resolvable domain name
* Tor
## Source Install/Development
* C Compiler (test with GCC) * C Compiler (test with GCC)
* Ruby (hopefully temporary) * Ruby (hopefully temporary)
* Globaly resolvable domain name
# Installation Instructions # Source Installation Instructions
Ensure you have system packages for the dependencies installed, then run the following code in Ensure you have system packages for the dependencies installed, then run the following code in
a terminal window: a terminal window:
@ -47,6 +51,6 @@ Starting section 5 (fetch)
```` ````
The listening port is chosen at random each time the program is run until setup, then the configured The listening port is chosen at random each time the program is run until setup, then the configured
port is used. Open a browser and navigate to http://${hostname-or-ip}:${port}/, then fill in the port is used. Open a browser and navigate to http://${hostnameOrIPAddress}:${port}/, then fill in the
required information. required information.

@ -2,14 +2,22 @@
// Model // Model
#include "src/model/server.h" #include "src/model/server.h"
#include "src/model/owner.h"
#include "src/model/account.h"
// View // View
// Controller // Controller
#include "src/controller/api/client_apps.h" #include "src/controller/api/client_apps.h"
// Submodules
#include "form.h"
#include "http/server/request.h" #include "http/server/request.h"
// Platform Headers
#include <string.h>
#include <stdlib.h>
const char* view_checkbox( bool value ) const char* view_checkbox( bool value )
{ {
return value ? "checked" : ""; return value ? "checked" : "";
@ -33,13 +41,132 @@ bool route_admin_request( struct http_request* req )
return false; return false;
} }
// Route: /admin/server-setup // Special: /, step=1
// Special: / (when server hasn't been configured) bool handle_admin_initial_owner_setup( struct http_request* req )
bool handle_admin_server_setup( struct http_request* req )
{ {
if( http_request_route_method( req, "POST" ) ) {
// TODO: handle post
FILE* body = http_request_get_request_data(req);
struct form_parser* fp = form_pull_parser_new(body);
if( !fp ) { goto show_owner_setup; }
struct owner* o = owner_new();
// Create owner account
struct account* owner = account_new();
owner->id = owner_account_id;
account_save(owner);
// Create home timeline account
{
struct account* home = account_new();
home->id = home_timeline_id;
home->handle = strdup("%home-timeline");
home->server = strdup("localhost");
account_save(home);
account_free(home);
}
// Create public timeline account
{
struct account* public = account_new();
public->id = public_timeline_id;
public->handle = strdup("%public-timeline");
public->server = strdup("localhost");
account_save(public);
account_free(public);
}
bool success = false;
char* password = NULL;
char* confirm = NULL;
char* key = NULL;
while( (key=form_pull_parser_read_key(fp)) ) {
if( 0 == strcmp(key,"password") ) {
password = strdup(form_pull_parser_read_value(fp));
} else if( 0 == strcmp(key,"confirm") ) {
confirm = strdup(form_pull_parser_read_value(fp));
} else if( 0 == strcmp(key,"handle") ) {
owner->handle = strdup(form_pull_parser_read_value(fp));
account_save(owner);
}
}
if( owner->handle && *owner->handle && password && confirm && ( 0 == strcmp(password,confirm) ) ) {
owner_set_password( o, password );
success = true;
}
form_pull_parser_release(fp);
if( success ) {
owner_save(o);
}
owner_free(o);
account_free(owner);
if( success ) {
// TODO: generate crypto keys
// Advance wizard to next step
g_server->configured = true;
app_args_save();
http_request_begin_send_headers( req, 302, false );
http_request_send_header( req, "Location", "/?complete" );
http_request_end_send_headers( req, false );
return true;
}
}
show_owner_setup:
http_request_send_headers( req, 200, "text/html", true ); http_request_send_headers( req, 200, "text/html", true );
FILE* f = http_request_get_response_body( req ); FILE* f = http_request_get_response_body( req );
#include "view/admin/server-setup.html.inc" #include "view/admin/owner-setup.html.inc"
return true; return true;
} }
// Route: /admin/server-setup
// Special: /, step=0 (when server hasn't been configured)
bool handle_admin_server_setup( struct http_request* req )
{
if( http_request_route_method( req, "POST" ) ) {
// TODO: handle post
FILE* body = http_request_get_request_data(req);
struct form_parser* fp = form_pull_parser_new(body);
if( !fp ) { return false; }
app_args_load_from_form( g_server, fp );
form_pull_parser_release(fp);
// Advance wizard to next step
if( g_server->setup_wizard_step == 0 ) {
g_server->setup_wizard_step = 1;
}
app_args_save();
app_args_load();
// Redirect
http_request_begin_send_headers( req, 302, false );
http_request_send_header( req, "Location", "/?account" );
http_request_end_send_headers( req, false );
return true;
} else {
http_request_send_headers( req, 200, "text/html", true );
FILE* f = http_request_get_response_body( req );
#include "view/admin/server-setup.html.inc"
return true;
}
}
bool handle_admin_server_setup_wizard( struct http_request* req )
{
switch(g_server->setup_wizard_step) {
case 0: return handle_admin_server_setup(req);
case 1: return handle_admin_initial_owner_setup(req);
}
return false;
}

@ -6,4 +6,5 @@ struct http_request;
bool route_admin_request( struct http_request* req ); bool route_admin_request( struct http_request* req );
bool handle_admin_server_setup( struct http_request* req ); bool handle_admin_server_setup( struct http_request* req );
bool handle_admin_server_setup_wizard( struct http_request* req );

@ -170,6 +170,9 @@ bool route_request( struct http_request* req )
char onion_location[512]; char onion_location[512];
snprintf( onion_location,512, "http://%s%s", g_server->tor_hidden_service, http_request_get_full_path(req) ); snprintf( onion_location,512, "http://%s%s", g_server->tor_hidden_service, http_request_get_full_path(req) );
http_request_add_header( req, "Onion-Location", onion_location ); http_request_add_header( req, "Onion-Location", onion_location );
#define SOURCE_URL "https://gitea.polaris-1.work/teknomunk/apogee"
#define LICENSE_URL "https://www.gnu.org/licenses/agpl-3.0.html"
http_request_add_header( req, "Link", "<" LICENSE_URL ">; rel=\"license\", <" SOURCE_URL ">; rel=\"source\"" );
bool inner( struct http_request* req ) { bool inner( struct http_request* req ) {
if( http_request_route( req, "/oauth" ) ) { if( http_request_route( req, "/oauth" ) ) {
@ -211,20 +214,26 @@ bool route_request( struct http_request* req )
return false; return false;
} }
if( http_request_route( req, "/api/v1/" ) ) { if( g_server->configured ) {
return route_mastodon_api( req ); if( http_request_route( req, "/api/v1/" ) ) {
} else if( http_request_route( req, "/api/pleroma" ) ) { return route_mastodon_api( req );
return route_pleroma_api2( req ); } else if( http_request_route( req, "/api/pleroma" ) ) {
} else if( http_request_route( req, "/admin" ) ) { return route_pleroma_api2( req );
return route_admin_request( req ); } else if( http_request_route( req, "/admin" ) ) {
} else if( inner( req ) ) { return route_admin_request( req );
return true; } else if( inner( req ) ) {
} else if( http_request_route_term( req, "/" ) && !g_server->configured ) { return true;
return handle_admin_server_setup( req ); } else {
return send_asset( req, "assets/soapbox/index.html" );
}
return false;
} else { } else {
return send_asset( req, "assets/soapbox/index.html" ); if( http_request_route_term( req, "/" ) || http_request_route( req, "/?") ) {
} return handle_admin_server_setup_wizard( req );
}
return false; return false;
}
} }

@ -3,6 +3,7 @@
#include "json/json.h" #include "json/json.h"
#include "json/layout.h" #include "json/layout.h"
#include "sha256/sha256.h" #include "sha256/sha256.h"
#include "form.h"
#include <stdlib.h> #include <stdlib.h>
#include <stdio.h> #include <stdio.h>
@ -32,10 +33,7 @@ struct owner* owner_new()
{ {
struct owner* o = allocate(sizeof(struct owner)); struct owner* o = allocate(sizeof(struct owner));
if( !json_read_object_layout_from_file( "data/owner.json", owner_layout, o ) ) { json_read_object_layout_from_file( "data/owner.json", owner_layout, o );
owner_free(o);
return NULL;
}
return o; return o;
} }
@ -46,15 +44,24 @@ void owner_free( struct owner* o )
free(o->password_hash); free(o->password_hash);
free(o); free(o);
} }
void owner_save( struct owner* o )
{
json_write_object_layout_to_file( "data/owner.json", "\t", owner_layout, o );
}
bool owner_check_password( struct owner* o, const char* passwd ) bool owner_check_password( struct owner* o, const char* passwd )
{ {
char buffer[512]; char buffer[512];
snprintf( buffer, 512, "%s:%s", o->password_salt, passwd ); snprintf( buffer, 512, "%s:%s", o->password_salt, passwd );
printf( "passwd='%s'\n", passwd );
char hash[65] = ""; char hash[65] = "";
sha256_easy_hash_hex( buffer, strlen(buffer), hash ); sha256_easy_hash_hex( buffer, strlen(buffer), hash );
printf( "hash=%s\n", hash );
printf( "expt=%s\n", o->password_hash );
return 0 == strcmp(hash,o->password_hash); return 0 == strcmp(hash,o->password_hash);
} }
@ -64,6 +71,7 @@ void owner_set_password( struct owner* o, const char* passwd )
free(o->password_hash); free(o->password_hash);
char* new_salt = o->password_salt = malloc(65); char* new_salt = o->password_salt = malloc(65);
memset(new_salt,0,65);
for( int i = 0; i < 64; ++i ) { for( int i = 0; i < 64; ++i ) {
new_salt[i] = 'a' + (rand()%26); new_salt[i] = 'a' + (rand()%26);
} }
@ -73,6 +81,10 @@ void owner_set_password( struct owner* o, const char* passwd )
snprintf( buffer, 512, "%s:%s", new_salt, passwd ); snprintf( buffer, 512, "%s:%s", new_salt, passwd );
char* new_hash = o->password_hash = malloc(65); char* new_hash = o->password_hash = malloc(65);
memset(new_hash,0,65);
sha256_easy_hash_hex( buffer, strlen(buffer), new_hash ); sha256_easy_hash_hex( buffer, strlen(buffer), new_hash );
o->password_hash = new_hash;
o->password_salt = new_salt;
} }

@ -15,4 +15,5 @@ void owner_free( struct owner* );
bool owner_check_password( struct owner* o, const char* passwd ); bool owner_check_password( struct owner* o, const char* passwd );
void owner_set_password( struct owner* o, const char* passwd ); void owner_set_password( struct owner* o, const char* passwd );
struct form_pull_parser;

@ -1,17 +1,20 @@
#include "server.h" #include "server.h"
#include "json/layout.h"
#include "controller/cli.h" #include "controller/cli.h"
#include "process.h" #include "process.h"
// Submodules
#include "json/layout.h"
#include "form.h"
// Platform libraries
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <stdio.h> #include <stdio.h>
#define OBJ_TYPE struct app_args #define OBJ_TYPE struct app_args
static struct json_object_field app_args_layout[] = { static struct json_object_field app_args_layout[] = {
JSON_FIELD_STRING( domain, true ), JSON_FIELD_STRING( icann_domain, false ),
JSON_FIELD_STRING( user_agent, false ), JSON_FIELD_STRING( user_agent, false ),
{ {
.key = "addr", .key = "addr",
@ -29,8 +32,10 @@ static struct json_object_field app_args_layout[] = {
JSON_FIELD_INTEGER( outbox_discard_limit, false ), JSON_FIELD_INTEGER( outbox_discard_limit, false ),
JSON_FIELD_BOOL( develop, false ), JSON_FIELD_BOOL( develop, false ),
JSON_FIELD_BOOL( enabled, false ), JSON_FIELD_BOOL( disabled, false ),
JSON_FIELD_BOOL( configured, false ), JSON_FIELD_BOOL( configured, false ),
JSON_FIELD_INTEGER( setup_wizard_step, false ),
JSON_FIELD_END, JSON_FIELD_END,
}; };
#undef OBJ_TYPE #undef OBJ_TYPE
@ -55,28 +60,40 @@ void app_args_refresh_tor_hidden_service()
} }
fclose(f); fclose(f);
} }
struct app_args* app_args_new( int argc, char** argv ) void app_args_load()
{ {
struct app_args* args = (struct app_args*)malloc( sizeof(struct app_args) ); g_server->http_settings.bind_port = 9000 + ( rand() % 41000 );
memset(args,0,sizeof(*args)); g_server->http_settings.bind_address = strdup("0.0.0.0");
if( !args ) { return NULL; } g_server->tor_socks_port = ( rand() % 41000 ) + 9000;
g_server->section = -1;
g_server->outbox_discard_limit = 5;
g_server->user_agent = strdup( "Apogee/0.1" );
g_server->disabled = true;
json_read_object_layout_from_file( "data/server.json", app_args_layout, g_server );
g_server = args; app_args_refresh_tor_hidden_service();
args->http_settings.bind_port = 9000 + ( rand() % 41000 ); if( g_server->icann_domain ) {
args->http_settings.bind_address = strdup("0.0.0.0"); g_server->domain = strdup(g_server->icann_domain);
args->tor_socks_port = ( rand() % 41000 ) + 9000; } else if( g_server->tor_hidden_service ) {
args->section = -1; g_server->domain = strdup(g_server->tor_hidden_service);
args->outbox_discard_limit = 5; }
args->user_agent = strdup( "Apogee/0.1" ); }
struct app_args* app_args_new( int argc, char** argv )
{
if( !g_server ) {
struct app_args* args = (struct app_args*)malloc( sizeof(struct app_args) );
memset(args,0,sizeof(*args));
if( !args ) { return NULL; }
json_read_object_layout_from_file( "data/server.json", app_args_layout, args ); g_server = args;
}
app_args_refresh_tor_hidden_service(); app_args_load();
if( ( argc > 1 ) && ( 0 != strncmp(argv[1],"--",2) ) ) { if( ( argc > 1 ) && ( 0 != strncmp(argv[1],"--",2) ) ) {
handle_command( argv, argc ); handle_command( argv, argc );
free(args);
return NULL; return NULL;
} }
@ -84,14 +101,14 @@ struct app_args* app_args_new( int argc, char** argv )
const char* arg = argv[i]; const char* arg = argv[i];
// Debug flag // Debug flag
if( 0 == strcmp(argv[i],"--debug") ) { args->debug = true; goto next_arg; } if( 0 == strcmp(argv[i],"--debug") ) { g_server->debug = true; goto next_arg; }
// Sections by number // Sections by number
if( sscanf(arg,"--section=%d",&args->section) ) { goto next_arg; } if( sscanf(arg,"--section=%d",&g_server->section) ) { goto next_arg; }
// Override server port // Override server port
if( sscanf(arg,"--listen=%d",&args->http_settings.bind_port) ) { if( sscanf(arg,"--listen=%d",&g_server->http_settings.bind_port) ) {
printf( "Listening on port %d\n", args->http_settings.bind_port ); printf( "Listening on port %d\n", g_server->http_settings.bind_port );
goto next_arg; goto next_arg;
} }
@ -99,7 +116,7 @@ struct app_args* app_args_new( int argc, char** argv )
if( ( argv[i][0] == '-' ) && ( argv[i][1] == '-' ) ) { if( ( argv[i][0] == '-' ) && ( argv[i][1] == '-' ) ) {
for( int i = 0; i <= process_get_max_section(); ++i ) { for( int i = 0; i <= process_get_max_section(); ++i ) {
if( strcmp( &argv[i][2], process_get_section_name(i) ) ) { if( strcmp( &argv[i][2], process_get_section_name(i) ) ) {
args->section = i; g_server->section = i;
goto next_arg; goto next_arg;
} }
} }
@ -107,32 +124,74 @@ struct app_args* app_args_new( int argc, char** argv )
// Development // Development
if( 0 == strcmp(argv[i],"--devel") ) { if( 0 == strcmp(argv[i],"--devel") ) {
args->section = 100; g_server->section = 100;
goto next_arg; goto next_arg;
} }
// Unknown argument // Unknown argument
printf( "Unknown argument: %s\n", argv[i] ); printf( "Unknown argument: %s\n", argv[i] );
free(args);
return NULL; return NULL;
next_arg:; next_arg:;
} }
return args; return g_server;
} }
void app_args_release( struct app_args* args ) void app_args_release( struct app_args* args )
{ {
free(args->http_settings.bind_address); free(args->http_settings.bind_address);
free(args->domain); free(args->domain);
free(args->icann_domain);
free(args->tor_hidden_service); free(args->tor_hidden_service);
free(args->user_agent); free(args->user_agent);
free(args); free(args);
} }
void app_args_save() void app_args_save()
{ {
// TODO: save server settings to disk // Save to disk
json_write_object_layout_to_file( "data/server.json", "\t", app_args_layout, g_server ); json_write_object_layout_to_file( "data/server.json", "\t", app_args_layout, g_server );
} }
void app_args_load_from_form( struct app_args* args, struct form_parser* fp )
{
char* key = NULL;
while( (key=form_pull_parser_read_key(fp)) != NULL ) {
for( struct json_object_field* jof = app_args_layout; jof->key; jof += 1 ) {
intptr_t field_offset = (intptr_t)args + jof->offset;
if( strcmp(jof->key, key) == 0 ) {
if( jof->type == &json_field_string ) {
const char* value = form_pull_parser_read_value(fp);
char** field = (char**)field_offset;
printf( "%s: %s\n", jof->key, value );
free( *field );
*field = NULL;
if( value ) {
*field = strdup(value);
}
} else if( jof->type == &json_field_integer ) {
const char* value = form_pull_parser_read_value(fp);
printf( "%s: %s\n", jof->key, value );
int* field = (int*)field_offset;
sscanf( value, "%d", field );
} else if( jof->type == &json_field_bool ) {
const char* value = form_pull_parser_read_value(fp);
printf( "%s: %s\n", jof->key, value );
bool* field = (bool*)field_offset;
if( strcmp(value,"on") == 0 ) {
*field = true;
} else {
*field = false;
}
} else {
printf( "Unknown type for field %s\n", jof->key );
}
}
}
}
}

@ -11,6 +11,7 @@ struct app_args
struct http_server_args http_settings; struct http_server_args http_settings;
//char* addr; //char* addr;
char* domain; char* domain;
char* icann_domain;
char* tor_hidden_service; char* tor_hidden_service;
char* user_agent; char* user_agent;
bool debug; bool debug;
@ -21,12 +22,17 @@ struct app_args
int outbox_discard_limit; int outbox_discard_limit;
bool develop; bool develop;
bool enabled; bool disabled;
bool configured; bool configured;
int setup_wizard_step;
}; };
struct app_args* app_args_new( int argc, char** argv ); struct app_args* app_args_new( int argc, char** argv );
void app_args_load();
void app_args_release( struct app_args* args ); void app_args_release( struct app_args* args );
void app_args_save(); void app_args_save();
struct form_parser;
void app_args_load_from_form( struct app_args* args, struct form_parser* fp );
extern struct app_args* g_server; extern struct app_args* g_server;

@ -0,0 +1,34 @@
<html>%(/*
vim: filetype=html
*/)
<body>
<h1>Welcome to Apogee</h1>
<p>
Apogee is a single-user Activity Pub federated server intended for self-hosting.
</p>
<h2>Account Settings</h2>
<form method="POST">
<table width='100%%'>
<tr>
<td width="15%%"><b>Username:</b></td>
<td><input name="handle" type="text" /></td>
<td></td>
</tr>
<tr>
<td><b>Password:</b></td>
<td><input name="password" type="password" /></td>
<td></td>
</tr>
<tr>
<td><b>Confirm Password:</b></td>
<td><input name="confirm" type="password" /></td>
<td></td>
</tr>
</table>
<br/>
<input type="submit" />
</form>
</body>
</html>

@ -8,13 +8,14 @@ Apogee is a single-user Activity Pub federated server intended for self-hosting.
</p> </p>
<h2>Server Settings</h2> <h2>Server Settings</h2>
<form method="POST" action="/admin/"> <form method="POST">
<h3>HTTP</h3> <h3>HTTP</h3>
<table width='100%%'> <table width='100%%'>
<tr> <tr>
<td width="15%%"><b>Domain Name:</b></td> <td width="15%%"><b>ICANN Domain Name:</b></td>
<td><input name="domain" type="text" value="%s{ g_server->domain ? g_server->domain : "" }"/></td> <td><input name="icann_domain" type="text" value="%s{ g_server->icann_domain ? g_server->icann_domain : "" }"/></td>
<td></td> <td></td>
</tr>
<tr> <tr>
<td><b>Listen Address:</b></td> <td><b>Listen Address:</b></td>
<td><input name="address" type="text" value="%s{ g_server->http_settings.bind_address ? g_server->http_settings.bind_address : "0.0.0.0" }"/></td> <td><input name="address" type="text" value="%s{ g_server->http_settings.bind_address ? g_server->http_settings.bind_address : "0.0.0.0" }"/></td>
@ -64,8 +65,8 @@ Apogee is a single-user Activity Pub federated server intended for self-hosting.
<td>Change server behavior to suit developers</td> <td>Change server behavior to suit developers</td>
</tr> </tr>
<tr> <tr>
<td><b>Server Enabled:</b></td> <td><b>Server Disabled:</b></td>
<td><input name="enabled" type="checkbox" %s{ view_checkbox( g_server->enabled ) }/></td> <td><input name="disabled" type="checkbox" %s{ view_checkbox( g_server->disabled ) }/></td>
<td>Turn the entire server off</td> <td>Turn the entire server off</td>
</tr> </tr>
</table> </table>

Loading…
Cancel
Save