diff --git a/GNUmakefile b/GNUmakefile index d14ba1da..71e404bb 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -18,7 +18,7 @@ MAKEFLAGS += --no-builtin-rules VERSION_CODE := 45 VERSION_CODE_IOS := 19 -VERSION_NUMBER := 0.2026.11-wip +VERSION_NUMBER := 0.2026.10.1 VERSION_NAME := This program kills fascists. IPHONEOS_VERSION_MIN=14.0 @@ -876,6 +876,10 @@ src/version.h : $(firstword $(MAKEFILE_LIST)) @echo "#define VERSION_NUMBER \"$(VERSION_NUMBER)\"" > $@ @echo "#define VERSION_NAME \"$(VERSION_NAME)\"" >> $@ +src/eula.h : core/eula.html + @echo "[xxd] $@" + @xxd -i -n k_eula $< $@ + src/android/AndroidManifest.xml : $(firstword $(MAKEFILE_LIST)) @echo "[android_version] $@" @sed -i \ diff --git a/core/eula.html b/core/eula.html new file mode 100644 index 00000000..ac3a2dce --- /dev/null +++ b/core/eula.html @@ -0,0 +1,67 @@ + + + + Tilde Friends End User License Agreement + + + + +

Tilde Friends End User License Agreement

+

+ Tilde Friends is an app that enables communication with other users + through the + Secure Scuttlebutt + protocol. +

+

You are responsible for your actions

+

+ Apple tolerates no objectionable content or abusive users on their + platforms. +

+

+ Your government, employer, family, friend group, etc. may impose + additional restrictions on acceptable behavior that you may be expected to + follow. +

+

+ You are solely responsible for your own actions within this app, + especially but not limited to what content you choose to post. +

+

You choose what you see

+

+ You are in full control of the content you see in Tilde Friends by the + peers to which you choose to connect and the users you choose to follow. + Initially you will be following no one with no connections and as a result + see no content from other users. +

+

+ If you find some content objectionable, you can filter it from your view + by blocking the user who posted it. This also makes it so that users + following you will not see it as a result of following you. +

+

+ The admin app contains a variety of settings that control the + types of connections Tilde Friends will make or accept, including whether + the app will even accept or make connections at all. +

+

This app is not a service

+

+ This app relies on no official servers. The author of this app has no more + ability to see or filter what you post or read than any other user of the + network. +

+

+ If you believe objectionable content obtained from a service that is + running as part of the Secure Scuttlebutt network should be flagged, look + for an email address contact for the service, generally by accessing the + server address in a web browser, and report it to the operator. +

+

Agree with these terms?

+

If you do not accept these terms, do not use this app.

