1 /*-
2 * SSLsplit - transparent SSL/TLS interception
3 * https://www.roe.ch/SSLsplit
4 *
5 * Copyright (c) 2009-2019, Daniel Roethlisberger <daniel@roe.ch>.
6 * Copyright (c) 2017-2021, Soner Tari <sonertari@gmail.com>.
7 * All rights reserved.
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions are met:
11 * 1. Redistributions of source code must retain the above copyright notice,
12 * this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright notice,
14 * this list of conditions and the following disclaimer in the documentation
15 * and/or other materials provided with the distribution.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS ``AS IS''
18 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
21 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 * POSSIBILITY OF SUCH DAMAGE.
28 */
29
30 #include "opts.h"
31
32 #include "sys.h"
33 #include "log.h"
34 #include "defaults.h"
35
36 #include <string.h>
37 #include <sys/types.h>
38 #include <sys/socket.h>
39 #include <sys/resource.h>
40 #include <errno.h>
41
42 #define equal(s1, s2) strlen((s1)) == strlen((s2)) && !strcmp((s1), (s2))
43
44 /*
45 * Handle out of memory conditions in early stages of main().
46 * Print error message and exit with failure status code.
47 * Does not return.
48 */
49 void NORET
oom_die(const char * argv0)50 oom_die(const char *argv0)
51 {
52 fprintf(stderr, "%s: out of memory\n", argv0);
53 exit(EXIT_FAILURE);
54 }
55
56 opts_t *
opts_new(void)57 opts_new(void)
58 {
59 opts_t *opts;
60
61 opts = malloc(sizeof(opts_t));
62 memset(opts, 0, sizeof(opts_t));
63
64 opts->stats_period = 1;
65 return opts;
66 }
67
68 void
opts_free(opts_t * opts)69 opts_free(opts_t *opts)
70 {
71 if (opts->spec) {
72 proxyspec_free(opts->spec);
73 }
74 if (opts->dropuser) {
75 free(opts->dropuser);
76 }
77 if (opts->dropgroup) {
78 free(opts->dropgroup);
79 }
80 if (opts->jaildir) {
81 free(opts->jaildir);
82 }
83 if (opts->pidfile) {
84 free(opts->pidfile);
85 }
86 if (opts->conffile) {
87 free(opts->conffile);
88 }
89 if (opts->connectlog) {
90 free(opts->connectlog);
91 }
92 if (opts->contentlog) {
93 free(opts->contentlog);
94 }
95 if (opts->contentlog_basedir) {
96 free(opts->contentlog_basedir);
97 }
98 memset(opts, 0, sizeof(opts_t));
99 free(opts);
100 }
101
102 /*
103 * Parse proxyspecs using a simple state machine.
104 */
105 void
proxyspec_parse(int * argc,char ** argv[],proxyspec_t ** opts_spec)106 proxyspec_parse(int *argc, char **argv[], proxyspec_t **opts_spec)
107 {
108 proxyspec_t *spec = NULL;
109 int af = AF_UNSPEC;
110 int state = 0;
111 char *la, *lp;
112
113 while ((*argc)--) {
114 switch (state) {
115 default:
116 case 0:
117 spec = malloc(sizeof(proxyspec_t));
118 memset(spec, 0, sizeof(proxyspec_t));
119 spec->next = *opts_spec;
120 *opts_spec = spec;
121
122 // @todo IPv6?
123 la = **argv;
124 state++;
125 break;
126 case 1:
127 lp = **argv;
128 af = sys_sockaddr_parse(&spec->listen_addr,
129 &spec->listen_addrlen,
130 la, lp,
131 sys_get_af(la),
132 EVUTIL_AI_PASSIVE);
133 if (af == -1) {
134 exit(EXIT_FAILURE);
135 }
136 state = 0;
137 break;
138 }
139 (*argv)++;
140 }
141 if (state != 0 && state != 4) {
142 fprintf(stderr, "Incomplete proxyspec!\n");
143 exit(EXIT_FAILURE);
144 }
145 }
146
147 /*
148 * Clear and free a proxy spec.
149 */
150 void
proxyspec_free(proxyspec_t * spec)151 proxyspec_free(proxyspec_t *spec)
152 {
153 do {
154 proxyspec_t *next = spec->next;
155 memset(spec, 0, sizeof(proxyspec_t));
156 free(spec);
157 spec = next;
158 } while (spec);
159 }
160
161 /*
162 * Return text representation of proxy spec for display to the user.
163 * Returned string must be freed by caller.
164 */
165 char *
proxyspec_str(proxyspec_t * spec)166 proxyspec_str(proxyspec_t *spec)
167 {
168 char *s;
169 char *lhbuf, *lpbuf;
170 char *cbuf = NULL;
171 if (sys_sockaddr_str((struct sockaddr *)&spec->listen_addr,
172 spec->listen_addrlen, &lhbuf, &lpbuf) != 0) {
173 return NULL;
174 }
175 if (spec->connect_addrlen) {
176 char *chbuf, *cpbuf;
177 if (sys_sockaddr_str((struct sockaddr *)&spec->connect_addr,
178 spec->connect_addrlen,
179 &chbuf, &cpbuf) != 0) {
180 return NULL;
181 }
182 if (asprintf(&cbuf, "\nconnect= [%s]:%s", chbuf, cpbuf) < 0) {
183 return NULL;
184 }
185 free(chbuf);
186 free(cpbuf);
187 }
188 if (asprintf(&s, "listen=[%s]:%s %s", lhbuf, lpbuf, "tcp") < 0) {
189 s = NULL;
190 }
191 free(lhbuf);
192 free(lpbuf);
193 if (cbuf)
194 free(cbuf);
195 return s;
196 }
197
198 void
opts_set_user(opts_t * opts,const char * argv0,const char * optarg)199 opts_set_user(opts_t *opts, const char *argv0, const char *optarg)
200 {
201 if (!sys_isuser(optarg)) {
202 fprintf(stderr, "%s: '%s' is not an existing user\n",
203 argv0, optarg);
204 exit(EXIT_FAILURE);
205 }
206 if (opts->dropuser)
207 free(opts->dropuser);
208 opts->dropuser = strdup(optarg);
209 if (!opts->dropuser)
210 oom_die(argv0);
211 #ifdef DEBUG_OPTS
212 log_dbg_printf("User: %s\n", opts->dropuser);
213 #endif /* DEBUG_OPTS */
214 }
215
216 void
opts_set_group(opts_t * opts,const char * argv0,const char * optarg)217 opts_set_group(opts_t *opts, const char *argv0, const char *optarg)
218 {
219
220 if (!sys_isgroup(optarg)) {
221 fprintf(stderr, "%s: '%s' is not an existing group\n",
222 argv0, optarg);
223 exit(EXIT_FAILURE);
224 }
225 if (opts->dropgroup)
226 free(opts->dropgroup);
227 opts->dropgroup = strdup(optarg);
228 if (!opts->dropgroup)
229 oom_die(argv0);
230 #ifdef DEBUG_OPTS
231 log_dbg_printf("Group: %s\n", opts->dropgroup);
232 #endif /* DEBUG_OPTS */
233 }
234
235 void
opts_set_jaildir(opts_t * opts,const char * argv0,const char * optarg)236 opts_set_jaildir(opts_t *opts, const char *argv0, const char *optarg)
237 {
238 if (!sys_isdir(optarg)) {
239 fprintf(stderr, "%s: '%s' is not a directory\n", argv0, optarg);
240 exit(EXIT_FAILURE);
241 }
242 if (opts->jaildir)
243 free(opts->jaildir);
244 opts->jaildir = realpath(optarg, NULL);
245 if (!opts->jaildir) {
246 fprintf(stderr, "%s: Failed to realpath '%s': %s (%i)\n",
247 argv0, optarg, strerror(errno), errno);
248 exit(EXIT_FAILURE);
249 }
250 #ifdef DEBUG_OPTS
251 log_dbg_printf("Chroot: %s\n", opts->jaildir);
252 #endif /* DEBUG_OPTS */
253 }
254
255 void
opts_set_pidfile(opts_t * opts,const char * argv0,const char * optarg)256 opts_set_pidfile(opts_t *opts, const char *argv0, const char *optarg)
257 {
258 if (opts->pidfile)
259 free(opts->pidfile);
260 opts->pidfile = strdup(optarg);
261 if (!opts->pidfile)
262 oom_die(argv0);
263 #ifdef DEBUG_OPTS
264 log_dbg_printf("PidFile: %s\n", opts->pidfile);
265 #endif /* DEBUG_OPTS */
266 }
267
268 void
opts_set_connectlog(opts_t * opts,const char * argv0,const char * optarg)269 opts_set_connectlog(opts_t *opts, const char *argv0, const char *optarg)
270 {
271 if (opts->connectlog)
272 free(opts->connectlog);
273 if (!(opts->connectlog = sys_realdir(optarg))) {
274 if (errno == ENOENT) {
275 fprintf(stderr, "Directory part of '%s' does not "
276 "exist\n", optarg);
277 exit(EXIT_FAILURE);
278 } else {
279 fprintf(stderr, "Failed to realpath '%s': %s (%i)\n",
280 optarg, strerror(errno), errno);
281 oom_die(argv0);
282 }
283 }
284 #ifdef DEBUG_OPTS
285 log_dbg_printf("ConnectLog: %s\n", opts->connectlog);
286 #endif /* DEBUG_OPTS */
287 }
288
289 void
opts_set_contentlog(opts_t * opts,const char * argv0,const char * optarg)290 opts_set_contentlog(opts_t *opts, const char *argv0, const char *optarg)
291 {
292 if (opts->contentlog)
293 free(opts->contentlog);
294 if (!(opts->contentlog = sys_realdir(optarg))) {
295 if (errno == ENOENT) {
296 fprintf(stderr, "Directory part of '%s' does not "
297 "exist\n", optarg);
298 exit(EXIT_FAILURE);
299 } else {
300 fprintf(stderr, "Failed to realpath '%s': %s (%i)\n",
301 optarg, strerror(errno), errno);
302 oom_die(argv0);
303 }
304 }
305 opts->contentlog_isdir = 0;
306 opts->contentlog_isspec = 0;
307 #ifdef DEBUG_OPTS
308 log_dbg_printf("ContentLog: %s\n", opts->contentlog);
309 #endif /* DEBUG_OPTS */
310 }
311
312 void
opts_set_contentlogdir(opts_t * opts,const char * argv0,const char * optarg)313 opts_set_contentlogdir(opts_t *opts, const char *argv0, const char *optarg)
314 {
315 if (!sys_isdir(optarg)) {
316 fprintf(stderr, "%s: '%s' is not a directory\n", argv0, optarg);
317 exit(EXIT_FAILURE);
318 }
319 if (opts->contentlog)
320 free(opts->contentlog);
321 opts->contentlog = realpath(optarg, NULL);
322 if (!opts->contentlog) {
323 fprintf(stderr, "%s: Failed to realpath '%s': %s (%i)\n",
324 argv0, optarg, strerror(errno), errno);
325 exit(EXIT_FAILURE);
326 }
327 opts->contentlog_isdir = 1;
328 opts->contentlog_isspec = 0;
329 #ifdef DEBUG_OPTS
330 log_dbg_printf("ContentLogDir: %s\n", opts->contentlog);
331 #endif /* DEBUG_OPTS */
332 }
333
334 static void
opts_set_logbasedir(const char * argv0,const char * optarg,char ** basedir,char ** log)335 opts_set_logbasedir(const char *argv0, const char *optarg,
336 char **basedir, char **log)
337 {
338 char *lhs, *rhs, *p, *q;
339 size_t n;
340 if (*basedir)
341 free(*basedir);
342 if (*log)
343 free(*log);
344 if (log_content_split_pathspec(optarg, &lhs, &rhs) == -1) {
345 fprintf(stderr, "%s: Failed to split '%s' in lhs/rhs:"
346 " %s (%i)\n", argv0, optarg,
347 strerror(errno), errno);
348 exit(EXIT_FAILURE);
349 }
350 /* eliminate %% from lhs */
351 for (p = q = lhs; *p; p++, q++) {
352 if (q < p)
353 *q = *p;
354 if (*p == '%' && *(p+1) == '%')
355 p++;
356 }
357 *q = '\0';
358 /* all %% in lhs resolved to % */
359 if (sys_mkpath(lhs, 0777) == -1) {
360 fprintf(stderr, "%s: Failed to create '%s': %s (%i)\n",
361 argv0, lhs, strerror(errno), errno);
362 exit(EXIT_FAILURE);
363 }
364 *basedir = realpath(lhs, NULL);
365 if (!*basedir) {
366 fprintf(stderr, "%s: Failed to realpath '%s': %s (%i)\n",
367 argv0, lhs, strerror(errno), errno);
368 exit(EXIT_FAILURE);
369 }
370 /* count '%' in basedir */
371 for (n = 0, p = *basedir;
372 *p;
373 p++) {
374 if (*p == '%')
375 n++;
376 }
377 free(lhs);
378 n += strlen(*basedir);
379 if (!(lhs = malloc(n + 1)))
380 oom_die(argv0);
381 /* re-encoding % to %%, copying basedir to lhs */
382 for (p = *basedir, q = lhs;
383 *p;
384 p++, q++) {
385 *q = *p;
386 if (*q == '%')
387 *(++q) = '%';
388 }
389 *q = '\0';
390 /* lhs contains encoded realpathed basedir */
391 if (asprintf(log, "%s/%s", lhs, rhs) < 0)
392 oom_die(argv0);
393 free(lhs);
394 free(rhs);
395 }
396
397 void
opts_set_contentlogpathspec(opts_t * opts,const char * argv0,const char * optarg)398 opts_set_contentlogpathspec(opts_t *opts, const char *argv0, const char *optarg)
399 {
400 opts_set_logbasedir(argv0, optarg, &opts->contentlog_basedir,
401 &opts->contentlog);
402 opts->contentlog_isdir = 0;
403 opts->contentlog_isspec = 1;
404 #ifdef DEBUG_OPTS
405 log_dbg_printf("ContentLogPathSpec: basedir=%s, %s\n",
406 opts->contentlog_basedir, opts->contentlog);
407 #endif /* DEBUG_OPTS */
408 }
409
410 void
opts_set_daemon(opts_t * opts)411 opts_set_daemon(opts_t *opts)
412 {
413 opts->detach = 1;
414 }
415
416 void
opts_unset_daemon(opts_t * opts)417 opts_unset_daemon(opts_t *opts)
418 {
419 opts->detach = 0;
420 }
421
422 void
opts_set_debug(opts_t * opts)423 opts_set_debug(opts_t *opts)
424 {
425 log_dbg_mode(LOG_DBG_MODE_ERRLOG);
426 opts->debug = 1;
427 }
428
429 void
opts_unset_debug(opts_t * opts)430 opts_unset_debug(opts_t *opts)
431 {
432 log_dbg_mode(LOG_DBG_MODE_NONE);
433 opts->debug = 0;
434 }
435
436 void
opts_set_debug_level(const char * optarg)437 opts_set_debug_level(const char *optarg)
438 {
439 if (equal(optarg, "2")) {
440 log_dbg_mode(LOG_DBG_MODE_FINE);
441 } else if (equal(optarg, "3")) {
442 log_dbg_mode(LOG_DBG_MODE_FINER);
443 } else if (equal(optarg, "4")) {
444 log_dbg_mode(LOG_DBG_MODE_FINEST);
445 } else {
446 fprintf(stderr, "Invalid DebugLevel '%s', use 2-4\n", optarg);
447 exit(EXIT_FAILURE);
448 }
449 #ifdef DEBUG_OPTS
450 log_dbg_printf("DebugLevel: %s\n", optarg);
451 #endif /* DEBUG_OPTS */
452 }
453
454 void
opts_set_statslog(opts_t * opts)455 opts_set_statslog(opts_t *opts)
456 {
457 opts->statslog = 1;
458 }
459
460 void
opts_unset_statslog(opts_t * opts)461 opts_unset_statslog(opts_t *opts)
462 {
463 opts->statslog = 0;
464 }
465
466 static void
opts_set_open_files_limit(const char * value,int line_num)467 opts_set_open_files_limit(const char *value, int line_num)
468 {
469 unsigned int i = atoi(value);
470 if (i >= 50 && i <= 10000) {
471 struct rlimit rl;
472 rl.rlim_cur = i;
473 rl.rlim_max = i;
474 if (setrlimit(RLIMIT_NOFILE, &rl) == -1) {
475 fprintf(stderr, "Failed setting OpenFilesLimit\n");
476 if (errno) {
477 fprintf(stderr, "%s\n", strerror(errno));
478 }
479 exit(EXIT_FAILURE);
480 }
481 } else {
482 fprintf(stderr, "Invalid OpenFilesLimit %s on line %d, use 50-10000\n", value, line_num);
483 exit(EXIT_FAILURE);
484 }
485 #ifdef DEBUG_OPTS
486 log_dbg_printf("OpenFilesLimit: %u\n", i);
487 #endif /* DEBUG_OPTS */
488 }
489
490 static int
check_value_yesno(const char * value,const char * name,int line_num)491 check_value_yesno(const char *value, const char *name, int line_num)
492 {
493 if (equal(value, "yes")) {
494 return 1;
495 } else if (equal(value, "no")) {
496 return 0;
497 }
498 fprintf(stderr, "Error in conf: Invalid '%s' value '%s' on line %d, use yes|no\n", name, value, line_num);
499 return -1;
500 }
501
502 #define MAX_TOKEN 10
503
504 static int
set_option(opts_t * opts,const char * argv0,const char * name,char * value,int line_num)505 set_option(opts_t *opts, const char *argv0,
506 const char *name, char *value, int line_num)
507 {
508 int yes;
509 int retval = -1;
510
511 if (equal(name, "User")) {
512 opts_set_user(opts, argv0, value);
513 } else if (equal(name, "Group")) {
514 opts_set_group(opts, argv0, value);
515 } else if (equal(name, "Chroot")) {
516 opts_set_jaildir(opts, argv0, value);
517 } else if (equal(name, "PidFile")) {
518 opts_set_pidfile(opts, argv0, value);
519 } else if (equal(name, "ConnectLog")) {
520 opts_set_connectlog(opts, argv0, value);
521 } else if (equal(name, "ContentLog")) {
522 opts_set_contentlog(opts, argv0, value);
523 } else if (equal(name, "ContentLogDir")) {
524 opts_set_contentlogdir(opts, argv0, value);
525 } else if (equal(name, "ContentLogPathSpec")) {
526 opts_set_contentlogpathspec(opts, argv0, value);
527 } else if (equal(name, "Daemon")) {
528 yes = check_value_yesno(value, "Daemon", line_num);
529 if (yes == -1) {
530 goto leave;
531 }
532 yes ? opts_set_daemon(opts) : opts_unset_daemon(opts);
533 #ifdef DEBUG_OPTS
534 log_dbg_printf("Daemon: %u\n", opts->detach);
535 #endif /* DEBUG_OPTS */
536 } else if (equal(name, "Debug")) {
537 yes = check_value_yesno(value, "Debug", line_num);
538 if (yes == -1) {
539 goto leave;
540 }
541 yes ? opts_set_debug(opts) : opts_unset_debug(opts);
542 #ifdef DEBUG_OPTS
543 log_dbg_printf("Debug: %u\n", opts->debug);
544 #endif /* DEBUG_OPTS */
545 } else if (equal(name, "DebugLevel")) {
546 opts_set_debug_level(value);
547 } else if (equal(name, "ProxySpec")) {
548 /* Use MAX_TOKEN instead of computing the actual number of tokens in value */
549 char **argv = malloc(sizeof(char *) * MAX_TOKEN);
550 char **save_argv = argv;
551 int argc = 0;
552 char *p, *last = NULL;
553
554 for ((p = strtok_r(value, " ", &last));
555 p;
556 (p = strtok_r(NULL, " ", &last))) {
557 /* Limit max # token */
558 if (argc < MAX_TOKEN) {
559 argv[argc++] = p;
560 } else {
561 break;
562 }
563 }
564
565 proxyspec_parse(&argc, &argv, &opts->spec);
566 free(save_argv);
567 } else if (!strncasecmp(name, "LogStats", 9)) {
568 yes = check_value_yesno(value, "LogStats", line_num);
569 if (yes == -1) {
570 goto leave;
571 }
572 yes ? opts_set_statslog(opts) : opts_unset_statslog(opts);
573 #ifdef DEBUG_OPTS
574 log_dbg_printf("LogStats: %u\n", opts->statslog);
575 #endif /* DEBUG_OPTS */
576 } else if (!strncasecmp(name, "StatsPeriod", 12)) {
577 unsigned int i = atoi(value);
578 if (i >= 1 && i <= 10) {
579 opts->stats_period = i;
580 } else {
581 fprintf(stderr, "Invalid StatsPeriod %s on line %d, use 1-10\n", value, line_num);
582 goto leave;
583 }
584 #ifdef DEBUG_OPTS
585 log_dbg_printf("StatsPeriod: %u\n", opts->stats_period);
586 #endif /* DEBUG_OPTS */
587 } else if (!strncasecmp(name, "OpenFilesLimit", 15)) {
588 opts_set_open_files_limit(value, line_num);
589 } else {
590 #ifdef DEBUG_OPTS
591 log_dbg_printf("Skipping option '%s' on line %d\n", name, line_num);
592 #endif /* DEBUG_OPTS */
593 }
594
595 retval = 0;
596 leave:
597 return retval;
598 }
599
600 /*
601 * Separator param is needed for command line options only.
602 * Conf file option separator is ' '.
603 */
604 static int
get_name_value(char ** name,char ** value,const char sep)605 get_name_value(char **name, char **value, const char sep)
606 {
607 char *n, *v, *value_end;
608 int retval = -1;
609
610 /* Skip to the end of option name and terminate it with '\0' */
611 for (n = *name;; n++) {
612 /* White spaces possible around separator,
613 * if the command line option is passed between the quotes */
614 if (*n == ' ' || *n == '\t' || *n == sep) {
615 *n = '\0';
616 n++;
617 break;
618 }
619 if (*n == '\0') {
620 n = NULL;
621 break;
622 }
623 }
624
625 /* No option name */
626 if (n == NULL) {
627 fprintf(stderr, "Error in option: No option name\n");
628 goto leave;
629 }
630
631 /* White spaces possible before value and around separator,
632 * if the command line option is passed between the quotes */
633 while (*n == ' ' || *n == '\t' || *n == sep) {
634 n++;
635 }
636
637 *value = n;
638
639 /* Find end of value and terminate it with '\0'
640 * Find first occurrence of trailing white space */
641 value_end = NULL;
642 for (v = *value;; v++) {
643 if (*v == '\0') {
644 break;
645 }
646 if (*v == '\r' || *v == '\n') {
647 *v = '\0';
648 break;
649 }
650 if (*v == ' ' || *v == '\t') {
651 if (!value_end) {
652 value_end = v;
653 }
654 } else {
655 value_end = NULL;
656 }
657 }
658
659 if (value_end) {
660 *value_end = '\0';
661 }
662
663 retval = 0;
664 leave:
665 return retval;
666 }
667
668 int
opts_set_option(opts_t * opts,const char * argv0,const char * optarg)669 opts_set_option(opts_t *opts, const char *argv0, const char *optarg)
670 {
671 char *name, *value;
672 int retval = -1;
673 char *line = strdup(optarg);
674
675 /* White spaces possible before option name,
676 * if the command line option is passed between the quotes */
677 for (name = line; *name == ' ' || *name == '\t'; name++);
678
679 /* Command line option separator is '=' */
680 retval = get_name_value(&name, &value, '=');
681 if (retval == 0) {
682 /* Line number param is for conf file, pass 0 for command line options */
683 retval = set_option(opts, argv0, name, value, 0);
684 }
685
686 if (line)
687 free(line);
688 return retval;
689 }
690
691 int
opts_load_conffile(opts_t * opts,const char * argv0)692 opts_load_conffile(opts_t *opts, const char *argv0)
693 {
694 int retval, line_num;
695 char *line, *name, *value;
696 size_t line_len;
697 FILE *f;
698
699 f = fopen(opts->conffile, "r");
700 if (!f) {
701 fprintf(stderr, "Error opening conf file '%s': %s\n", opts->conffile, strerror(errno));
702 return -1;
703 }
704
705 line = NULL;
706 line_num = 0;
707 retval = -1;
708 while (!feof(f)) {
709 if (getline(&line, &line_len, f) == -1) {
710 break;
711 }
712 if (line == NULL) {
713 fprintf(stderr, "Error in conf file: getline() returns NULL line after line %d\n", line_num);
714 goto leave;
715 }
716 line_num++;
717
718 /* Skip white space */
719 for (name = line; *name == ' ' || *name == '\t'; name++);
720
721 /* Skip comments and empty lines */
722 if ((name[0] == '\0') || (name[0] == '#') || (name[0] == ';') ||
723 (name[0] == '\r') || (name[0] == '\n')) {
724 continue;
725 }
726
727 retval = get_name_value(&name, &value, ' ');
728 if (retval == 0) {
729 retval = set_option(opts, argv0, name, value, line_num);
730 }
731
732 if (retval == -1) {
733 goto leave;
734 }
735 free(line);
736 line = NULL;
737 }
738
739 leave:
740 fclose(f);
741 if (line)
742 free(line);
743 return retval;
744 }
745
746 /* vim: set noet ft=c: */
747