]>
Commit | Line | Data |
---|---|---|
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 | } |