From ed1c15929d2eb2dbc6432986c26661bf1549964a Mon Sep 17 00:00:00 2001 From: Anders Betts Date: Thu, 17 Sep 2026 21:26:20 +0200 Subject: Add native TLS transport, TLS clients and lego cert sidecar - bokfd: optional TLS listener (OpenSSL), certificate reload on change - clients: tls:host:port targets with chain and host verification - compose: port 8788 and an INWX/lego renewal sidecar - Makefile: header dependency tracking (-MMD -MP) --- clients/bokftui.c | 112 ++++++++++++++++++++++++++++-------------------------- 1 file changed, 58 insertions(+), 54 deletions(-) (limited to 'clients/bokftui.c') diff --git a/clients/bokftui.c b/clients/bokftui.c index 1939eeb..c26d188 100644 --- a/clients/bokftui.c +++ b/clients/bokftui.c @@ -32,7 +32,7 @@ static int ui_getch(void) } struct app { - int fd; + struct client_conn conn; char socket[256]; char session[128]; char username[64]; @@ -895,10 +895,10 @@ static void app_refresh_context(struct app *a) { char args[64]; snprintf(args, sizeof args, "{\"org\":%lld}", (long long)a->org); - char *resp = client_rpc(a->fd, "session.use_org", a->session, 0, args); + char *resp = client_rpc(&a->conn, "session.use_org", a->session, 0, args); free(resp); - resp = client_rpc(a->fd, "org.get", a->session, a->org, "{}"); + resp = client_rpc(&a->conn, "org.get", a->session, a->org, "{}"); if (resp && client_ok(resp)) { char *name = jstr_dup(resp, "result.name"); if (name) { @@ -908,7 +908,7 @@ static void app_refresh_context(struct app *a) } free(resp); - resp = client_rpc(a->fd, "org.list", a->session, 0, "{}"); + resp = client_rpc(&a->conn, "org.list", a->session, 0, "{}"); if (resp) { size_t n = jarr_size(resp, "result.items"); for (size_t i = 0; i < n; i++) { @@ -928,7 +928,7 @@ static void app_refresh_context(struct app *a) snprintf(a->default_series, sizeof a->default_series, "%s", "A"); a->attachment_dir[0] = '\0'; - resp = client_rpc(a->fd, "settings.get", a->session, a->org, "{}"); + resp = client_rpc(&a->conn, "settings.get", a->session, a->org, "{}"); if (resp && client_ok(resp)) { char *ser = jstr_dup(resp, "result.default_series"); if (ser && *ser) @@ -942,7 +942,7 @@ static void app_refresh_context(struct app *a) free(resp); a->max_attachment_bytes = 10 * 1024 * 1024; - resp = client_rpc(a->fd, "meta", NULL, 0, NULL); + resp = client_rpc(&a->conn, "meta", NULL, 0, NULL); if (resp && client_ok(resp)) { int64_t v = jint_val(resp, "result.limits.max_attachment_bytes", 0); @@ -951,7 +951,7 @@ static void app_refresh_context(struct app *a) } free(resp); - resp = client_rpc(a->fd, "fiscal_year.get", a->session, a->org, "{}"); + resp = client_rpc(&a->conn, "fiscal_year.get", a->session, a->org, "{}"); if (resp && client_ok(resp)) { a->fy = jint_val(resp, "result.id", 0); char *label = jstr_dup(resp, "result.label"); @@ -983,7 +983,7 @@ static int64_t fy_new_form(struct app *a) { char label[32] = "", start[16] = "", end[16] = ""; char *resp = - client_rpc(a->fd, "fiscal_year.list", a->session, a->org, "{}"); + client_rpc(&a->conn, "fiscal_year.list", a->session, a->org, "{}"); if (resp && client_ok(resp)) { size_t n = jarr_size(resp, "result.items"); char max_end[16] = ""; @@ -1098,7 +1098,7 @@ static int64_t fy_new_form(struct app *a) yyjson_mut_obj_add_strcpy(d, o, "end_date", end); char *args = yyjson_mut_write(d, 0, NULL); yyjson_mut_doc_free(d); - resp = client_rpc(a->fd, "fiscal_year.open", a->session, a->org, + resp = client_rpc(&a->conn, "fiscal_year.open", a->session, a->org, args); free(args); if (resp && client_ok(resp)) { @@ -1131,7 +1131,7 @@ static void select_fiscal_year(struct app *a) if (g_quit) return; char *resp = - client_rpc(a->fd, "fiscal_year.list", a->session, a->org, "{}"); + client_rpc(&a->conn, "fiscal_year.list", a->session, a->org, "{}"); if (!resp || !client_ok(resp)) { show_error("Räkenskapsår", resp); free(resp); @@ -1375,7 +1375,7 @@ static int voucher_detail(struct app *a, int64_t id, int64_t *out_new) snprintf(args, sizeof args, "{\"id\":%lld}", (long long)id); for (;;) { char *resp = - client_rpc(a->fd, "voucher.get", a->session, a->org, args); + client_rpc(&a->conn, "voucher.get", a->session, a->org, args); if (!resp || !client_ok(resp)) { show_error("Verifikat", resp); free(resp); @@ -1494,7 +1494,7 @@ static int voucher_detail(struct app *a, int64_t id, int64_t *out_new) yyjson_mut_obj_add_strcpy(d, o, "client_ref", ref); char *cargs = yyjson_mut_write(d, 0, NULL); yyjson_mut_doc_free(d); - char *r = client_rpc(a->fd, "voucher.correct", a->session, + char *r = client_rpc(&a->conn, "voucher.correct", a->session, a->org, cargs); if (r && client_ok(r)) { int64_t new_id = jint_val(r, "result.id", 0); @@ -1612,7 +1612,7 @@ static void acct_cache_load(struct app *a) if (g_accts.loaded && g_accts.org == a->org) return; acct_cache_free(); - char *resp = client_rpc(a->fd, "account.list", a->session, a->org, + char *resp = client_rpc(&a->conn, "account.list", a->session, a->org, "{\"active_only\":true}"); if (!resp || !client_ok(resp)) { free(resp); @@ -1844,7 +1844,7 @@ static int64_t vouchers_new(struct app *a) } if (ch == KEY_F(4)) { char tname[128] = ""; - char *lresp = client_rpc(a->fd, "template.list", a->session, + char *lresp = client_rpc(&a->conn, "template.list", a->session, a->org, "{\"active_only\":true}"); if (!lresp || !client_ok(lresp)) { show_error("Mallar", lresp); @@ -1901,7 +1901,7 @@ static int64_t vouchers_new(struct app *a) yyjson_mut_obj_add_strcpy(d, to, "name", tbuf); char *targs = yyjson_mut_write(d, 0, NULL); yyjson_mut_doc_free(d); - char *resp = client_rpc(a->fd, "template.get", a->session, + char *resp = client_rpc(&a->conn, "template.get", a->session, a->org, targs); free(targs); if (!resp || !client_ok(resp)) { @@ -2030,8 +2030,8 @@ static int64_t vouchers_new(struct app *a) a->session, (long long)a->org, args); free(args); char *r = NULL; - if (client_send_line(a->fd, raw) == 0) - r = client_read_line(a->fd); + if (client_send_line(&a->conn, raw) == 0) + r = client_read_line(&a->conn); free(raw); if (r && client_ok(r)) { att_ids[natt] = jint_val(r, "result.id", 0); @@ -2107,8 +2107,8 @@ static int64_t vouchers_new(struct app *a) "\"session\":\"%s\",\"org\":%lld,\"dry_run\":true," "\"args\":%s}", a->session, (long long)a->org, args); - if (client_send_line(a->fd, raw) == 0) { - char *r = client_read_line(a->fd); + if (client_send_line(&a->conn, raw) == 0) { + char *r = client_read_line(&a->conn); if (r && client_ok(r)) { int64_t num = jint_val(r, "result.number", 0); message("Validering OK", @@ -2122,7 +2122,7 @@ static int64_t vouchers_new(struct app *a) } free(raw); } else { - char *r = client_rpc(a->fd, "voucher.post", a->session, a->org, + char *r = client_rpc(&a->conn, "voucher.post", a->session, a->org, args); if (r && client_ok(r)) { int64_t id = jint_val(r, "result.id", 0); @@ -2221,7 +2221,7 @@ static void vouchers_screen(struct app *a) snprintf(largs, sizeof largs, "{\"limit\":200,\"fiscal_year\":%lld}", (long long)a->fy); - char *resp = client_rpc(a->fd, "voucher.list", a->session, a->org, + char *resp = client_rpc(&a->conn, "voucher.list", a->session, a->org, largs); if (!resp || !client_ok(resp)) { show_error("Verifikat", resp); @@ -2401,7 +2401,7 @@ static const char *vat_display(const char *name, const char *vat_code, static void accounts_report(struct app *a) { for (;;) { - char *resp = client_rpc(a->fd, "account.list", a->session, a->org, + char *resp = client_rpc(&a->conn, "account.list", a->session, a->org, "{\"active_only\":false}"); if (!resp || !client_ok(resp)) { show_error("Kontolista", resp); @@ -2523,7 +2523,7 @@ static void reports_screen(struct app *a) } } for (;;) { - char *resp = client_rpc(a->fd, cmd, a->session, a->org, args); + char *resp = client_rpc(&a->conn, cmd, a->session, a->org, args); if (!resp || !client_ok(resp)) { show_error("Rapport", resp); free(resp); @@ -2550,7 +2550,7 @@ static void inbox_screen(struct app *a) for (;;) { if (g_quit) return; - char *resp = client_rpc(a->fd, "attachment.list", a->session, a->org, + char *resp = client_rpc(&a->conn, "attachment.list", a->session, a->org, "{\"unlinked\":true,\"limit\":200}"); if (!resp || !client_ok(resp)) { show_error("Underlag", resp); @@ -2639,8 +2639,8 @@ static void inbox_screen(struct app *a) a->session, (long long)a->org, args); free(args); char *r = NULL; - if (client_send_line(a->fd, raw) == 0) - r = client_read_line(a->fd); + if (client_send_line(&a->conn, raw) == 0) + r = client_read_line(&a->conn); free(raw); if (r && client_ok(r)) message("Underlag", "Sparat."); @@ -2663,7 +2663,7 @@ static void audit_screen(struct app *a) return; if (sel == 0) { char *resp = - client_rpc(a->fd, "audit.verify", a->session, a->org, "{}"); + client_rpc(&a->conn, "audit.verify", a->session, a->org, "{}"); if (!resp || !client_ok(resp)) { show_error("Revision", resp); free(resp); @@ -2677,7 +2677,7 @@ static void audit_screen(struct app *a) free(resp); } else { for (;;) { - char *resp = client_rpc(a->fd, "audit.list", a->session, + char *resp = client_rpc(&a->conn, "audit.list", a->session, a->org, "{\"limit\":200}"); if (!resp || !client_ok(resp)) { show_error("Revision", resp); @@ -2779,7 +2779,7 @@ static int template_form(struct app *a, const char *load_name) char *args = yyjson_mut_write(d, 0, NULL); yyjson_mut_doc_free(d); char *resp = - client_rpc(a->fd, "template.get", a->session, a->org, args); + client_rpc(&a->conn, "template.get", a->session, a->org, args); free(args); if (!resp || !client_ok(resp)) { show_error("Mall", resp); @@ -3037,7 +3037,7 @@ static int template_form(struct app *a, const char *load_name) char *args = yyjson_mut_write(d, 0, NULL); yyjson_mut_doc_free(d); const char *cmd = tpl_id ? "template.update" : "template.create"; - char *resp = client_rpc(a->fd, cmd, a->session, a->org, args); + char *resp = client_rpc(&a->conn, cmd, a->session, a->org, args); free(args); if (resp && client_ok(resp)) { if (ch == KEY_F(5)) { @@ -3106,7 +3106,7 @@ static void template_archive_ui(struct app *a) char *args = yyjson_mut_write(d, 0, NULL); yyjson_mut_doc_free(d); char *resp = - client_rpc(a->fd, "template.archive", a->session, a->org, args); + client_rpc(&a->conn, "template.archive", a->session, a->org, args); free(args); if (resp && client_ok(resp)) message("Mall", "Mallen '%s' arkiverad.", name); @@ -3170,7 +3170,7 @@ static int ib_load(struct app *a, char ***out_acc, int64_t **out_amt, "\"limit\":200}", (long long)a->fy); char *resp = - client_rpc(a->fd, "voucher.list", a->session, a->org, args); + client_rpc(&a->conn, "voucher.list", a->session, a->org, args); if (!resp || !client_ok(resp)) { show_error("Ingående balans", resp); free(resp); @@ -3188,7 +3188,7 @@ static int ib_load(struct app *a, char ***out_acc, int64_t **out_amt, continue; char vargs[64]; snprintf(vargs, sizeof vargs, "{\"id\":%lld}", (long long)id); - char *v = client_rpc(a->fd, "voucher.get", a->session, a->org, vargs); + char *v = client_rpc(&a->conn, "voucher.get", a->session, a->org, vargs); if (!v || !client_ok(v)) { free(v); continue; @@ -3474,7 +3474,7 @@ static int ib_form(struct app *a, char **old_acc, int64_t *old_amt, int nold) char *args = yyjson_mut_write(d, 0, NULL); yyjson_mut_doc_free(d); char *resp = - client_rpc(a->fd, "voucher.post", a->session, a->org, args); + client_rpc(&a->conn, "voucher.post", a->session, a->org, args); free(args); if (resp && client_ok(resp)) { int64_t num = jint_val(resp, "result.number", 0); @@ -3618,7 +3618,7 @@ static void settings_screen(struct app *a) if (g_quit) return; char *resp = - client_rpc(a->fd, "settings.get", a->session, a->org, "{}"); + client_rpc(&a->conn, "settings.get", a->session, a->org, "{}"); if (!resp || !client_ok(resp)) { show_error("Inställningar", resp); free(resp); @@ -3673,7 +3673,7 @@ static void settings_screen(struct app *a) yyjson_mut_obj_add_strcpy(d, o, "value", val); char *args = yyjson_mut_write(d, 0, NULL); yyjson_mut_doc_free(d); - char *r = client_rpc(a->fd, "settings.set", a->session, + char *r = client_rpc(&a->conn, "settings.set", a->session, a->org, args); free(args); if (r && client_ok(r)) { @@ -3731,7 +3731,7 @@ static void templates_screen(struct app *a) continue; } for (;;) { - char *resp = client_rpc(a->fd, "template.list", a->session, + char *resp = client_rpc(&a->conn, "template.list", a->session, a->org, "{\"active_only\":true}"); if (!resp || !client_ok(resp)) { show_error("Mallar", resp); @@ -3936,22 +3936,26 @@ static int login_screen(struct app *a) /* attempt login */ snprintf(a->socket, sizeof a->socket, "%s", socket_path); - a->fd = client_connect(a->socket); - if (a->fd < 0) { - message("Fel", "Kunde inte ansluta till %s", a->socket); + if (client_connect(a->socket, &a->conn) != 0) { + message("Fel", "Kunde inte ansluta till %s: %s", a->socket, + client_last_error()); continue; } char *err = NULL, *session = NULL; - if (client_login(a->fd, user, pass, &session, &err) != 0) { - char *code = jstr_dup(err, "error.code"); - char *msg = jstr_dup(err, "error.message"); - message("Inloggning misslyckades", "%s: %s", - code ? code : "fel", msg ? msg : "okänt fel"); - free(code); - free(msg); + if (client_login(&a->conn, user, pass, &session, &err) != 0) { + if (err && err[0] == '{') { + char *code = jstr_dup(err, "error.code"); + char *msg = jstr_dup(err, "error.message"); + message("Inloggning misslyckades", "%s: %s", + code ? code : "fel", msg ? msg : "okänt fel"); + free(code); + free(msg); + } else { + message("Inloggning misslyckades", "%s", + err ? err : "transportfel"); + } free(err); - close(a->fd); - a->fd = -1; + client_close(&a->conn); continue; } snprintf(a->session, sizeof a->session, "%s", session); @@ -3963,7 +3967,7 @@ static int login_screen(struct app *a) static int select_org(struct app *a) { - char *resp = client_rpc(a->fd, "session.list_orgs", a->session, 0, "{}"); + char *resp = client_rpc(&a->conn, "session.list_orgs", a->session, 0, "{}"); if (!resp || !client_ok(resp)) { show_error("Organisationer", resp); free(resp); @@ -4010,7 +4014,7 @@ static void usage(FILE *f) { fprintf(f, "usage: bokftui [options]\n" - " --socket TARGET unix socket or tcp:host:port\n" + " --socket TARGET unix path, tcp:host:port or tls:host:port\n" " --user NAME prefill username\n" " --org ID select org directly\n" " --version\n"); @@ -4020,7 +4024,7 @@ int main(int argc, char **argv) { struct app app; memset(&app, 0, sizeof app); - app.fd = -1; + app.conn.fd = -1; const char *socket = getenv("BOKFD_SOCKET"); if (!socket) socket = "/run/bokfd/bokfd.sock"; @@ -4068,9 +4072,9 @@ int main(int argc, char **argv) update_status(&app); dashboard(&app); - client_rpc(app.fd, "session.close", app.session, 0, "{}"); + client_rpc(&app.conn, "session.close", app.session, 0, "{}"); acct_cache_free(); - close(app.fd); + client_close(&app.conn); endwin(); return 0; } -- cgit v1.3