1#include <limits.h>
2#include <stdio.h>
3#include <stdlib.h>
4#include <string.h>
5#include <strings.h>
6
7#include "config.h"
8#include "log.h"
9#include "users.h"
10
11static int load_path(const char* path);
12static int parse_bool(const char* s, bool* out);
13static int set_kv(const char* k, const char* v);
14static char* trim(char* s);
15
16/* defaults */
17char media_dir[4096] = "/mnt/sto/Media/";
18char server_addr[64] = "127.0.0.1";
19int server_port = 8088;
20bool verbose_log = true;
21char cors_origin[1024] = "";
22int http_io_timeout = 5;
23int max_clients = 64;
24int auth_delay = 250;
25
26/**
27 * @brief Load configuration from a file path
28 *
29 * @param path Path to config file
30 *
31 * @return 0=Success, -1=Failure
32 */
33static int load_path(const char* path)
34{
35 FILE* f = fopen(path, "r");
36 if (!f)
37 return -1;
38
39 char* line = NULL;
40 size_t cap = 0;
41 ssize_t n;
42
43 while ((n = getline(&line, &cap, f)) >= 0) {
44 (void)n;
45
46 char* s = trim(line);
47 if (s[0] == '\0')
48 continue;
49 if (s[0] == '#')
50 continue;
51
52 char* eq = strchr(s, '=');
53 if (!eq)
54 continue;
55
56 *eq = '\0';
57 char* k = trim(s);
58 char* v = trim(eq + 1);
59
60 if (k[0] == '\0')
61 continue;
62
63 if (set_kv(k, v) < 0)
64 LOG(true, "CONF", "Config Error %s=%s", k, v);
65 }
66
67 free(line);
68 fclose(f);
69 return 0;
70}
71
72/**
73 * @brief Parse bool string value
74 *
75 * @param s Input string
76 * @param out Output bool
77 *
78 * @return 0=Success, -1=Failure
79 */
80static int parse_bool(const char* s, bool* out)
81{
82 if (!s || !out)
83 return -1;
84
85 if (strcmp(s, "1") == 0 || strcasecmp(s, "true") == 0 || strcasecmp(s, "yes") == 0)
86 *out = true;
87 else if (strcmp(s, "0") == 0 || strcasecmp(s, "false") == 0 || strcasecmp(s, "no") == 0)
88 *out = false;
89 else
90 return -1;
91
92 return 0;
93}
94
95/**
96 * @brief Apply a config key/value pair
97 *
98 * @param k Config key
99 * @param v Config value
100 *
101 * @return 0=Success, -1=Failure
102 */
103static int set_kv(const char* k, const char* v)
104{
105 /* regular */
106 if (strcmp(k, "media_dir") == 0) {
107 snprintf(media_dir, sizeof(media_dir), "%s", v);
108 return 0;
109 }
110
111 if (strcmp(k, "server_addr") == 0) {
112 snprintf(server_addr, sizeof(server_addr), "%s", v);
113 return 0;
114 }
115
116 if (strcmp(k, "server_port") == 0) {
117 char* end = NULL;
118 long p = strtol(v, &end, 10);
119
120 if (end == v || *end != '\0') {
121 LOG(true, "CONF", "Config Error %s invalid integer '%s'", k, v);
122 return -1;
123 }
124 if (p < 1 || p > 65535) {
125 LOG(true, "CONF", "Config Error server_port out of bounds (1..65535)");
126 return -1;
127 }
128
129 server_port = (int)p;
130 return 0;
131 }
132
133 if (strcmp(k, "verbose_log") == 0) {
134 bool b;
135 if (parse_bool(v, &b) < 0){
136 LOG(true, "CONF", "Config Error verbose_log invalid bool \"%s\"", v);
137 return -1;
138 }
139
140 verbose_log = b;
141 return 0;
142 }
143
144 if (strcmp(k, "cors_origin") == 0) {
145 snprintf(cors_origin, sizeof(cors_origin), "%s", v);
146 return 0;
147 }
148
149 if (strcmp(k, "http_io_timeout") == 0) {
150 char* end = NULL;
151 long t = strtol(v, &end, 10);
152
153 if (end == v || *end != '\0') {
154 LOG(true, "CONF", "Config Error %s invalid integer '%s'", k, v);
155 return -1;
156 }
157 if (t < 1 || t > 300) {
158 LOG(true, "CONF", "Config Error http_io_timeout too %s (1..300)", t > 300 ? "high" : "low");
159 return -1;
160 }
161
162 http_io_timeout = (int)t;
163 return 0;
164 }
165
166 if (strcmp(k, "max_clients") == 0) {
167 char* end = NULL;
168 long n = strtol(v, &end, 10);
169
170 if (end == v || *end != '\0') {
171 LOG(true, "CONF", "Config Error %s invalid integer '%s'", k, v);
172 return -1;
173 }
174 if (n < 1 || n > 1024) {
175 LOG(true, "CONF", "Config Error max_clients too %s (1..1024)", n > 1024 ? "high" : "low");
176 return -1;
177 }
178
179 max_clients = (int)n;
180 return 0;
181 }
182
183 if (strcmp(k, "auth_delay") == 0) {
184 char* end = NULL;
185 long n = strtol(v, &end, 10);
186
187 if (end == v || *end != '\0') {
188 LOG(true, "CONF", "Config Error %s invalid integer '%s'", k, v);
189 return -1;
190 }
191 if (n < 0 || n > INT_MAX) {
192 LOG(true, "CONF", "Config Error auth_delay out of bounds (0..%d)", INT_MAX);
193 return -1;
194 }
195
196 auth_delay = (int)n;
197 return 0;
198 }
199
200 /* auth */
201 if (strcmp(k, "user") == 0) {
202 int r = users_push(v);
203 if (r < 0)
204 LOG(true, "CONF", "Config Error user invalid '%s'", v);
205 return r;
206 }
207
208 if (strcmp(k, "pass") == 0) {
209 int r = users_set_pass(v);
210 if (r < 0)
211 LOG(true, "CONF", "Config Error pass invalid '%s'", v);
212 return r;
213 }
214
215 if (strcmp(k, "allow") == 0) {
216 int r = users_add_allow(v);
217 if (r < 0)
218 LOG(true, "CONF", "Config Error allow invalid '%s'", v);
219 return r;
220 }
221
222 /* unknown key */
223 LOG(true, "CONF", "Unknown Key Value %s=%s", k, v);
224 return 0;
225}
226
227/**
228 * @brief Trim leading and trailing whitespace
229 *
230 * @param s Input string
231 *
232 * @return Ptr to trimmed string
233 */
234static char* trim(char* s)
235{
236 while (*s && (*s == ' ' || *s == '\t' || *s == '\r' || *s == '\n'))
237 s++;
238
239 char* e = s + strlen(s);
240 while (e > s) {
241 char c = e[-1];
242 if (c != ' ' && c != '\t' && c != '\r' && c != '\n')
243 break;
244 e--;
245 }
246 *e = '\0';
247 return s;
248}
249
250void config_load(void)
251{
252 const char* e = getenv("PARADOS_CONFIG");
253 const char* path = "";
254 if (e && e[0]) {
255 (void)load_path(e);
256 path = e;
257 goto log;
258 }
259
260 if (load_path("/etc/parados.conf") == 0) {
261 path = "/etc/parados.conf";
262 goto log;
263 }
264
265 if (load_path("./parados.conf") == 0) {
266 path = "./parados.conf";
267 goto log;
268 }
269
270 LOG(true, "CONF", "Unable to load configuration... using defaults");
271 return;
272
273log:
274 (void)0;
275
276 char port[8];
277 char tmo[16];
278 char maxc[16];
279 char adly[16];
280
281 LOG(true, "CONF", "Config File %s", path);
282 LOG(true, "CONF", "Media Directory %s", media_dir);
283 LOG(true, "CONF", "Server Address %s", server_addr);
284 snprintf(port, sizeof(port), "%d", server_port);
285 LOG(true, "CONF", "Server Port %s", port);
286 LOG(true, "CONF", "Verbose Logging %s", (verbose_log) ? "true" : "false");
287 LOG(true, "CONF", "CORS origins %s", cors_origin);
288 snprintf(tmo, sizeof(tmo), "%d", http_io_timeout);
289 LOG(true, "CONF", "HTTP IO Timeout %s", tmo);
290 snprintf(maxc, sizeof(maxc), "%d", max_clients);
291 LOG(true, "CONF", "Max Clients %s", maxc);
292 snprintf(adly, sizeof(adly), "%d", auth_delay);
293 LOG(true, "CONF", "Auth Delay %s (ms)", adly);
294
295 return;
296}
297