Improve error reporting in dc-qcmd.
[doldaconnect.git] / daemon / conf.c
CommitLineData
d3372da9 1/*
2 * Dolda Connect - Modular multiuser Direct Connect-style client
302a2600 3 * Copyright (C) 2004 Fredrik Tolf <fredrik@dolda2000.com>
d3372da9 4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18*/
b5010caa 19
20#include <stdlib.h>
d3372da9 21#include <langinfo.h>
22#include <stdio.h>
23#include <unistd.h>
d3372da9 24#include <string.h>
25#include <sys/types.h>
26#include <errno.h>
27#include <wctype.h>
28#include <stddef.h>
29#include <wchar.h>
30#include <iconv.h>
31#include <arpa/inet.h>
05fe12f8 32#include <gdbm.h>
d3372da9 33
34#ifdef HAVE_CONFIG_H
35#include <config.h>
36#endif
37#include "conf.h"
38#include "log.h"
39#include "utils.h"
40
41static struct configmod *modules = NULL;
42
43#if 0
44static void dumpconfig(void)
45{
46 struct configmod *mod;
47 struct configvar *var;
48
49 for(mod = modules; mod != NULL; mod = mod->next)
50 {
51 printf("%s:\n", mod->name);
52 for(var = mod->vars; var->type != CONF_VAR_END; var++)
53 {
54 switch(var->type)
55 {
56 case CONF_VAR_BOOL:
57 printf("\t%s: %s\n", var->name, var->val.num?"t":"f");
58 break;
59 case CONF_VAR_INT:
60 printf("\t%s: %i\n", var->name, var->val.num);
61 break;
62 case CONF_VAR_STRING:
63 printf("\t%s: \"%ls\" (%i)\n", var->name, var->val.str, wcslen(var->val.str));
64 break;
65 case CONF_VAR_IPV4:
66 printf("\t%s: %s\n", var->name, inet_ntoa(var->val.ipv4));
67 break;
68 }
69 }
70 }
71}
72#endif
73
74struct configvar *confgetvar(char *modname, char *varname)
75{
76 struct configmod *m;
77 struct configvar *v;
78
79 for(m = modules; m != NULL; m = m->next)
80 {
81 if(!strcmp(m->name, modname))
82 {
83 for(v = m->vars; v->type != CONF_VAR_END; v++)
84 {
85 if(!strcmp(v->name, varname))
86 return(v);
87 }
88 break;
89 }
90 }
91 return(NULL);
92}
93
94void confregmod(struct configmod *mod)
95{
96 struct configvar *var;
97
98 for(var = mod->vars; var->type != CONF_VAR_END; var++)
99 {
100 switch(var->type)
101 {
102 case CONF_VAR_BOOL:
103 case CONF_VAR_INT:
104 var->val.num = var->defaults.num;
105 break;
106 case CONF_VAR_STRING:
107 if(var->defaults.str != NULL)
108 {
109 var->val.str = swcsdup(var->defaults.str);
110 } else {
111 var->val.str = NULL;
112 }
113 break;
114 case CONF_VAR_IPV4:
115 var->val.ipv4.s_addr = var->defaults.ipv4.s_addr;
116 break;
117 }
118 CBCHAININIT(var, conf_update);
119 }
120 mod->next = modules;
121 modules = mod;
122}
123
124int runconfcmd(int argc, wchar_t **argv)
125{
126 struct configmod *module;
127 struct configvar *var;
128 struct configcmd *cmd;
129 int ret, handled;
130 wchar_t *p;
131 char *cmdn, *buf, *buf2, *valbuf;
132 long num;
133 struct in_addr newipv4;
134 int cb;
135
136 if(argc < 1)
137 return(0);
138 if((cmdn = icwcstombs(argv[0], "us-ascii")) == NULL)
139 {
140 flog(LOG_WARNING, "could not convert %ls to us-ascii", argv[0]);
141 return(1);
142 }
143 ret = 1;
144 handled = 0;
145 if(!strcmp(cmdn, "set"))
146 {
147 handled = 1;
148 ret = 0;
149 if((p = wcschr(argv[1], L'.')) == NULL)
150 {
151 flog(LOG_WARNING, "illegal configuration variable format: %ls", argv[1]);
152 errno = EINVAL;
153 free(cmdn);
154 return(1);
155 }
156 *(p++) = L'\0';
157 if((buf = icwcstombs(argv[1], "us-ascii")) == NULL)
158 {
159 flog(LOG_WARNING, "could not convert %ls to us-ascii", argv[1]);
160 free(cmdn);
161 return(1);
162 }
163 if((buf2 = icwcstombs(p, "us-ascii")) == NULL)
164 {
165 free(buf);
166 flog(LOG_WARNING, "could not convert %ls to us-ascii", p);
167 free(cmdn);
168 return(1);
169 }
170 for(module = modules; module != NULL; module = module->next)
171 {
172 if(!strcmp(module->name, buf) && (module->vars != NULL))
173 {
174 for(var = module->vars; var->type != CONF_VAR_END; var++)
175 {
176 if(!strcmp(var->name, buf2))
177 {
178 cb = 0;
179 switch(var->type)
180 {
181 case CONF_VAR_BOOL:
182 wcstolower(argv[2]);
183 if(!wcscmp(argv[2], L"off") ||
184 !wcscmp(argv[2], L"false") ||
185 !wcscmp(argv[2], L"no") ||
186 !wcscmp(argv[2], L"0"))
187 {
188 if(var->val.num)
189 cb = 1;
190 var->val.num = 0;
191 } else if(!wcscmp(argv[2], L"on") ||
192 !wcscmp(argv[2], L"true") ||
193 !wcscmp(argv[2], L"yes") ||
194 !wcscmp(argv[2], L"1")) {
195 if(!var->val.num)
196 cb = 1;
197 var->val.num = 1;
198 } else {
199 flog(LOG_WARNING, "unrecognized boolean: %ls", argv[2]);
200 }
201 break;
202 case CONF_VAR_INT:
203 num = wcstol(argv[2], &p, 0);
204 if(p == argv[2])
205 {
206 flog(LOG_WARNING, "%ls: not a number, ignoring", argv[2]);
207 ret = 1;
208 } else {
209 if(*p != L'\0')
210 flog(LOG_WARNING, "%ls: could not entirely parse as a number, ignoring trailing garbage", argv[2]);
211 if(num != var->val.num)
212 cb = 1;
213 var->val.num = num;
214 }
215 break;
216 case CONF_VAR_STRING:
217 if(wcscmp(var->val.str, argv[2]))
218 cb = 1;
219 free(var->val.str);
220 var->val.str = swcsdup(argv[2]);
221 break;
222 case CONF_VAR_IPV4:
223 if((valbuf = icwcstombs(argv[2], "us-ascii")) == NULL)
224 {
225 flog(LOG_WARNING, "could not convert IPv4 address to as-ascii in var %s, ignoring", buf2);
226 } else {
227 if(!inet_aton(valbuf, &newipv4))
228 {
229 flog(LOG_WARNING, "could not parse IPv4 address (%s), ignoring", valbuf);
230 memcpy(&var->val.ipv4, &var->defaults.ipv4, sizeof(var->val.ipv4));
231 } else {
232 if(memcmp(&newipv4, &var->val.ipv4, sizeof(newipv4)))
233 cb = 1;
234 memcpy(&var->val.ipv4, &newipv4, sizeof(newipv4));
235 }
236 free(valbuf);
237 }
238 break;
239 }
240 if(cb)
241 CBCHAINDOCB(var, conf_update, var);
242 break;
243 }
244 }
245 if(var == NULL)
246 flog(LOG_WARNING, "variable %s not found, ignoring set command", buf2);
247 break;
248 }
249 }
250 if(module == NULL)
251 flog(LOG_WARNING, "module %s not found, ignoring set command", buf);
252 free(buf2);
253 free(buf);
254 }
255 for(module = modules; !handled && (module != NULL); module = module->next)
256 {
257 if(module->cmds != NULL)
258 {
259 for(cmd = module->cmds; cmd->name != NULL; cmd++)
260 {
261 if(!strcmp(cmd->name, cmdn))
262 {
263 handled = 1;
264 ret = cmd->handler(argc, argv);
265 break;
266 }
267 }
268 }
269 }
270 if(!handled)
271 flog(LOG_WARNING, "command not found: %s", cmdn);
272 free(cmdn);
273 return(ret);
274}
275
d3372da9 276void readconfig(FILE *stream)
277{
278 int state;
279 wint_t c;
280 wchar_t *words[16];
281 wchar_t *buf, *p, *p2;
282 int w;
283 int line;
284
285 buf = smalloc(sizeof(wchar_t) * 1024);
286 state = 0;
287 c = getwc(stream);
288 w = 0;
289 line = 1;
290 p = buf;
291 while(c != WEOF)
292 {
293 if(c == '#')
294 {
295 do
296 c = getwc(stream);
297 while((c != WEOF) && (c != L'\n'));
298 continue;
299 }
300 switch(state)
301 {
302 case 0:
303 if(iswspace(c))
304 {
305 if(c == L'\n')
306 {
307 line++;
308 if(runconfcmd(w, words))
309 flog(LOG_WARNING, "ignoring this command on line %i", line);
310 w = 0;
311 }
312 c = getwc(stream);
313 } else {
314 state = 1;
315 p2 = p;
316 }
317 break;
318 case 1:
319 if(c == L'\"')
320 {
321 state = 2;
322 c = getwc(stream);
323 } else if(iswspace(c)) {
324 if(w >= 16)
325 {
326 flog(LOG_WARNING, "too many words on config line %i, ignoring rest", line);
327 } else {
328 *(p++) = L'\0';
329 words[w++] = p2;
330 }
331 state = 0;
332 } else {
333 if(c == L'\\')
334 c = getwc(stream);
335 if(p - buf < 1023)
336 *(p++) = c;
337 else
338 flog(LOG_WARNING, "too many characters on config line %i, ignoring rest", line);
339 c = getwc(stream);
340 }
341 break;
342 case 2:
343 if(c == L'\"')
344 {
345 c = getwc(stream);
346 state = 1;
347 } else {
348 if(c == L'\\')
349 c = getwc(stream);
350 if(p - buf < 1023)
351 *(p++) = c;
352 else
353 flog(LOG_WARNING, "too many characters on config line %i, ignoring rest", line);
354 c = getwc(stream);
355 }
356 break;
357 }
358 }
359 free(buf);
360 if(ferror(stream))
361 flog(LOG_WARNING, "error on configuration stream: %s", strerror(errno));
362 if(state != 0)
363 flog(LOG_WARNING, "unexpected end of file");
364}
05fe12f8 365
366/* {store,fetch}var re-opens the database every time, just in case two
367 * doldacond processes would be running simultaneously. */
368void storevar(char *key, void *val, size_t len)
369{
370 char *dbname;
371 GDBM_FILE db;
372 datum k, v;
373
eb260dc7 374 dbname = findfile("dc-vardb", NULL, 1);
05fe12f8 375 if((db = gdbm_open(dbname, 0, GDBM_WRCREAT, 0666, NULL)) == NULL)
376 {
377 flog(LOG_CRIT, "could not open var database for writing, cannot continue: %s", gdbm_strerror(gdbm_errno));
378 abort();
379 }
380 free(dbname);
381 k.dptr = key;
382 k.dsize = strlen(key);
383 v.dptr = val;
384 v.dsize = len;
385 gdbm_store(db, k, v, GDBM_REPLACE);
386 gdbm_close(db);
387}
388
389void *fetchvar(char *key, size_t *lenb)
390{
391 char *dbname;
392 GDBM_FILE db;
393 datum k, v;
394
eb260dc7 395 if((dbname = findfile("dc-vardb", NULL, 0)) == NULL)
05fe12f8 396 return(NULL);
397 if((db = gdbm_open(dbname, 0, GDBM_READER, 0666, NULL)) == NULL)
398 return(NULL);
399 free(dbname);
400 k.dptr = key;
401 k.dsize = strlen(key);
402 v = gdbm_fetch(db, k);
403 gdbm_close(db);
404 if(v.dptr == NULL)
405 return(NULL);
406 if(lenb != NULL)
407 *lenb = v.dsize;
408 return(v.dptr);
409}