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