summaryrefslogtreecommitdiff
path: root/src/commands.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/commands.c')
-rw-r--r--src/commands.c122
1 files changed, 108 insertions, 14 deletions
diff --git a/src/commands.c b/src/commands.c
index a587ce3..6100d6a 100644
--- a/src/commands.c
+++ b/src/commands.c
@@ -19,12 +19,16 @@
#include "ledger.h"
#include "log.h"
#include "reports.h"
+#include "secret.h"
#include "seed.h"
#include "sie.h"
#include "sru.h"
#include "util.h"
#include "version.h"
+/* src/secret.c is not part of CORE_SRC; compile it as part of this unit. */
+#include "secret.c"
+
/* ------------------------------------------------------------------ */
/* small helpers */
/* ------------------------------------------------------------------ */
@@ -2612,6 +2616,7 @@ static yyjson_mut_val *h_settings_get(struct req *r)
{
yyjson_mut_val *o = yyjson_mut_obj(r->rdoc);
int have_default = 0, have_bank = 0, have_receivable = 0, have_revenue = 0;
+ int have_password = 0, have_security = 0;
sqlite3_stmt *st = NULL;
if (sqlite3_prepare_v2(
r->db, "SELECT key,value FROM settings WHERE org_id=?1", -1, &st,
@@ -2621,6 +2626,10 @@ static yyjson_mut_val *h_settings_get(struct req *r)
const char *k = (const char *)sqlite3_column_text(st, 0);
const char *v = (const char *)sqlite3_column_text(st, 1);
if (k) {
+ if (secret_key_is_secret(k)) {
+ have_password = 1;
+ continue;
+ }
yyjson_mut_obj_add(o, yyjson_mut_strcpy(r->rdoc, k),
yyjson_mut_strcpy(r->rdoc, v ? v : ""));
if (strcmp(k, "default_series") == 0)
@@ -2631,6 +2640,8 @@ static yyjson_mut_val *h_settings_get(struct req *r)
have_receivable = 1;
if (strcmp(k, "invoice_revenue_account") == 0)
have_revenue = 1;
+ if (strcmp(k, "smtp_security") == 0)
+ have_security = 1;
}
}
sqlite3_finalize(st);
@@ -2645,24 +2656,100 @@ static yyjson_mut_val *h_settings_get(struct req *r)
if (!have_revenue)
yyjson_mut_obj_add_strcpy(r->rdoc, o, "invoice_revenue_account",
"3001");
+ if (!have_security)
+ yyjson_mut_obj_add_strcpy(r->rdoc, o, "smtp_security", "starttls");
+ yyjson_mut_obj_add_bool(r->rdoc, o, "smtp_password_set", have_password);
+ return o;
+}
+
+static yyjson_mut_val *settings_result(struct req *r, const char *key,
+ const char *value, int dry)
+{
+ yyjson_mut_val *o = yyjson_mut_obj(r->rdoc);
+ yyjson_mut_obj_add_strcpy(r->rdoc, o, "key", key);
+ yyjson_mut_obj_add_strcpy(r->rdoc, o, "value", value);
+ if (dry)
+ yyjson_mut_obj_add_bool(r->rdoc, o, "dry_run", true);
return o;
}
+static char *settings_secret_audit_json(const char *key)
+{
+ yyjson_mut_doc *doc = yyjson_mut_doc_new(NULL);
+ yyjson_mut_val *o = yyjson_mut_obj(doc);
+ yyjson_mut_doc_set_root(doc, o);
+ yyjson_mut_obj_add_strcpy(doc, o, "key", key);
+ yyjson_mut_obj_add_strcpy(doc, o, "value", "[redacted]");
+ char *json = yyjson_mut_write(doc, 0, NULL);
+ yyjson_mut_doc_free(doc);
+ return json ? json : xstrdup("{}");
+}
+
static yyjson_mut_val *h_settings_set(struct req *r)
{
const char *key = arg_str(r->args, "key");
const char *value = arg_str(r->args, "value");
if (!key || !value)
return fail(r, "INVALID_ARGS", "key and value are required");
+ if (secret_key_is_secret(key)) {
+ if (!secret_available())
+ return fail(r, "INTERNAL",
+ "BOKFD_SECRET_KEY is missing or invalid");
+ char *stored = NULL;
+ if (*value && secret_encrypt(value, &stored) != 0)
+ return fail(r, "INTERNAL",
+ "BOKFD_SECRET_KEY is missing or invalid");
+ if (r->dry_run) {
+ free(stored);
+ return settings_result(r, key, "[redacted]", 1);
+ }
+ sqlite3_stmt *st = NULL;
+ const char *sql = *value
+ ? "INSERT INTO settings(org_id,key,value)"
+ " VALUES(?1,?2,?3)"
+ " ON CONFLICT(org_id,key) DO UPDATE SET"
+ " value=excluded.value"
+ : "DELETE FROM settings WHERE org_id=?1"
+ " AND key=?2";
+ if (sqlite3_prepare_v2(r->db, sql, -1, &st, NULL) != SQLITE_OK) {
+ free(stored);
+ return fail(r, "INTERNAL", "database error");
+ }
+ sqlite3_bind_int64(st, 1, r->org_id);
+ sqlite3_bind_text(st, 2, key, -1, SQLITE_TRANSIENT);
+ if (*value)
+ sqlite3_bind_text(st, 3, stored, -1, SQLITE_TRANSIENT);
+ int rc = sqlite3_step(st);
+ sqlite3_finalize(st);
+ free(stored);
+ if (rc != SQLITE_DONE)
+ return fail(r, "INTERNAL", sqlite3_errmsg(r->db));
+ char *reqjson = settings_secret_audit_json(key);
+ audit_append(r->db, r->org_id, r->sess->user_id, r->sess->token_id,
+ "settings.set", reqjson, "OK", NULL);
+ free(reqjson);
+ return settings_result(r, key, "[redacted]", 0);
+ }
size_t maxlen;
- int digits_only = 0, bankgiro = 0;
+ int digits_only = 0, bankgiro = 0, port = 0, security = 0;
if (strcmp(key, "default_series") == 0)
maxlen = 8;
- else if (strcmp(key, "attachment_dir") == 0)
+ else if (strcmp(key, "attachment_dir") == 0 ||
+ strcmp(key, "smtp_host") == 0 ||
+ strcmp(key, "smtp_user") == 0)
maxlen = 255;
- else if (strcmp(key, "bank_account") == 0 ||
- strcmp(key, "invoice_receivable_account") == 0 ||
- strcmp(key, "invoice_revenue_account") == 0) {
+ else if (strcmp(key, "smtp_from") == 0 ||
+ strcmp(key, "smtp_reply_to") == 0)
+ maxlen = 254;
+ else if (strcmp(key, "smtp_port") == 0) {
+ maxlen = 5;
+ port = 1;
+ } else if (strcmp(key, "smtp_security") == 0) {
+ maxlen = 8;
+ security = 1;
+ } else if (strcmp(key, "bank_account") == 0 ||
+ strcmp(key, "invoice_receivable_account") == 0 ||
+ strcmp(key, "invoice_revenue_account") == 0) {
maxlen = 10;
digits_only = 1;
} else if (strcmp(key, "invoice_bankgiro") == 0) {
@@ -2675,7 +2762,7 @@ static yyjson_mut_val *h_settings_set(struct req *r)
return failf(r, "INVALID_ARGS", "%s must be 1-%zu characters", key,
maxlen);
for (const char *p = value; *p; p++) {
- if (digits_only && (*p < '0' || *p > '9'))
+ if ((digits_only || port) && (*p < '0' || *p > '9'))
return failf(r, "INVALID_ARGS", "%s must be digits only", key);
if (bankgiro && !((*p >= '0' && *p <= '9') || *p == '-'))
return failf(r, "INVALID_ARGS",
@@ -2684,13 +2771,17 @@ static yyjson_mut_val *h_settings_set(struct req *r)
return failf(r, "INVALID_ARGS",
"%s must not contain control characters", key);
}
- if (r->dry_run) {
- yyjson_mut_val *o = yyjson_mut_obj(r->rdoc);
- yyjson_mut_obj_add_strcpy(r->rdoc, o, "key", key);
- yyjson_mut_obj_add_strcpy(r->rdoc, o, "value", value);
- yyjson_mut_obj_add_bool(r->rdoc, o, "dry_run", true);
- return o;
+ if (port) {
+ long v = strtol(value, NULL, 10);
+ if (v < 1 || v > 65535)
+ return failf(r, "INVALID_ARGS", "%s must be 1-65535", key);
}
+ if (security && strcmp(value, "starttls") != 0 &&
+ strcmp(value, "tls") != 0 && strcmp(value, "plain") != 0)
+ return failf(r, "INVALID_ARGS", "%s must be starttls, tls or plain",
+ key);
+ if (r->dry_run)
+ return settings_result(r, key, value, 1);
sqlite3_stmt *st = NULL;
if (sqlite3_prepare_v2(
r->db,
@@ -7644,8 +7735,11 @@ static const struct cmd_arg args_bokslut_post[] = {
static const struct cmd_arg args_settings_set[] = {
{ "key", ARG_STR, 1, NULL, NULL,
"default_series, attachment_dir, bank_account,"
- " invoice_receivable_account or invoice_revenue_account" },
- { "value", ARG_STR, 1, NULL, NULL, "Setting value" },
+ " invoice_receivable_account, invoice_revenue_account, smtp_host,"
+ " smtp_port, smtp_user, smtp_from, smtp_reply_to, smtp_security or"
+ " smtp_password" },
+ { "value", ARG_STR, 0, NULL, NULL,
+ "Setting value; an empty value clears smtp_password" },
};
static const struct cmd_arg args_bank_import[] = {