+ }
+ if (tokens[2][0] == '{' && tokens[2][1] == '\0') {
+ *opt = stringcopy(tokens[0], 0);
+ if (!*opt)
+ goto errmalloc;
+ *val = stringcopy(tokens[1], 0);
+ if (!*val)
+ goto errmalloc;
+ *conftype = CONF_CBK;
+ break;
+ }
+ /* fall through */
+ default:
+ if (block)
+ debug(DBG_ERR, "configuration error in block %s, line starting with %s", block, tokens[0]);
+ else
+ debug(DBG_ERR, "configuration error, syntax error in line starting with %s", tokens[0]);
+ return 0;
+ }
+
+ if (**val)
+ return 1;
+
+ debug(DBG_ERR, "configuration error, option %s needs a non-empty value", *opt);
+ goto errexit;
+
+errmalloc:
+ debug(DBG_ERR, "malloc failed");
+errexit:
+ free(*opt);
+ *opt = NULL;
+ free(*val);
+ *val = NULL;
+ return 0;
+}
+
+uint8_t hexdigit2int(char d) {
+ if (d >= '0' && d <= '9')
+ return d - '0';
+ if (d >= 'a' && d <= 'f')
+ return 10 + d - 'a';
+ if (d >= 'A' && d <= 'F')
+ return 10 + d - 'A';
+ return 0;
+}
+
+void unhex(char *s) {
+ char *t;
+ for (t = s; *t; s++) {
+ if (*t == '%' && isxdigit((int)t[1]) && isxdigit((int)t[2])) {
+ *s = 16 * hexdigit2int(t[1]) + hexdigit2int(t[2]);
+ t += 3;
+ } else
+ *s = *t++;
+ }
+ *s = '\0';
+}
+
+typedef int (*t_fptr)(struct gconffile **, void *, char *, char *, char *);
+
+/* returns 1 if ok, 0 on error */
+/* caller must free returned values also on error */
+int getgenericconfig(struct gconffile **cf, char *block, ...) {
+ va_list ap;
+ char *opt = NULL, *val, *word, *optval, **str = NULL, ***mstr = NULL, **newmstr, *endptr;
+ uint8_t *bln = NULL;
+ long int *lint = NULL;
+ int type = 0, conftype = 0, n;
+ t_fptr cbk = NULL;
+ void *cbkarg = NULL;
+
+ for (;;) {
+ free(opt);
+ if (!getconfigline(cf, block, &opt, &val, &conftype))
+ return 0;
+ if (!opt)
+ return 1;
+
+ if (conftype == CONF_STR && !strcasecmp(opt, "include")) {
+ if (!pushgconfpaths(cf, val)) {
+ debug(DBG_ERR, "failed to include config file %s", val);
+ goto errexit;