1 /* dot.conf - configuration file parser library
2 * Copyright (C) 1999,2000,2001,2002 Lukas Schroeder <lukas@azzit.de>,
3 * and others.
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License.
9 *
10 * This library 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 GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the
17 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18 * Boston, MA 02111-1307, USA.
19 *
20 */
21
22 /* -- dotconf.c - this code is responsible for the input, parsing and dispatching of options */
23
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
27
28 /* Added by Stephen W. Boyer <sboyer@caldera.com>
29 * for wildcard support in Include file paths
30 */
31 #include <sys/types.h>
32 #include <sys/stat.h>
33 #include <fcntl.h>
34 #include <sys/stat.h>
35
36 /* -- AIX 4.3 compile time fix
37 * by Eduardo Marcel Macan <macan@colband.com.br>
38 *
39 * modified by Stephen W. Boyer <sboyer@caldera.com>
40 * for Unixware and OpenServer
41 */
42
43 #if defined (_AIX43) || defined(UNIXWARE) || defined(OSR5)
44 #include <strings.h>
45 #endif
46
47 #include <stdarg.h>
48 #include <time.h>
49 #include <sys/stat.h>
50
51 #ifndef WIN32
52
53 #include <dirent.h>
54 #include <unistd.h>
55
56 #else /* ndef WIN32 */
57
58 #include "readdir.h" /* WIN32 fix by Robert J. Buck */
59
60 #define strncasecmp strnicmp
61 typedef unsigned long ulong;
62 #define snprintf _snprintf
63 #define vsnprintf _vsnprintf
64 #endif /* !WIN32 */
65
66 #include <ctype.h>
67 #include "dotconf.h"
68 #include "dotconf_priv.h"
69
70 #ifndef MIN
71 #define MIN(a,b) ((a)<(b)?(a):(b))
72 #endif
73
74 static char name[CFG_MAX_OPTION + 1]; /* option name */
75
76 /*
77 * some 'magic' options that are predefined by dot.conf itself for
78 * advanced functionality
79 */
80 static DOTCONF_CB(dotconf_cb_include); /* internal 'Include' */
81 static DOTCONF_CB(dotconf_cb_includepath); /* internal 'IncludePath' */
82
83 static configoption_t dotconf_options[] = {
84 {"Include", ARG_STR, dotconf_cb_include, NULL, CTX_ALL},
85 {"IncludePath", ARG_STR, dotconf_cb_includepath, NULL, CTX_ALL},
86 LAST_CONTEXT_OPTION
87 };
88
skip_whitespace(signed char ** cp,int n,char term)89 static void skip_whitespace(signed char **cp, int n, char term)
90 {
91 signed char *cp1 = *cp;
92 while (isspace((int)*cp1) && *cp1 != term && n--)
93 cp1++;
94 *cp = cp1;
95 }
96
copy_word(signed char ** dest,signed char ** src,int max,char term)97 static void copy_word(signed char **dest, signed char **src, int max, char term)
98 {
99 signed char *cp1 = *src;
100 signed char *cp2 = *dest;
101 while (max-- && !isspace((int)*cp1) && *cp1 != term)
102 *cp2++ = *cp1++;
103 *cp2 = 0;
104
105 *src = cp1;
106 *dest = cp2;
107 }
108
get_argname_fallback(const configoption_t * options)109 static const configoption_t *get_argname_fallback(const configoption_t *
110 options)
111 {
112 int i;
113
114 for (i = 0; (options[i].name && options[i].name[0]); i++) ;
115 if (options[i].type == ARG_NAME && options[i].callback)
116 return &options[i];
117 return NULL;
118 }
119
dotconf_substitute_env(configfile_t * configfile,char * str)120 char *dotconf_substitute_env(configfile_t * configfile, char *str)
121 {
122 char *cp1, *cp2, *cp3, *eos, *eob;
123 char *env_value;
124 char env_name[CFG_MAX_VALUE + 1];
125 char env_default[CFG_MAX_VALUE + 1];
126 char tmp_value[CFG_MAX_VALUE + 1];
127
128 memset(env_name, 0, CFG_MAX_VALUE + 1);
129 memset(env_default, 0, CFG_MAX_VALUE + 1);
130 memset(tmp_value, 0, CFG_MAX_VALUE + 1);
131
132 cp1 = str;
133 eob = cp1 + strlen(str) + 1;
134 cp2 = tmp_value;
135 eos = cp2 + CFG_MAX_VALUE + 1;
136
137 while ((cp1 < eob) && (cp2 < eos) && (*cp1 != '\0')) {
138 /* substitution needed ?? */
139 if (*cp1 == '$' && *(cp1 + 1) == '{') {
140 cp1 += 2; /* skip ${ */
141 cp3 = env_name;
142 while ((cp1 < eob) && !(*cp1 == '}' || *cp1 == ':'))
143 *cp3++ = *cp1++;
144 *cp3 = '\0'; /* terminate */
145
146 /* default substitution */
147 if (*cp1 == ':' && *(cp1 + 1) == '-') {
148 cp1 += 2; /* skip :- */
149 cp3 = env_default;
150 while ((cp1 < eob) && (*cp1 != '}'))
151 *cp3++ = *cp1++;
152 *cp3 = '\0'; /* terminate */
153 } else {
154 while ((cp1 < eob) && (*cp1 != '}'))
155 cp1++;
156 }
157
158 if (*cp1 != '}') {
159 dotconf_warning(configfile, DCLOG_WARNING,
160 ERR_PARSE_ERROR,
161 "Unbalanced '{'");
162 } else {
163 cp1++; /* skip } */
164 if ((env_value = getenv(env_name)) != NULL) {
165 strncat(cp2, env_value, eos - cp2);
166 cp2 += strlen(env_value);
167 } else {
168 strncat(cp2, env_default, eos - cp2);
169 cp2 += strlen(env_default);
170 }
171 }
172
173 }
174
175 *cp2++ = *cp1++;
176 }
177 *cp2 = '\0'; /* terminate buffer */
178
179 free(str);
180 return strdup(tmp_value);
181 }
182
dotconf_warning(configfile_t * configfile,int type,unsigned long errnum,const char * fmt,...)183 int dotconf_warning(configfile_t * configfile, int type, unsigned long errnum,
184 const char *fmt, ...)
185 {
186 va_list args;
187 int retval = 0;
188
189 va_start(args, fmt);
190 if (configfile->errorhandler != 0) { /* an errorhandler is registered */
191 char msg[CFG_BUFSIZE];
192 vsnprintf(msg, CFG_BUFSIZE, fmt, args);
193 retval =
194 configfile->errorhandler(configfile, type, errnum, msg);
195 } else { /* no errorhandler, do-it-yourself */
196
197 retval = 0;
198 fprintf(stderr, "%s:%ld: ", configfile->filename,
199 configfile->line);
200 vfprintf(stderr, fmt, args);
201 fprintf(stderr, "\n");
202 }
203 va_end(args);
204
205 return retval;
206 }
207
dotconf_register_options(configfile_t * configfile,const configoption_t * options)208 int dotconf_register_options(configfile_t * configfile,
209 const configoption_t * options)
210 {
211 int num = configfile->config_option_count;
212 int ret = 0;
213 configoption_t const **temp = configfile->config_options;
214
215 #define GROW_BY 10
216
217 /* resize memoryblock for options blockwise */
218 if (temp == NULL)
219 temp = malloc(sizeof(configoption_t *) * (GROW_BY + 1));
220 else {
221 if (!(num % GROW_BY))
222 temp = realloc(temp,
223 sizeof(configoption_t *) * (num +
224 GROW_BY +
225 1));
226 }
227
228 #undef GROW_BY
229
230 if (temp != NULL) {
231 /* Allocation or reallocation was successful. */
232 /* append new options */
233 temp[configfile->config_option_count] = options;
234 configfile->config_options = temp;
235 configfile->config_options[++configfile->config_option_count] =
236 0;
237 ret = 1;
238 }
239
240 return ret;
241 }
242
dotconf_callback(configfile_t * configfile,callback_types type,dotconf_callback_t callback)243 void dotconf_callback(configfile_t * configfile, callback_types type,
244 dotconf_callback_t callback)
245 {
246 switch (type) {
247 case ERROR_HANDLER:
248 configfile->errorhandler = (dotconf_errorhandler_t) callback;
249 break;
250 case CONTEXT_CHECKER:
251 configfile->contextchecker =
252 (dotconf_contextchecker_t) callback;
253 break;
254 default:
255 break;
256 }
257 }
258
dotconf_continue_line(char * buffer,size_t length)259 int dotconf_continue_line(char *buffer, size_t length)
260 {
261 /* ------ match [^\\]\\[\r]\n ------------------------------ */
262 char *cp1 = buffer + length - 1;
263
264 if (length < 2)
265 return 0;
266
267 if (*cp1-- != '\n')
268 return 0;
269
270 if (*cp1 == '\r')
271 cp1--;
272
273 if (*cp1-- != '\\')
274 return 0;
275
276 cp1[1] = 0; /* strip escape character and/or newline */
277 return (*cp1 != '\\');
278 }
279
dotconf_get_next_line(char * buffer,size_t bufsize,configfile_t * configfile)280 int dotconf_get_next_line(char *buffer, size_t bufsize,
281 configfile_t * configfile)
282 {
283 char *cp1, *cp2;
284 char buf2[CFG_BUFSIZE];
285 int length;
286
287 if (configfile->eof)
288 return 1;
289
290 cp1 = fgets(buffer, CFG_BUFSIZE, configfile->stream);
291
292 if (!cp1) {
293 configfile->eof = 1;
294 return 1;
295 }
296
297 configfile->line++;
298 length = strlen(cp1);
299 while (dotconf_continue_line(cp1, length)) {
300 cp2 = fgets(buf2, CFG_BUFSIZE, configfile->stream);
301 if (!cp2) {
302 fprintf(stderr,
303 "[dotconf] Parse error. Unexpected end of file at "
304 "line %ld in file %s\n", configfile->line,
305 configfile->filename);
306 configfile->eof = 1;
307 return 1;
308 }
309 configfile->line++;
310 strcpy(cp1 + length - 2, cp2);
311 length = strlen(cp1);
312 }
313
314 return 0;
315 }
316
dotconf_get_here_document(configfile_t * configfile,const char * delimit)317 char *dotconf_get_here_document(configfile_t * configfile, const char *delimit)
318 {
319 /* it's a here-document: yeah, what a cool feature ;) */
320 unsigned int limit_len;
321 char here_string;
322 char buffer[CFG_BUFSIZE];
323 char *here_doc = 0;
324 char here_limit[9]; /* max length for here-document delimiter: 8 */
325 struct stat finfo;
326 int offset = 0;
327
328 if (configfile->size <= 0) {
329 if (stat(configfile->filename, &finfo)) {
330 dotconf_warning(configfile, DCLOG_EMERG, ERR_NOACCESS,
331 "[emerg] could not stat currently read file (%s)\n",
332 configfile->filename);
333 return NULL;
334 }
335 configfile->size = finfo.st_size;
336 }
337
338 /*
339 * allocate a buffer of filesize bytes; should be enough to
340 * prevent buffer overflows
341 */
342 here_doc = malloc(configfile->size); /* allocate buffer memory */
343 memset(here_doc, 0, configfile->size);
344
345 here_string = 1;
346 limit_len = snprintf(here_limit, 9, "%s", delimit);
347 while (!dotconf_get_next_line(buffer, CFG_BUFSIZE, configfile)) {
348 if (!strncmp(here_limit, buffer, limit_len - 1)) {
349 here_string = 0;
350 break;
351 }
352 offset +=
353 snprintf((here_doc + offset), configfile->size - offset - 1,
354 "%s", buffer);
355 }
356 if (here_string)
357 dotconf_warning(configfile, DCLOG_WARNING, ERR_PARSE_ERROR,
358 "Unterminated here-document!");
359
360 here_doc[offset - 1] = '\0'; /* strip newline */
361
362 return (char *)realloc(here_doc, offset);
363 }
364
dotconf_invoke_command(configfile_t * configfile,command_t * cmd)365 const char *dotconf_invoke_command(configfile_t * configfile, command_t * cmd)
366 {
367 const char *error = 0;
368
369 error = cmd->option->callback(cmd, configfile->context);
370 return error;
371 }
372
dotconf_read_arg(configfile_t * configfile,signed char ** line)373 char *dotconf_read_arg(configfile_t * configfile, signed char **line)
374 {
375 int sq = 0, dq = 0; /* single quote, double quote */
376 int done;
377 signed char *cp1 = *line;
378 char *cp2, *eos;
379 char buf[CFG_MAX_VALUE];
380
381 memset(buf, 0, CFG_MAX_VALUE);
382 done = 0;
383 cp2 = buf;
384 eos = cp2 + CFG_MAX_VALUE - 1;
385
386 if (*cp1 == '#' || !*cp1)
387 return NULL;
388
389 skip_whitespace(&cp1, CFG_MAX_VALUE, 0);
390
391 while ((*cp1 != '\0') && (cp2 != eos) && !done) {
392 switch (*cp1) {
393 case '\'': /* single quote */
394 if (dq)
395 break; /* already double quoting, break out */
396 if (sq)
397 sq--; /* already single quoting, clear state */
398 else if (!sq)
399 sq++; /* set state for single quoting */
400 break;
401 case '"': /* double quote */
402 if (sq)
403 break; /* already single quoting, break out */
404 if (dq)
405 dq--; /* already double quoting, clear state */
406 else if (!dq)
407 dq++; /* set state for double quoting */
408 break;
409 case '\\': /* protected chars */
410 if (!cp1[1]) /* dont protect NUL */
411 break;
412 *cp2++ = *(++cp1);
413 cp1++; /* skip the protected one */
414 continue;
415 break;
416 default:
417 break;
418 }
419
420 /* unquoted space: start a new option argument */
421 if (isspace((int)*cp1) && !dq && !sq) {
422 *cp2 = '\0';
423 break;
424 }
425 /* unquoted, unescaped comment-hash ; break out, unless NO_INLINE_COMMENTS is set */
426 else if (*cp1 == '#' && !dq && !sq
427 && !(configfile->flags & NO_INLINE_COMMENTS)) {
428 /*
429 * NOTE: 1.0.8a got the NO_INLINE_COMMENTS feature wrong: it
430 * skipped every argument starting with a #, instead of simply eating it!
431 */
432
433 *cp2 = 0;
434 *cp1 = 0;
435 *line = cp1;
436 return NULL;
437 }
438 /* not space or quoted: eat it; dont take quote if quoting */
439 else if ((!isspace((int)*cp1) && !dq && !sq && *cp1 != '"'
440 && *cp1 != '\'')
441 || (dq && (*cp1 != '"')) || (sq && *cp1 != '\'')) {
442 *cp2++ = *cp1;
443 }
444
445 cp1++;
446 }
447
448 *line = cp1;
449
450 /* FIXME: escaping substitutes does not work
451 Subst ${HOME} \$\{HOME\}
452 BOTH! will be substituted, which is somewhat wrong, ain't it ?? :-(
453 */
454 if ((configfile->flags & DONT_SUBSTITUTE) == DONT_SUBSTITUTE)
455 return buf[0] ? strdup(buf) : NULL;
456 return buf[0] ? dotconf_substitute_env(configfile, strdup(buf)) : NULL;
457 }
458
459 /* dotconf_find_command remains here for backwards compatability. it's
460 * internally unused since dot.conf 1.0.9 because it cannot handle the
461 * DUPLICATE_OPTION_NAMES flag
462 */
dotconf_find_command(configfile_t * configfile,const char * command)463 configoption_t *dotconf_find_command(configfile_t * configfile,
464 const char *command)
465 {
466 configoption_t *option;
467 int i = 0, mod = 0, done = 0;
468
469 for (option = 0, mod = 0; configfile->config_options[mod] && !done;
470 mod++)
471 for (i = 0; configfile->config_options[mod][i].name[0]; i++) {
472 if (!configfile->cmp_func(name,
473 configfile->
474 config_options[mod][i].name,
475 CFG_MAX_OPTION)) {
476 option =
477 (configoption_t *) & configfile->
478 config_options[mod][i];
479 /* TODO: this could be flagged: option overwriting by modules */
480 done = 1;
481 break; /* found it; break out */
482 }
483 }
484
485 /* handle ARG_NAME fallback */
486 if ((option && option->name[0] == 0)
487 || configfile->config_options[mod - 1][i].type == ARG_NAME) {
488 option =
489 (configoption_t *) & configfile->config_options[mod - 1][i];
490 }
491
492 return option;
493 }
494
dotconf_set_command(configfile_t * configfile,const configoption_t * option,signed char * args,command_t * cmd)495 void dotconf_set_command(configfile_t * configfile,
496 const configoption_t * option, signed char *args,
497 command_t * cmd)
498 {
499 signed char *eob = args + strlen(args);
500
501 /* fill in the command_t structure with values we already know */
502 cmd->name = option->type == ARG_NAME ? name : option->name;
503 cmd->option = (configoption_t *) option;
504 cmd->context = configfile->context;
505 cmd->configfile = configfile;
506 cmd->data.list = (char **)calloc(CFG_VALUES, sizeof(char *));
507 cmd->data.str = 0;
508
509 if (option->type == ARG_RAW) {
510 /* if it is an ARG_RAW type, save some time and call the
511 callback now */
512 cmd->data.str = strdup(args);
513 } else if (option->type == ARG_STR) {
514 signed char *cp = args;
515
516 /* check if it's a here-document and act accordingly */
517 skip_whitespace(&cp, eob - cp, 0);
518
519 if (!strncmp("<<", cp, 2)) {
520 cmd->data.str =
521 dotconf_get_here_document(configfile, cp + 2);
522 cmd->arg_count = 1;
523 }
524 }
525
526 if (!(option->type == ARG_STR && cmd->data.str != 0)) {
527 /* we only get here for non-heredocument lines */
528
529 skip_whitespace(&args, eob - args, 0);
530
531 cmd->arg_count = 0;
532 while (cmd->arg_count < (CFG_VALUES - 1)
533 && (cmd->data.list[cmd->arg_count] =
534 dotconf_read_arg(configfile, &args))) {
535 cmd->arg_count++;
536 }
537
538 skip_whitespace(&args, eob - args, 0);
539
540 if (cmd->arg_count && cmd->data.list[cmd->arg_count - 1]
541 && *args)
542 cmd->data.list[cmd->arg_count++] = strdup(args);
543
544 /* has an option entry been found before or do we have to use a fallback? */
545 if ((option->name && option->name[0] > 32)
546 || option->type == ARG_NAME) {
547 /* found it, now check the type of args it wants */
548 switch (option->type) {
549 case ARG_TOGGLE:
550 /* the value is true if the argument is Yes, On or 1 */
551 if (cmd->arg_count < 1) {
552 dotconf_warning(configfile,
553 DCLOG_WARNING,
554 ERR_WRONG_ARG_COUNT,
555 "Missing argument to option '%s'",
556 name);
557 return;
558 }
559
560 cmd->data.value =
561 CFG_TOGGLED(cmd->data.list[0]);
562 break;
563 case ARG_INT:
564 if (cmd->arg_count < 1) {
565 dotconf_warning(configfile,
566 DCLOG_WARNING,
567 ERR_WRONG_ARG_COUNT,
568 "Missing argument to option '%s'",
569 name);
570 return;
571 }
572
573 sscanf(cmd->data.list[0], "%li",
574 &cmd->data.value);
575 break;
576
577 case ARG_DOUBLE:
578 if (cmd->arg_count < 1) {
579 dotconf_warning(configfile,
580 DCLOG_WARNING,
581 ERR_WRONG_ARG_COUNT,
582 "Missing argument to option '%s'",
583 name);
584 return;
585 }
586
587 cmd->data.dvalue = strtod(cmd->data.list[0], 0);
588 break;
589
590 case ARG_STR:
591 if (cmd->arg_count < 1) {
592 dotconf_warning(configfile,
593 DCLOG_WARNING,
594 ERR_WRONG_ARG_COUNT,
595 "Missing argument to option '%s'",
596 name);
597 return;
598 }
599
600 cmd->data.str = strdup(cmd->data.list[0]);
601 break;
602 case ARG_NAME: /* fall through */
603 case ARG_LIST:
604 case ARG_NONE:
605 case ARG_RAW: /* this has been handled before */
606 default:
607 break;
608 }
609 }
610 }
611 }
612
dotconf_free_command(command_t * command)613 void dotconf_free_command(command_t * command)
614 {
615 int i;
616
617 if (command->data.str)
618 free(command->data.str);
619
620 for (i = 0; i < command->arg_count; i++)
621 free(command->data.list[i]);
622 free(command->data.list);
623 }
624
dotconf_handle_command(configfile_t * configfile,char * buffer)625 const char *dotconf_handle_command(configfile_t * configfile, char *buffer)
626 {
627 signed char *cp1;
628 signed char *cp2;
629 /* generic char pointer */
630 signed char *eob; /* end of buffer; end of string */
631 const char *error; /* error message we'll return */
632 const char *context_error; /* error message returned by contextchecker */
633 command_t command; /* command structure */
634 int mod = 0;
635 int next_opt_idx = 0;
636
637 memset(&command, 0, sizeof(command_t));
638 name[0] = 0;
639 error = 0;
640 context_error = 0;
641
642 cp1 = buffer;
643 eob = cp1 + strlen(cp1);
644
645 skip_whitespace(&cp1, eob - cp1, 0);
646
647 /* ignore comments and empty lines */
648 if (!cp1 || !*cp1 || *cp1 == '#' || *cp1 == '\n' || *cp1 == EOF)
649 return NULL;
650
651 /* skip line if it only contains whitespace */
652 if (cp1 == eob)
653 return NULL;
654
655 /* get first token: read the name of a possible option */
656 cp2 = name;
657 copy_word(&cp2, &cp1, MIN(eob - cp1, CFG_MAX_OPTION), 0);
658
659 while (1) {
660 const configoption_t *option;
661 int done = 0;
662 int opt_idx = 0;
663
664 for (option = 0; configfile->config_options[mod] && !done;
665 mod++) {
666 for (opt_idx = next_opt_idx;
667 configfile->config_options[mod][opt_idx].name[0];
668 opt_idx++) {
669 if (!configfile->
670 cmp_func(name,
671 configfile->
672 config_options[mod][opt_idx].name,
673 CFG_MAX_OPTION)) {
674 /* TODO: this could be flagged: option overwriting by modules */
675 option =
676 (configoption_t *) & configfile->
677 config_options[mod][opt_idx];
678 done = 1;
679 break; /* found one; break out */
680 }
681 }
682 }
683
684 if (!option)
685 option =
686 get_argname_fallback(configfile->config_options[1]);
687
688 if (!option || !option->callback) {
689 if (error)
690 return error;
691 dotconf_warning(configfile, DCLOG_INFO,
692 ERR_UNKNOWN_OPTION,
693 "Unknown Config-Option: '%s'", name);
694 return NULL;
695 }
696
697 /* set up the command structure (contextchecker wants this) */
698 dotconf_set_command(configfile, option, cp1, &command);
699
700 if (configfile->contextchecker)
701 context_error =
702 configfile->contextchecker(&command,
703 command.option->context);
704
705 if (!context_error)
706 error = dotconf_invoke_command(configfile, &command);
707 else {
708 if (!error) {
709 /* avoid returning another error then the first. This makes it easier to
710 reproduce problems. */
711 error = context_error;
712 }
713 }
714
715 dotconf_free_command(&command);
716
717 if (!context_error
718 || !(configfile->flags & DUPLICATE_OPTION_NAMES)) {
719 /* don't try more, just quit now. */
720 break;
721 }
722 }
723
724 return error;
725 }
726
dotconf_command_loop_until_error(configfile_t * configfile)727 const char *dotconf_command_loop_until_error(configfile_t * configfile)
728 {
729 char buffer[CFG_BUFSIZE];
730
731 while (!(dotconf_get_next_line(buffer, CFG_BUFSIZE, configfile))) {
732 const char *error = dotconf_handle_command(configfile, buffer);
733 if (error)
734 return error;
735 }
736 return NULL;
737 }
738
dotconf_command_loop(configfile_t * configfile)739 int dotconf_command_loop(configfile_t * configfile)
740 {
741 /* ------ returns: 0 for failure -- !0 for success ------------------------------------------ */
742 char buffer[CFG_BUFSIZE];
743
744 while (!(dotconf_get_next_line(buffer, CFG_BUFSIZE, configfile))) {
745 const char *error = dotconf_handle_command(configfile, buffer);
746 if (error != NULL) {
747 if (dotconf_warning(configfile, DCLOG_ERR, 0, error))
748 return 0;
749 }
750 }
751 return 1;
752 }
753
dotconf_cleanup(configfile_t * configfile)754 void dotconf_cleanup(configfile_t * configfile)
755 {
756 if (configfile->stream)
757 fclose(configfile->stream);
758
759 if (configfile->filename)
760 free(configfile->filename);
761
762 if (configfile->config_options)
763 free(configfile->config_options);
764
765 if (configfile->includepath)
766 free(configfile->includepath);
767
768 free(configfile);
769 }
770
dotconf_create(char * fname,const configoption_t * options,context_t * context,unsigned long flags)771 configfile_t *dotconf_create(char *fname, const configoption_t * options,
772 context_t * context, unsigned long flags)
773 {
774 char *dc_env = NULL;
775 int registered = 0;
776 configfile_t *new_cfg = calloc(1, sizeof(configfile_t));
777 char *path = NULL;
778 char *cwd = NULL;
779
780 if (!new_cfg)
781 return NULL;
782
783 /*
784 * From here on, we can use dotconf_cleanup to free resources
785 * when errors occur. All of our pointers are NULL, because
786 * we allocated with calloc. When an error occurs, dotconf_cleanup
787 * will free all of the resources that were allocated prior to the
788 * error.
789 */
790
791 new_cfg->context = context;
792 new_cfg->flags = flags;
793 if (new_cfg->flags & CASE_INSENSITIVE)
794 new_cfg->cmp_func = strncasecmp;
795 else
796 new_cfg->cmp_func = strncmp;
797
798 new_cfg->stream = fopen(fname, "r");
799 if (new_cfg->stream == NULL) {
800 fprintf(stderr, "Error opening configuration file '%s'\n",
801 fname);
802 dotconf_cleanup(new_cfg);
803 return NULL;
804 }
805
806 registered = dotconf_register_options(new_cfg, dotconf_options);
807 if (!registered) {
808 dotconf_cleanup(new_cfg);
809 return NULL;
810 }
811
812 registered = dotconf_register_options(new_cfg, options);
813 if (!registered) {
814 dotconf_cleanup(new_cfg);
815 return NULL;
816 }
817
818 new_cfg->filename = strdup(fname);
819 if (!new_cfg->filename) {
820 dotconf_cleanup(new_cfg);
821 return NULL;
822 }
823
824 new_cfg->includepath = malloc(CFG_MAX_FILENAME);
825 if (!new_cfg->includepath) {
826 dotconf_cleanup(new_cfg);
827 return NULL;
828 }
829
830 new_cfg->includepath[0] = 0x00;
831
832 /*
833 * take default includepath from environment if present
834 * Otherwise, resolve the path of the configuration file and use that
835 * as default includepath.
836 */
837 dc_env = getenv(CFG_INCLUDEPATH_ENV);
838 if (dc_env != NULL) {
839 snprintf(new_cfg->includepath, CFG_MAX_FILENAME, "%s", dc_env);
840 } else {
841 path = get_path(fname);
842 if (path != NULL) {
843 if (path[0] == '/') {
844 snprintf(new_cfg->includepath, CFG_MAX_FILENAME, "%s", path);
845 } else {
846 cwd = get_cwd();
847 if (cwd != NULL) {
848 snprintf(new_cfg->includepath, CFG_MAX_FILENAME,
849 "%s/%s", cwd, path);
850 free(cwd);
851 }
852 }
853 free(path);
854 }
855 }
856 return new_cfg;
857 }
858
859 /* ------ internal utility function that verifies if a character is in the WILDCARDS list -- */
dotconf_is_wild_card(char value)860 int dotconf_is_wild_card(char value)
861 {
862 int retval = 0;
863 int i;
864 int wildcards_len = strlen(WILDCARDS);
865
866 for (i = 0; i < wildcards_len; i++) {
867 if (value == WILDCARDS[i]) {
868 retval = 1;
869 break;
870 }
871 }
872
873 return retval;
874 }
875
876 /* ------ internal utility function that calls the appropriate routine for the wildcard passed in -- */
dotconf_handle_wild_card(command_t * cmd,char wild_card,char * path,char * pre,char * ext)877 int dotconf_handle_wild_card(command_t * cmd, char wild_card, char *path,
878 char *pre, char *ext)
879 {
880 int retval = 0;
881
882 switch (wild_card) {
883 case '*':
884
885 retval = dotconf_handle_star(cmd, path, pre, ext);
886
887 break;
888
889 case '?':
890
891 retval = dotconf_handle_question_mark(cmd, path, pre, ext);
892
893 break;
894
895 default:
896 retval = -1;
897 }
898
899 return retval;
900 }
901
902 /* ------ internal utility function that frees allocated memory from dotcont_find_wild_card -- */
dotconf_wild_card_cleanup(char * path,char * pre)903 void dotconf_wild_card_cleanup(char *path, char *pre)
904 {
905
906 if (path != NULL) {
907 free(path);
908 }
909
910 if (pre != NULL) {
911 free(pre);
912 }
913
914 }
915
916 /* ------ internal utility function to check for wild cards in file path -- */
917 /* ------ path and pre must be freed by the developer ( dotconf_wild_card_cleanup) -- */
dotconf_find_wild_card(char * filename,char * wildcard,char ** path,char ** pre,char ** ext)918 int dotconf_find_wild_card(char *filename, char *wildcard, char **path,
919 char **pre, char **ext)
920 {
921 int retval = -1;
922 int prefix_len = 0;
923 int tmp_count = 0;
924 char *tmp = 0;
925 int found_path = 0;
926
927 int len = strlen(filename);
928
929 if (wildcard != NULL && len > 0 && path != NULL && pre != NULL
930 && ext != NULL) {
931 prefix_len = strcspn(filename, WILDCARDS); /* find any wildcard in WILDCARDS */
932
933 if (prefix_len < len) { /* Wild card found */
934 tmp = filename + prefix_len;
935 tmp_count = prefix_len + 1;
936
937 while (tmp != filename && *(tmp) != '/') {
938 tmp--;
939 tmp_count--;
940 }
941
942 if (*(tmp) == '/') {
943 *path = (char *)malloc(tmp_count + 1);
944 found_path = 1;
945
946 } else
947
948 *path = (char *)malloc(1);
949
950 *pre =
951 (char *)
952 malloc((prefix_len -
953 (tmp_count - (found_path ? 0 : 1))) + 1);
954
955 if (*path && *pre) {
956 if (found_path)
957 strncpy(*path, filename, tmp_count);
958 (*path)[tmp_count] = '\0';
959
960 strncpy(*pre, (tmp + (found_path ? 1 : 0)),
961 (prefix_len -
962 (tmp_count - (found_path ? 0 : 1))));
963 (*pre)[(prefix_len -
964 (tmp_count - (found_path ? 0 : 1)))] =
965 '\0';
966
967 *ext = filename + prefix_len;
968 *wildcard = (**ext);
969 (*ext)++;
970
971 retval = prefix_len;
972
973 }
974
975 }
976
977 }
978
979 return retval;
980 }
981
982 /* ------ internal utility function that compares two stings from back to front -- */
dotconf_strcmp_from_back(const char * s1,const char * s2)983 int dotconf_strcmp_from_back(const char *s1, const char *s2)
984 {
985 int retval = 0;
986 int i, j;
987 int len_1 = strlen(s1);
988 int len_2 = strlen(s2);
989
990 for (i = len_1, j = len_2; (i >= 0 && j >= 0); i--, j--) {
991 if (s1[i] != s2[j]) {
992 retval = -1;
993 break;
994 }
995 }
996
997 return retval;
998 }
999
1000 /* ------ internal utility function that determins if a string matches the '?' criteria -- */
dotconf_question_mark_match(char * dir_name,char * pre,char * ext)1001 int dotconf_question_mark_match(char *dir_name, char *pre, char *ext)
1002 {
1003 int retval = -1;
1004 int dir_name_len = strlen(dir_name);
1005 int pre_len = strlen(pre);
1006 int ext_len = strlen(ext);
1007 int w_card_check = strcspn(ext, WILDCARDS);
1008
1009 if ((w_card_check < ext_len) && (strncmp(dir_name, pre, pre_len) == 0)
1010 && (strcmp(dir_name, ".") != 0) && (strcmp(dir_name, "..") != 0)) {
1011 retval = 1; /* Another wildcard found */
1012
1013 } else {
1014
1015 if ((dir_name_len >= pre_len) &&
1016 (strncmp(dir_name, pre, pre_len) == 0) &&
1017 (strcmp(dir_name, ".") != 0) &&
1018 (strcmp(dir_name, "..") != 0)) {
1019 retval = 0; /* Matches no other wildcards */
1020 }
1021
1022 }
1023
1024 return retval;
1025 }
1026
1027 /* ------ internal utility function that determins if a string matches the '*' criteria -- */
dotconf_star_match(char * dir_name,char * pre,char * ext)1028 int dotconf_star_match(char *dir_name, char *pre, char *ext)
1029 {
1030 int retval = -1;
1031 int dir_name_len = strlen(dir_name);
1032 int pre_len = strlen(pre);
1033 int ext_len = strlen(ext);
1034 int w_card_check = strcspn(ext, WILDCARDS);
1035
1036 if ((w_card_check < ext_len) && (strncmp(dir_name, pre, pre_len) == 0)
1037 && (strcmp(dir_name, ".") != 0) && (strcmp(dir_name, "..") != 0)) {
1038 retval = 1; /* Another wildcard found */
1039
1040 } else {
1041
1042 if ((dir_name_len >= (ext_len + pre_len)) &&
1043 (dotconf_strcmp_from_back(dir_name, ext) == 0) &&
1044 (strncmp(dir_name, pre, pre_len) == 0) &&
1045 (strcmp(dir_name, ".") != 0) &&
1046 (strcmp(dir_name, "..") != 0)) {
1047 retval = 0; /* Matches no other wildcards */
1048 }
1049
1050 }
1051
1052 return retval;
1053 }
1054
1055 /* ------ internal utility function that determins matches for filenames with -- */
1056 /* ------ a '?' in name and calls the Internal Include function on that filename -- */
dotconf_handle_question_mark(command_t * cmd,char * path,char * pre,char * ext)1057 int dotconf_handle_question_mark(command_t * cmd, char *path, char *pre,
1058 char *ext)
1059 {
1060 configfile_t *included;
1061 DIR *dh = 0;
1062 struct dirent *dirptr = 0;
1063 int i;
1064
1065 char new_pre[CFG_MAX_FILENAME];
1066 char already_matched[CFG_MAX_FILENAME];
1067
1068 char wc = '\0';
1069
1070 char *new_path = 0;
1071 char *wc_path = 0;
1072 char *wc_pre = 0;
1073 char *wc_ext = 0;
1074 char *temp = NULL;
1075
1076 int pre_len;
1077 int new_path_len;
1078 int name_len = 0;
1079 int alloced = 0;
1080 int match_state = 0;
1081
1082 pre_len = strlen(pre);
1083
1084 if ((dh = opendir(path)) != NULL) {
1085 while ((dirptr = readdir(dh)) != NULL) {
1086 match_state =
1087 dotconf_question_mark_match(dirptr->d_name, pre,
1088 ext);
1089
1090 if (match_state >= 0) {
1091 name_len = strlen(dirptr->d_name);
1092 new_path_len =
1093 strlen(path) + name_len + strlen(ext) + 1;
1094
1095 if (!alloced) {
1096 if ((new_path =
1097 (char *)malloc(new_path_len)) ==
1098 NULL) {
1099 return -1;
1100 }
1101
1102 alloced = new_path_len;
1103
1104 } else {
1105
1106 if (new_path_len > alloced) {
1107 temp =
1108 realloc(new_path,
1109 new_path_len);
1110 if (temp == NULL) {
1111 free(new_path);
1112 return -1;
1113 }
1114 new_path = temp;
1115 alloced = new_path_len;
1116
1117 }
1118
1119 }
1120
1121 if (match_state == 1) {
1122
1123 strncpy(new_pre, dirptr->d_name,
1124 (name_len >
1125 pre_len) ? (pre_len +
1126 1) : pre_len);
1127 new_pre[(name_len >
1128 pre_len) ? (pre_len +
1129 1) : pre_len] =
1130 '\0';
1131
1132 sprintf(new_path, "%s%s%s", path,
1133 new_pre, ext);
1134
1135 if (strcmp(new_path, already_matched) ==
1136 0) {
1137 continue; /* Already searched this expression */
1138
1139 } else {
1140
1141 strcpy(already_matched,
1142 new_path);
1143
1144 }
1145
1146 if (dotconf_find_wild_card
1147 (new_path, &wc, &wc_path, &wc_pre,
1148 &wc_ext) >= 0) {
1149 if (dotconf_handle_wild_card
1150 (cmd, wc, wc_path, wc_pre,
1151 wc_ext) < 0) {
1152 dotconf_warning(cmd->
1153 configfile,
1154 DCLOG_WARNING,
1155 ERR_INCLUDE_ERROR,
1156 "Error occured while processing wildcard %c\n"
1157 "Filename is '%s'\n",
1158 wc,
1159 new_path);
1160
1161 free(new_path);
1162 dotconf_wild_card_cleanup
1163 (wc_path, wc_pre);
1164 return -1;
1165 }
1166
1167 dotconf_wild_card_cleanup
1168 (wc_path, wc_pre);
1169 continue;
1170 }
1171
1172 }
1173
1174 sprintf(new_path, "%s%s", path, dirptr->d_name);
1175
1176 if (access(new_path, R_OK)) {
1177 dotconf_warning(cmd->configfile,
1178 DCLOG_WARNING,
1179 ERR_INCLUDE_ERROR,
1180 "Cannot open %s for inclusion.\n"
1181 "IncludePath is '%s'\n",
1182 new_path,
1183 cmd->configfile->
1184 includepath);
1185 return -1;
1186 }
1187
1188 included =
1189 dotconf_create(new_path,
1190 cmd->configfile->
1191 config_options[1],
1192 cmd->configfile->context,
1193 cmd->configfile->flags);
1194 if (included) {
1195 for (i = 2;
1196 cmd->configfile->config_options[i];
1197 i++)
1198 dotconf_register_options
1199 (included,
1200 cmd->configfile->
1201 config_options[i]);
1202 included->errorhandler =
1203 cmd->configfile->errorhandler;
1204 included->contextchecker =
1205 cmd->configfile->contextchecker;
1206 dotconf_command_loop(included);
1207 dotconf_cleanup(included);
1208 }
1209
1210 }
1211
1212 }
1213
1214 closedir(dh);
1215 free(new_path);
1216
1217 }
1218
1219 return 0;
1220 }
1221
1222 /* ------ internal utility function that determins matches for filenames with --- */
1223 /* ------ a '*' in name and calls the Internal Include function on that filename -- */
dotconf_handle_star(command_t * cmd,char * path,char * pre,char * ext)1224 int dotconf_handle_star(command_t * cmd, char *path, char *pre, char *ext)
1225 {
1226 configfile_t *included;
1227 DIR *dh = 0;
1228 struct dirent *dirptr = 0;
1229
1230 char new_pre[CFG_MAX_FILENAME];
1231 char new_ext[CFG_MAX_FILENAME];
1232 char already_matched[CFG_MAX_FILENAME];
1233
1234 char wc = '\0';
1235
1236 char *new_path = 0;
1237 char *s_ext = 0;
1238 char *t_ext = 0;
1239 char *sub = 0;
1240 char *wc_path = 0;
1241 char *wc_pre = 0;
1242 char *wc_ext = 0;
1243 char *temp = NULL;
1244
1245 int pre_len;
1246 int new_path_len;
1247 int name_len = 0;
1248 int alloced = 0;
1249 int match_state = 0;
1250 int t_ext_count = 0;
1251 int sub_count = 0;
1252
1253 pre_len = strlen(pre);
1254 memset(already_matched, 0, CFG_MAX_FILENAME);
1255 s_ext = ext;
1256
1257 while (dotconf_is_wild_card(*s_ext)) { /* remove trailing wild-cards proceeded by * */
1258 s_ext++;
1259 }
1260
1261 t_ext = s_ext;
1262
1263 while (t_ext != NULL && !(dotconf_is_wild_card(*t_ext))
1264 && *t_ext != '\0') {
1265 t_ext++; /* find non-wild-card string */
1266 t_ext_count++;
1267 }
1268
1269 strncpy(new_ext, s_ext, t_ext_count);
1270 new_ext[t_ext_count] = '\0';
1271
1272 if ((dh = opendir(path)) != NULL) {
1273 while ((dirptr = readdir(dh)) != NULL) {
1274 sub_count = 0;
1275 t_ext_count = 0;
1276
1277 match_state =
1278 dotconf_star_match(dirptr->d_name, pre, s_ext);
1279
1280 if (match_state >= 0) {
1281 name_len = strlen(dirptr->d_name);
1282 new_path_len =
1283 strlen(path) + name_len + strlen(s_ext) + 1;
1284
1285 if (!alloced) {
1286 if ((new_path =
1287 (char *)malloc(new_path_len)) ==
1288 NULL) {
1289 return -1;
1290 }
1291
1292 alloced = new_path_len;
1293
1294 } else {
1295
1296 if (new_path_len > alloced) {
1297 temp =
1298 realloc(new_path,
1299 new_path_len);
1300 if (temp == NULL) {
1301 free(new_path);
1302 return -1;
1303 }
1304 new_path = temp;
1305 alloced = new_path_len;
1306
1307 }
1308
1309 }
1310
1311 if (match_state == 1) {
1312
1313 if ((sub =
1314 strstr((dirptr->d_name + pre_len),
1315 new_ext)) == NULL) {
1316 continue;
1317 }
1318
1319 while (sub != dirptr->d_name) {
1320 sub--;
1321 sub_count++;
1322 }
1323
1324 if (sub_count + t_ext_count > name_len) {
1325 continue;
1326 }
1327
1328 strncpy(new_pre, dirptr->d_name,
1329 (sub_count + t_ext_count));
1330 new_pre[sub_count + t_ext_count] = '\0';
1331 strcat(new_pre, new_ext);
1332
1333 sprintf(new_path, "%s%s%s", path,
1334 new_pre, t_ext);
1335
1336 if (strcmp(new_path, already_matched) ==
1337 0) {
1338 continue; /* Already searched this expression */
1339
1340 } else {
1341
1342 strcpy(already_matched,
1343 new_path);
1344
1345 }
1346
1347 if (dotconf_find_wild_card
1348 (new_path, &wc, &wc_path, &wc_pre,
1349 &wc_ext) >= 0) {
1350 if (dotconf_handle_wild_card
1351 (cmd, wc, wc_path, wc_pre,
1352 wc_ext) < 0) {
1353 dotconf_warning(cmd->
1354 configfile,
1355 DCLOG_WARNING,
1356 ERR_INCLUDE_ERROR,
1357 "Error occured while processing wildcard %c\n"
1358 "Filename is '%s'\n",
1359 wc,
1360 new_path);
1361
1362 free(new_path);
1363 dotconf_wild_card_cleanup
1364 (wc_path, wc_pre);
1365 return -1;
1366 }
1367
1368 dotconf_wild_card_cleanup
1369 (wc_path, wc_pre);
1370 continue;
1371 }
1372
1373 }
1374
1375 sprintf(new_path, "%s%s", path, dirptr->d_name);
1376
1377 if (access(new_path, R_OK)) {
1378 dotconf_warning(cmd->configfile,
1379 DCLOG_WARNING,
1380 ERR_INCLUDE_ERROR,
1381 "Cannot open %s for inclusion.\n"
1382 "IncludePath is '%s'\n",
1383 new_path,
1384 cmd->configfile->
1385 includepath);
1386 return -1;
1387 }
1388
1389 included =
1390 dotconf_create(new_path,
1391 cmd->configfile->
1392 config_options[1],
1393 cmd->configfile->context,
1394 cmd->configfile->flags);
1395 if (included) {
1396 included->errorhandler =
1397 cmd->configfile->errorhandler;
1398 included->contextchecker =
1399 cmd->configfile->contextchecker;
1400 dotconf_command_loop(included);
1401 dotconf_cleanup(included);
1402 }
1403
1404 }
1405
1406 }
1407
1408 closedir(dh);
1409 free(new_path);
1410
1411 }
1412
1413 return 0;
1414 }
1415
get_cwd(void)1416 char *get_cwd(void)
1417 {
1418 char *buf = calloc(1, CFG_MAX_FILENAME);
1419
1420 if (buf == NULL)
1421 return NULL;
1422 getcwd(buf, CFG_MAX_FILENAME);
1423 return buf;
1424 }
1425
get_path(char * name)1426 char *get_path(char *name)
1427 {
1428 char *tmp;
1429 char *buf = NULL;
1430 int len = 0;
1431
1432 tmp = strrchr(name, '/');
1433 if (tmp == NULL)
1434 return NULL;
1435 buf = calloc(1, CFG_MAX_FILENAME);
1436 if (buf == NULL)
1437 return NULL;
1438 if (tmp == name) {
1439 sprintf(buf, "/");
1440 } else {
1441 len = tmp - name + 1;
1442 if (len > CFG_MAX_FILENAME)
1443 len -= 1;
1444 }
1445 snprintf(buf, len, "%s", name);
1446 return buf;
1447 }
1448
1449 /* ------ callbacks of the internal option (Include, IncludePath) ------------------------------- */
DOTCONF_CB(dotconf_cb_include)1450 DOTCONF_CB(dotconf_cb_include)
1451 {
1452 char *filename = 0;
1453 configfile_t *included;
1454
1455 char wild_card;
1456 char *path = 0;
1457 char *pre = 0;
1458 char *ext = 0;
1459
1460 if (cmd->configfile->includepath
1461 && cmd->data.str[0] != '/'
1462 && cmd->configfile->includepath[0] != '\0') {
1463 /* relative file AND include path is used */
1464 int len, inclen;
1465 char *sl;
1466
1467 inclen = strlen(cmd->configfile->includepath);
1468 if ((len =
1469 (strlen(cmd->data.str) + inclen + 1)) ==
1470 CFG_MAX_FILENAME) {
1471 dotconf_warning(cmd->configfile, DCLOG_WARNING,
1472 ERR_INCLUDE_ERROR,
1473 "Absolute filename too long (>%d)",
1474 CFG_MAX_FILENAME);
1475 return NULL;
1476 }
1477
1478 if (cmd->configfile->includepath[inclen - 1] == '/')
1479 sl = "";
1480 else {
1481 sl = "/";
1482 len++;
1483 }
1484
1485 filename = malloc(len);
1486 snprintf(filename, len, "%s%s%s",
1487 cmd->configfile->includepath, sl, cmd->data.str);
1488 } else /* fully qualified, or no includepath */
1489 filename = strdup(cmd->data.str);
1490
1491 /* Added wild card support here */
1492 if (dotconf_find_wild_card(filename, &wild_card, &path, &pre, &ext) >=
1493 0) {
1494 if (dotconf_handle_wild_card(cmd, wild_card, path, pre, ext) <
1495 0) {
1496 dotconf_warning(cmd->configfile, DCLOG_WARNING,
1497 ERR_INCLUDE_ERROR,
1498 "Error occured while attempting to process %s for inclusion.\n"
1499 "IncludePath is '%s'\n", filename,
1500 cmd->configfile->includepath);
1501 }
1502
1503 dotconf_wild_card_cleanup(path, pre);
1504 free(filename);
1505 return NULL;
1506 }
1507
1508 if (access(filename, R_OK)) {
1509 dotconf_warning(cmd->configfile, DCLOG_WARNING,
1510 ERR_INCLUDE_ERROR,
1511 "Cannot open %s for inclusion.\n"
1512 "IncludePath is '%s'\n", filename,
1513 cmd->configfile->includepath);
1514 free(filename);
1515 return NULL;
1516 }
1517
1518 included = dotconf_create(filename, cmd->configfile->config_options[1],
1519 cmd->configfile->context,
1520 cmd->configfile->flags);
1521 if (included) {
1522 included->contextchecker =
1523 (dotconf_contextchecker_t) cmd->configfile->contextchecker;
1524 included->errorhandler =
1525 (dotconf_errorhandler_t) cmd->configfile->errorhandler;
1526
1527 dotconf_command_loop(included);
1528 dotconf_cleanup(included);
1529 }
1530
1531 free(filename);
1532 return NULL;
1533 }
1534
DOTCONF_CB(dotconf_cb_includepath)1535 DOTCONF_CB(dotconf_cb_includepath)
1536 {
1537 char *env = getenv(CFG_INCLUDEPATH_ENV);
1538 /* environment overrides configuration file setting */
1539 if (!env)
1540 snprintf(cmd->configfile->includepath, CFG_MAX_FILENAME, "%s",
1541 cmd->data.str);
1542 return NULL;
1543 }
1544