+
+ Accept Agreement +
+ + diff --git a/docs/usage.md b/docs/usage.md index 3df02c73..c3912288 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -60,6 +60,7 @@ options: broadcast (default: true): Send network discovery broadcasts. discovery (default: true): Receive network discovery broadcasts. stay_connected (default: false): Whether to attempt to keep several peer connections open. + accepted_eula_crc (default: 0): The CRC32 of the last accepted EULA. -o, --one-proc Run everything in one process (unsafely!). -z, --zip path Zip archive from which to load files. -v, --verbose Log raw messages. diff --git a/src/android/AndroidManifest.xml b/src/android/AndroidManifest.xml index 597cb421..0757f449 100644 --- a/src/android/AndroidManifest.xml +++ b/src/android/AndroidManifest.xml @@ -2,7 +2,7 @@ + android:versionName="0.2026.10.1"> +#endif #define CYAN "\e[1;36m" #define MAGENTA "\e[1;35m" @@ -616,28 +622,99 @@ tf_httpd_user_app_t* tf_httpd_parse_user_app_from_path(const char* path, const c return result; } -static void _httpd_endpoint_root_callback(const char* path, void* user_data) +typedef struct _root_t { - tf_http_request_t* request = user_data; - const char* headers[] = { - "Location", - path ? path : "/~core/apps/", - }; - tf_http_respond(request, 303, headers, tf_countof(headers) / 2, NULL, 0); - tf_http_request_unref(request); -} + tf_http_request_t* request; + const char* path; +} root_t; -static void _httpd_endpoint_root(tf_http_request_t* request) +static void _httpd_root_work(tf_ssb_t* ssb, void* user_data) { + root_t* root = user_data; + tf_http_request_t* request = root->request; const char* host = tf_http_request_get_header(request, "x-forwarded-host"); if (!host) { host = tf_http_request_get_header(request, "host"); } + + bool require_eula = +#if TARGET_OS_IPHONE + true; +#else + false; +#endif + + int64_t accepted_eula_crc = 0; + uint32_t eula_crc = crc32(crc32(0, NULL, 0), k_eula, k_eula_len); + + sqlite3* db = tf_ssb_acquire_db_reader(ssb); + tf_ssb_db_get_global_setting_int64(db, "accepted_eula_crc", &accepted_eula_crc); + if (require_eula && accepted_eula_crc != eula_crc) + { + root->path = tf_strdup("/static/eula.html"); + } + else + { + root->path = tf_ssb_db_resolve_index(db, host); + } + tf_ssb_release_db_reader(ssb, db); +} + +static void _httpd_root_after_work(tf_ssb_t* ssb, int status, void* user_data) +{ + root_t* root = user_data; + tf_http_request_t* request = root->request; + const char* headers[] = { + "Location", + root->path ? root->path : "/~core/apps/", + }; + tf_http_respond(request, 303, headers, tf_countof(headers) / 2, NULL, 0); + tf_http_request_unref(request); + tf_free((void*)root->path); + tf_free(root); +} + +static void _httpd_endpoint_root(tf_http_request_t* request) +{ + root_t* root = tf_malloc(sizeof(root_t)); + *root = (root_t) { + .request = request, + }; + tf_http_request_ref(request); tf_task_t* task = request->user_data; tf_ssb_t* ssb = tf_task_get_ssb(task); + tf_ssb_run_work(ssb, _httpd_root_work, _httpd_root_after_work, root); +} + +static void _httpd_accept_eula_work(tf_ssb_t* ssb, void* user_data) +{ + uint32_t eula_crc = crc32(crc32(0, NULL, 0), k_eula, k_eula_len); + char buffer[64]; + snprintf(buffer, sizeof(buffer), "%u", eula_crc); + + sqlite3* db = tf_ssb_acquire_db_writer(ssb); + tf_ssb_db_set_global_setting_from_string(db, "accepted_eula_crc", buffer); + tf_ssb_release_db_writer(ssb, db); +} + +static void _httpd_accept_eula_after_work(tf_ssb_t* ssb, int status, void* user_data) +{ + tf_http_request_t* request = user_data; + const char* headers[] = { + "Location", + "/", + }; + tf_http_respond(request, 303, headers, tf_countof(headers) / 2, NULL, 0); + tf_http_request_unref(request); +} + +static void _httpd_endpoint_accept_eula(tf_http_request_t* request) +{ tf_http_request_ref(request); - tf_ssb_db_resolve_index_async(ssb, host, _httpd_endpoint_root_callback, request); + tf_task_t* task = request->user_data; + tf_ssb_t* ssb = tf_task_get_ssb(task); + tf_ssb_run_work(ssb, _httpd_accept_eula_work, _httpd_accept_eula_after_work, request); } static void _httpd_endpoint_robots_txt(tf_http_request_t* request) @@ -913,6 +990,7 @@ tf_http_t* tf_httpd_create(JSContext* context) tf_http_add_handler(http, "/login/logout", tf_httpd_endpoint_logout, NULL, task); tf_http_add_handler(http, "/login/auto", tf_httpd_endpoint_login_auto, NULL, task); tf_http_add_handler(http, "/login", tf_httpd_endpoint_login, NULL, task); + tf_http_add_handler(http, "/eula/accept", _httpd_endpoint_accept_eula, NULL, task); tf_http_add_handler(http, "/app/socket", tf_httpd_endpoint_app_socket, NULL, task); diff --git a/src/httpd.static.c b/src/httpd.static.c index 87e5a051..08796f97 100644 --- a/src/httpd.static.c +++ b/src/httpd.static.c @@ -124,6 +124,7 @@ void tf_httpd_endpoint_static(tf_http_request_t* request) const char* k_static_files[] = { "index.html", + "eula.html", "client.js", "tildefriends.svg", "jszip.min.js", diff --git a/src/ios/Info.plist b/src/ios/Info.plist index 1589b23f..3812f891 100644 --- a/src/ios/Info.plist +++ b/src/ios/Info.plist @@ -13,7 +13,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 0.2026.11 + 0.2026.10.1 CFBundleSupportedPlatforms iPhoneOS diff --git a/src/ssb.db.c b/src/ssb.db.c index 0cf4bc08..6f800293 100644 --- a/src/ssb.db.c +++ b/src/ssb.db.c @@ -2186,56 +2186,40 @@ bool tf_ssb_db_identity_get_active(sqlite3* db, const char* user, const char* pa return found; } -typedef struct _resolve_index_t +const char* tf_ssb_db_resolve_index(sqlite3* db, const char* host) { - const char* host; - const char* path; - void (*callback)(const char* path, void* user_data); - void* user_data; -} resolve_index_t; + const char* result = NULL; -static void _tf_ssb_db_resolve_index_work(tf_ssb_t* ssb, void* user_data) -{ - resolve_index_t* request = user_data; - - sqlite3* db = tf_ssb_acquire_db_reader(ssb); - sqlite3_stmt* statement; - if (sqlite3_prepare_v2(db, "SELECT json_extract(value, '$.index_map') FROM properties WHERE id = 'core' AND key = 'settings'", -1, &statement, NULL) == SQLITE_OK) + if (!result) { - if (sqlite3_step(statement) == SQLITE_ROW) - { - const char* index_map = (const char*)sqlite3_column_text(statement, 0); - const char* start = index_map; - while (start) - { - const char* end = strchr(start, '\n'); - const char* equals = strchr(start, '='); - if (equals && strncasecmp(request->host, start, equals - start) == 0) - { - size_t value_length = end && equals < end ? (size_t)(end - (equals + 1)) : strlen(equals + 1); - char* path = tf_malloc(value_length + 1); - memcpy(path, equals + 1, value_length); - path[value_length] = '\0'; - request->path = path; - break; - } - start = end ? end + 1 : NULL; - } - } - sqlite3_finalize(statement); - } - else - { - tf_printf("prepare failed: %s\n", sqlite3_errmsg(db)); + /* Maybe we need to force the EULA first. */ } - if (!request->path) + if (!result) { - if (sqlite3_prepare_v2(db, "SELECT json_extract(value, '$.index') FROM properties WHERE id = 'core' AND key = 'settings'", -1, &statement, NULL) == SQLITE_OK) + /* Use the index_map setting. */ + sqlite3_stmt* statement; + if (sqlite3_prepare_v2(db, "SELECT json_extract(value, '$.index_map') FROM properties WHERE id = 'core' AND key = 'settings'", -1, &statement, NULL) == SQLITE_OK) { if (sqlite3_step(statement) == SQLITE_ROW) { - request->path = tf_strdup((const char*)sqlite3_column_text(statement, 0)); + const char* index_map = (const char*)sqlite3_column_text(statement, 0); + const char* start = index_map; + while (start) + { + const char* end = strchr(start, '\n'); + const char* equals = strchr(start, '='); + if (equals && strncasecmp(host, start, equals - start) == 0) + { + size_t value_length = end && equals < end ? (size_t)(end - (equals + 1)) : strlen(equals + 1); + char* path = tf_malloc(value_length + 1); + memcpy(path, equals + 1, value_length); + path[value_length] = '\0'; + result = path; + break; + } + start = end ? end + 1 : NULL; + } } sqlite3_finalize(statement); } @@ -2244,32 +2228,32 @@ static void _tf_ssb_db_resolve_index_work(tf_ssb_t* ssb, void* user_data) tf_printf("prepare failed: %s\n", sqlite3_errmsg(db)); } } - tf_ssb_release_db_reader(ssb, db); - if (!request->path) + if (!result) { - request->path = tf_strdup(tf_util_get_default_global_setting_string("index")); + /* Use the index setting. */ + sqlite3_stmt* statement; + if (sqlite3_prepare_v2(db, "SELECT json_extract(value, '$.index') FROM properties WHERE id = 'core' AND key = 'settings'", -1, &statement, NULL) == SQLITE_OK) + { + if (sqlite3_step(statement) == SQLITE_ROW) + { + result = tf_strdup((const char*)sqlite3_column_text(statement, 0)); + } + sqlite3_finalize(statement); + } + else + { + tf_printf("prepare failed: %s\n", sqlite3_errmsg(db)); + } } -} -static void _tf_ssb_db_resolve_index_after_work(tf_ssb_t* ssb, int status, void* user_data) -{ - resolve_index_t* request = user_data; - request->callback(request->path, request->user_data); - tf_free((void*)request->host); - tf_free((void*)request->path); - tf_free(request); -} + if (!result) + { + /* Use the default index. */ + result = tf_strdup(tf_util_get_default_global_setting_string("index")); + } -void tf_ssb_db_resolve_index_async(tf_ssb_t* ssb, const char* host, void (*callback)(const char* path, void* user_data), void* user_data) -{ - resolve_index_t* request = tf_malloc(sizeof(resolve_index_t)); - *request = (resolve_index_t) { - .host = tf_strdup(host), - .callback = callback, - .user_data = user_data, - }; - tf_ssb_run_work(ssb, _tf_ssb_db_resolve_index_work, _tf_ssb_db_resolve_index_after_work, request); + return result; } static void _tf_ssb_db_set_flags(tf_ssb_t* ssb, const char* message_id, int flags) @@ -2514,7 +2498,7 @@ bool tf_ssb_db_set_global_setting_from_string(sqlite3* db, const char* name, cha bound = sqlite3_bind_int(statement, 2, value && (strcmp(value, "true") == 0 || atoi(value))) == SQLITE_OK; break; case k_kind_int: - bound = sqlite3_bind_int(statement, 2, atoi(value)) == SQLITE_OK; + bound = sqlite3_bind_int64(statement, 2, atoll(value)) == SQLITE_OK; break; case k_kind_string: bound = sqlite3_bind_text(statement, 2, value, -1, NULL) == SQLITE_OK; diff --git a/src/ssb.db.h b/src/ssb.db.h index e6aacae7..badc9a9b 100644 --- a/src/ssb.db.h +++ b/src/ssb.db.h @@ -445,12 +445,11 @@ bool tf_ssb_db_add_value_to_array_property(tf_ssb_t* ssb, const char* id, const /** ** Resolve a hostname to its index path by global settings. -** @param ssb The SSB instance. +** @param db The database. ** @param host The hostname. -** @param callback The callback. -** @param user_data The callback user data. +** @return The resolved index. Free with tf_free(). */ -void tf_ssb_db_resolve_index_async(tf_ssb_t* ssb, const char* host, void (*callback)(const char* path, void* user_data), void* user_data); +const char* tf_ssb_db_resolve_index(sqlite3* db, const char* host); /** ** Verify an author's feed. diff --git a/src/util.js.c b/src/util.js.c index 351342b0..b3d1badc 100644 --- a/src/util.js.c +++ b/src/util.js.c @@ -338,6 +338,7 @@ static const setting_t k_settings[] = { .type = "boolean", .description = "Whether to attempt to keep several peer connections open.", .default_value = { .kind = k_kind_bool, .bool_value = false } }, + { .name = "accepted_eula_crc", .type = "hidden", .description = "The CRC32 of the last accepted EULA.", .default_value = { .kind = k_kind_int, .int_value = 0 } }, }; static const setting_t* _util_get_setting(const char* name, tf_setting_kind_t kind) diff --git a/src/version.h b/src/version.h index 26d35b04..1b216591 100644 --- a/src/version.h +++ b/src/version.h @@ -1,2 +1,2 @@ -#define VERSION_NUMBER "0.2026.11-wip" +#define VERSION_NUMBER "0.2026.10.1" #define VERSION_NAME "This program kills fascists."