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