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