Improve error reporting in dc-qcmd.
[doldaconnect.git] / daemon / conf.c
1 /*
2  *  Dolda Connect - Modular multiuser Direct Connect-style client
3  *  Copyright (C) 2004 Fredrik Tolf <fredrik@dolda2000.com>
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 */
19
20 #include <stdlib.h>
21 #include <langinfo.h>
22 #include <stdio.h>
23 #include <unistd.h>
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>
32 #include <gdbm.h>
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
41 static struct configmod *modules = NULL;
42
43 #if 0
44 static 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
74 struct 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
94 void 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
124 int 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
276 void 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 }
365
366 /* {store,fetch}var re-opens the database every time, just in case two
367  * doldacond processes would be running simultaneously. */
368 void storevar(char *key, void *val, size_t len)
369 {
370     char *dbname;
371     GDBM_FILE db;
372     datum k, v;
373     
374     dbname = findfile("dc-vardb", NULL, 1);
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
389 void *fetchvar(char *key, size_t *lenb)
390 {
391     char *dbname;
392     GDBM_FILE db;
393     datum k, v;
394     
395     if((dbname = findfile("dc-vardb", NULL, 0)) == NULL)
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 }