Line data Source code
1 : /* SPDX-License-Identifier: GPL-3.0-or-later */
2 : /* Copyright 2026 Peter Csaszar */
3 :
4 : #include "config_store.h"
5 : #include "config.h"
6 : #include "fs_util.h"
7 : #include "platform/path.h"
8 : #include "raii.h"
9 : #include "logger.h"
10 : #include <stdio.h>
11 : #include <stdlib.h>
12 : #include <string.h>
13 : #include <ctype.h>
14 :
15 : #define CONFIG_APP_DIR "tg-cli"
16 : #define CONFIG_FILE "config.ini"
17 :
18 : /** @brief Trims leading and trailing whitespace from a string in-place. */
19 12 : static char* trim(char *str) {
20 : char *end;
21 12 : while (isspace((unsigned char)*str)) str++;
22 12 : if (*str == 0) return str;
23 12 : end = str + strlen(str) - 1;
24 12 : while (end > str && isspace((unsigned char)*end)) end--;
25 12 : end[1] = '\0';
26 12 : return str;
27 : }
28 :
29 : /** @brief Returns a heap-allocated path to the config file. Caller must free. */
30 6 : static char* get_config_path(void) {
31 6 : const char *config_base = platform_config_dir();
32 6 : if (!config_base) return NULL;
33 6 : char *path = NULL;
34 6 : if (asprintf(&path, "%s/%s/%s", config_base, CONFIG_APP_DIR, CONFIG_FILE) == -1) {
35 0 : return NULL;
36 : }
37 6 : return path;
38 : }
39 :
40 4 : Config* config_load_from_store(void) {
41 8 : RAII_STRING char *path = get_config_path();
42 4 : if (!path) return NULL;
43 :
44 8 : RAII_FILE FILE *fp = fopen(path, "r");
45 4 : if (!fp) return NULL;
46 :
47 3 : Config *cfg = calloc(1, sizeof(Config));
48 3 : if (!cfg) return NULL;
49 :
50 : char line[512];
51 9 : while (fgets(line, sizeof(line), fp)) {
52 6 : char *key = strtok(line, "=");
53 6 : char *val = strtok(NULL, "\n");
54 6 : if (key && val) {
55 6 : key = trim(key);
56 6 : val = trim(val);
57 6 : if (strcmp(key, "API_BASE") == 0) cfg->api_base = strdup(val);
58 3 : else if (strcmp(key, "TOKEN") == 0) cfg->token = strdup(val);
59 1 : else if (strcmp(key, "SSL_NO_VERIFY") == 0) cfg->ssl_no_verify = atoi(val);
60 : }
61 : }
62 :
63 : /* Set default API base if missing */
64 3 : if (!cfg->api_base) cfg->api_base = strdup("https://api.telegram.org");
65 :
66 3 : if (!cfg->token) {
67 1 : logger_log(LOG_WARN, "Config found but incomplete (missing TOKEN).");
68 1 : config_free(cfg);
69 1 : return NULL;
70 : }
71 :
72 2 : return cfg;
73 : }
74 :
75 2 : int config_save_to_store(const Config *cfg) {
76 2 : const char *config_base = platform_config_dir();
77 2 : if (!config_base) return -1;
78 :
79 : char dir_path[1024];
80 2 : snprintf(dir_path, sizeof(dir_path), "%s/%s", config_base, CONFIG_APP_DIR);
81 :
82 : /* Create directory with 0700 */
83 2 : if (fs_mkdir_p(dir_path, 0700) != 0) {
84 0 : logger_log(LOG_ERROR, "Failed to create config directory %s", dir_path);
85 0 : return -1;
86 : }
87 :
88 4 : RAII_STRING char *path = get_config_path();
89 2 : if (!path) {
90 0 : logger_log(LOG_ERROR, "Failed to determine config directory");
91 0 : return -1;
92 : }
93 4 : RAII_FILE FILE *fp = fopen(path, "w");
94 2 : if (!fp) {
95 0 : logger_log(LOG_ERROR, "Failed to open config file for writing: %s", path);
96 0 : return -1;
97 : }
98 :
99 : /* Set 0600 immediately */
100 2 : if (fs_ensure_permissions(path, 0600) != 0) {
101 0 : logger_log(LOG_WARN, "Failed to set strict permissions on %s", path);
102 : }
103 :
104 2 : fprintf(fp, "API_BASE=%s\n", cfg->api_base ? cfg->api_base : "https://api.telegram.org");
105 2 : fprintf(fp, "TOKEN=%s\n", cfg->token ? cfg->token : "");
106 2 : if (cfg->ssl_no_verify)
107 1 : fprintf(fp, "SSL_NO_VERIFY=1\n");
108 :
109 2 : logger_log(LOG_INFO, "Config saved to %s", path);
110 2 : return 0;
111 : }
|