1 /*
2  * Copyright (c) 2007-2012, Vsevolod Stakhov
3  * All rights reserved.
4 
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are met:
7  * Redistributions of source code must retain the above copyright notice, this
8  * list of conditions and the following disclaimer. Redistributions in binary form
9  * must reproduce the above copyright notice, this list of conditions and the
10  * following disclaimer in the documentation and/or other materials provided with
11  * the distribution. Neither the name of the author nor the names of its
12  * contributors may be used to endorse or promote products derived from this
13  * software without specific prior written permission.
14 
15  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
16  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
17  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18  * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
19  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
21  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
22  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
23  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25  */
26 
27 #include <assert.h>
28 #include "config.h"
29 
30 #include "cfg_file.h"
31 #include "rmilter.h"
32 
33 extern int yylineno;
34 extern char *yytext;
35 extern char* fnames_stack[];
36 extern int include_stack_ptr;
37 
parse_err(const char * fmt,...)38 void parse_err(const char *fmt, ...)
39 {
40 	va_list aq;
41 	char logbuf[BUFSIZ], readbuf[32];
42 	int r;
43 
44 	va_start (aq, fmt);
45 	rmilter_strlcpy (readbuf, yytext, sizeof(readbuf));
46 
47 	if (include_stack_ptr > 0) {
48 		r = snprintf(logbuf, sizeof(logbuf), "config file <%s> parse error! line: %d, "
49 				"text: %s, reason: ", fnames_stack[include_stack_ptr - 1],
50 				yylineno, readbuf);
51 	}
52 	else {
53 		r = snprintf(logbuf, sizeof(logbuf), "config file parse error! line: %d, "
54 						"text: %s, reason: ",
55 						yylineno, readbuf);
56 	}
57 	r += vsnprintf(logbuf + r, sizeof(logbuf) - r, fmt, aq);
58 
59 	va_end (aq);
60 	fprintf (stderr, "%s\n", logbuf);
61 	syslog (LOG_ERR, "%s", logbuf);
62 }
63 
parse_warn(const char * fmt,...)64 void parse_warn(const char *fmt, ...)
65 {
66 	va_list aq;
67 	char logbuf[BUFSIZ], readbuf[32];
68 	int r;
69 
70 	va_start (aq, fmt);
71 	rmilter_strlcpy (readbuf, yytext, sizeof(readbuf));
72 
73 	r = snprintf(logbuf, sizeof(logbuf),
74 			"config file parse warning! line: %d, text: %s, reason: ", yylineno,
75 			readbuf);
76 	r += vsnprintf(logbuf + r, sizeof(logbuf) - r, fmt, aq);
77 
78 	va_end (aq);
79 	syslog (LOG_ERR, "%s", logbuf);
80 }
81 
copy_regexp(char ** dst,const char * src)82 static size_t copy_regexp(char **dst, const char *src)
83 {
84 	size_t len;
85 	if (!src || *src == '\0')
86 		return 0;
87 
88 	len = strlen (src);
89 
90 	/* Skip slashes */
91 	if (*src == '/') {
92 		src++;
93 		len--;
94 	}
95 	if (src[len - 1] == '/') {
96 		len--;
97 	}
98 
99 	*dst = malloc (len + 1);
100 	if (!*dst)
101 		return 0;
102 
103 	return rmilter_strlcpy (*dst, src, len + 1);
104 }
105 
add_cache_server(struct config_file * cf,char * str,char * str2,int type)106 int add_cache_server(struct config_file *cf, char *str, char *str2,
107 		int type)
108 {
109 	char *cur_tok, *err_str;
110 	struct cache_server *mc = NULL;
111 	uint16_t port;
112 	unsigned *pnum;
113 
114 	if (str == NULL) {
115 		return 0;
116 	}
117 
118 	switch (type) {
119 	case CACHE_SERVER_GREY:
120 		pnum = &cf->cache_servers_grey_num;
121 		mc = cf->cache_servers_grey;
122 		break;
123 	case CACHE_SERVER_WHITE:
124 		pnum = &cf->cache_servers_white_num;
125 		mc = cf->cache_servers_white;
126 		break;
127 	case CACHE_SERVER_LIMITS:
128 		pnum = &cf->cache_servers_limits_num;
129 		mc = cf->cache_servers_limits;
130 		break;
131 	case CACHE_SERVER_ID:
132 		pnum = &cf->cache_servers_id_num;
133 		mc = cf->cache_servers_id;
134 		break;
135 	case CACHE_SERVER_COPY:
136 		pnum = &cf->cache_servers_copy_num;
137 		mc = cf->cache_servers_copy;
138 		break;
139 	case CACHE_SERVER_SPAM:
140 		pnum = &cf->cache_servers_spam_num;
141 		mc = cf->cache_servers_spam;
142 		break;
143 	}
144 
145 	if (*pnum >= MAX_CACHE_SERVERS) {
146 		yyerror ("yyparse: maximum number of cache servers is reached %d",
147 				MAX_CACHE_SERVERS);
148 		return 0;
149 	}
150 
151 	mc += *pnum;
152 	cur_tok = strsep (&str, ":");
153 
154 	if (cur_tok == NULL || *cur_tok == '\0') {
155 		return 0;
156 	}
157 
158 	/* cur_tok - server name, str - server port */
159 	if (str == NULL) {
160 		port = DEFAULT_MEMCACHED_PORT;
161 	}
162 	else {
163 		port = strtoul (str, &err_str, 10);
164 		if (*err_str != '\0') {
165 			yyerror ("yyparse: bad memcached port: %s", str);
166 			return 0;
167 		}
168 	}
169 
170 	mc->addr = strdup (cur_tok);
171 	mc->port = port;
172 
173 	if (str2 != NULL) {
174 		msg_warn("mirrored servers are no longer supported; "
175 				"server %s will be ignored", str2);
176 	}
177 
178 	(*pnum)++;
179 
180 	return 1;
181 }
182 
add_clamav_server(struct config_file * cf,char * str)183 int add_clamav_server(struct config_file *cf, char *str)
184 {
185 	char *cur_tok, *err_str;
186 	struct clamav_server *srv;
187 	struct hostent *he;
188 
189 	if (str == NULL)
190 		return 0;
191 
192 	if (cf->clamav_servers_num == MAX_CLAMAV_SERVERS) {
193 		yyerror ("yyparse: maximum number of clamav servers is reached %d",
194 				MAX_CLAMAV_SERVERS);
195 	}
196 
197 	srv = &cf->clamav_servers[cf->clamav_servers_num];
198 
199 	if (srv == NULL)
200 		return 0;
201 
202 	cur_tok = strsep (&str, ":");
203 
204 	if (cur_tok == NULL || *cur_tok == '\0') {
205 		return 0;
206 	}
207 
208 	srv->name = strdup (cur_tok);
209 
210 	if (str != NULL) {
211 		/* We have also port */
212 		srv->port = strtoul (str, NULL, 10);
213 	}
214 
215 	/* Try to parse priority */
216 	cur_tok = strsep (&str, ":");
217 	if (str != NULL && *str != '\0') {
218 		srv->up.priority = strtoul (str, NULL, 10);
219 	}
220 
221 	cf->clamav_servers_num ++;
222 
223 	return 1;
224 }
225 
add_spamd_server(struct config_file * cf,char * str,int is_extra)226 int add_spamd_server(struct config_file *cf, char *str, int is_extra)
227 {
228 	char *cur_tok, *err_str;
229 	struct spamd_server *srv;
230 	struct hostent *he;
231 
232 	if (str == NULL)
233 		return 0;
234 
235 	if (is_extra) {
236 		if (cf->extra_spamd_servers_num == MAX_SPAMD_SERVERS) {
237 			yyerror ("yyparse: maximum number of spamd servers is reached %d",
238 					MAX_SPAMD_SERVERS);
239 			return -1;
240 		}
241 	}
242 	else {
243 		if (cf->spamd_servers_num == MAX_SPAMD_SERVERS) {
244 			yyerror ("yyparse: maximum number of spamd servers is reached %d",
245 					MAX_SPAMD_SERVERS);
246 			return -1;
247 		}
248 	}
249 
250 	if (is_extra) {
251 		srv = &cf->extra_spamd_servers[cf->extra_spamd_servers_num];
252 	}
253 	else {
254 		srv = &cf->spamd_servers[cf->spamd_servers_num];
255 	}
256 
257 	if (*str == 'r' && *(str + 1) == ':') {
258 		srv->type = SPAMD_RSPAMD;
259 		str += 2;
260 	}
261 	else {
262 		srv->type = SPAMD_RSPAMD;
263 	}
264 
265 	cur_tok = strsep (&str, ":");
266 
267 	if (cur_tok == NULL || *cur_tok == '\0') {
268 		return 0;
269 	}
270 
271 	srv->name = strdup (cur_tok);
272 
273 	if (str != NULL) {
274 		/* We have also port */
275 		srv->port = strtoul (str, NULL, 10);
276 	}
277 
278 	/* Try to parse priority */
279 	cur_tok = strsep (&str, ":");
280 	if (str != NULL && *str != '\0') {
281 		srv->up.priority = strtoul (str, NULL, 10);
282 	}
283 
284 	if (is_extra) {
285 		cf->extra_spamd_servers_num++;
286 	}
287 	else {
288 		cf->spamd_servers_num++;
289 	}
290 	return 1;
291 }
292 
add_ip_radix(radix_compressed_t ** tree,char * ipnet)293 int add_ip_radix (radix_compressed_t **tree, char *ipnet)
294 {
295 	if (!radix_add_generic_iplist (ipnet, tree, true)) {
296 		yyerror ("add_ip_radix: cannot insert ip to tree: %s",
297 				ipnet);
298 		return 0;
299 	}
300 
301 	return 1;
302 }
303 
304 #ifdef WITH_DKIM
add_hashed_header(const char * name,struct dkim_hash_entry ** hash)305 static void add_hashed_header(const char *name, struct dkim_hash_entry **hash)
306 {
307 	struct dkim_hash_entry *new;
308 
309 	new = malloc (sizeof(struct dkim_hash_entry));
310 	new->name = strdup (name);
311 	rmilter_str_lc (new->name, strlen (new->name));
312 	HASH_ADD_KEYPTR (hh, *hash, new->name, strlen (new->name), new);
313 }
314 #endif
315 
init_defaults(struct config_file * cfg)316 void init_defaults(struct config_file *cfg)
317 {
318 	memset (cfg, 0, sizeof (*cfg));
319 
320 	cfg->wlist_rcpt_global = NULL;
321 	cfg->wlist_rcpt_limit = NULL;
322 	cfg->clamav_connect_timeout = DEFAULT_CLAMAV_CONNECT_TIMEOUT;
323 	cfg->clamav_port_timeout = DEFAULT_CLAMAV_PORT_TIMEOUT;
324 	cfg->clamav_results_timeout = DEFAULT_CLAMAV_RESULTS_TIMEOUT;
325 	cfg->cache_connect_timeout = DEFAULT_MEMCACHED_CONNECT_TIMEOUT;
326 	cfg->spamd_connect_timeout = DEFAULT_SPAMD_CONNECT_TIMEOUT;
327 	cfg->spamd_results_timeout = DEFAULT_SPAMD_RESULTS_TIMEOUT;
328 
329 	cfg->clamav_error_time = DEFAULT_UPSTREAM_ERROR_TIME;
330 	cfg->clamav_dead_time = DEFAULT_UPSTREAM_DEAD_TIME;
331 	cfg->clamav_maxerrors = DEFAULT_UPSTREAM_MAXERRORS;
332 
333 	cfg->spamd_error_time = DEFAULT_UPSTREAM_ERROR_TIME;
334 	cfg->spamd_dead_time = DEFAULT_UPSTREAM_DEAD_TIME;
335 	cfg->spamd_maxerrors = DEFAULT_UPSTREAM_MAXERRORS;
336 	cfg->spamd_reject_message = strdup (DEFAUL_SPAMD_REJECT);
337 	cfg->rspamd_metric = strdup (DEFAULT_RSPAMD_METRIC);
338 	cfg->spam_header = strdup (DEFAULT_SPAM_HEADER);
339 	cfg->spam_header_value = strdup (DEFAULT_SPAM_HEADER_VALUE);
340 	cfg->spamd_retry_count = DEFAULT_SPAMD_RETRY_COUNT;
341 	cfg->spamd_retry_timeout = DEFAULT_SPAMD_RETRY_TIMEOUT;
342 	cfg->spamd_temp_fail = 0;
343 	cfg->spam_bar_char = strdup ("x");
344 
345 	cfg->cache_error_time = DEFAULT_UPSTREAM_ERROR_TIME;
346 	cfg->cache_dead_time = DEFAULT_UPSTREAM_DEAD_TIME;
347 	cfg->cache_maxerrors = DEFAULT_UPSTREAM_MAXERRORS;
348 
349 	cfg->grey_whitelist_tree = radix_create_compressed ();
350 	cfg->limit_whitelist_tree = radix_create_compressed ();
351 	cfg->spamd_whitelist = radix_create_compressed ();
352 	cfg->clamav_whitelist = radix_create_compressed ();
353 	cfg->dkim_ip_tree = radix_create_compressed ();
354 	cfg->our_networks = radix_create_compressed ();
355 	cfg->greylisted_message = strdup (DEFAULT_GREYLISTED_MESSAGE);
356 	/* Defaults for greylisting */
357 	/* 1d for greylisting data */
358 	cfg->greylisting_expire = 86400;
359 	/* 3d for whitelisting */
360 	cfg->whitelisting_expire = cfg->greylisting_expire * 3;
361 	cfg->greylisting_timeout = 300;
362 	cfg->white_prefix = strdup ("white");
363 	cfg->grey_prefix = strdup ("grey");
364 	cfg->id_prefix = strdup ("id");
365 	cfg->spamd_spam_add_header = 1;
366 
367 	cfg->cache_copy_prob = 100.0;
368 
369 	cfg->spamd_soft_fail = 1;
370 	cfg->spamd_greylist = 1;
371 	cfg->greylisting_enable = 1;
372 	cfg->ratelimit_enable = 1;
373 
374 	cfg->dkim_auth_only = 1;
375 	cfg->dkim_enable = 1;
376 	cfg->pid_file = NULL;
377 	cfg->tempfiles_mode = 00600;
378 
379 #if 0
380 	/* Init static defaults */
381 	white_from_abuse.addr = "abuse";
382 	white_from_abuse.len = sizeof ("abuse") - 1;
383 	white_from_postmaster.addr = "postmaster";
384 	white_from_postmaster.len = sizeof ("postmaster") - 1;
385 	LIST_INSERT_HEAD (&cfg->whitelist_static, &white_from_abuse, next);
386 	LIST_INSERT_HEAD (&cfg->whitelist_static, &white_from_postmaster, next);
387 #endif
388 
389 #ifdef WITH_DKIM
390 	cfg->dkim_lib = dkim_init (NULL, NULL);
391 	/* Add recommended by rfc headers */
392 	add_hashed_header ("from", &cfg->headers);
393 	add_hashed_header ("sender", &cfg->headers);
394 	add_hashed_header ("reply-to", &cfg->headers);
395 	add_hashed_header ("subject", &cfg->headers);
396 	add_hashed_header ("date", &cfg->headers);
397 	add_hashed_header ("message-id", &cfg->headers);
398 	add_hashed_header ("to", &cfg->headers);
399 	add_hashed_header ("cc", &cfg->headers);
400 	add_hashed_header ("date", &cfg->headers);
401 	add_hashed_header ("mime-version", &cfg->headers);
402 	add_hashed_header ("content-type", &cfg->headers);
403 	add_hashed_header ("content-transfer-encoding", &cfg->headers);
404 	add_hashed_header ("resent-to", &cfg->headers);
405 	add_hashed_header ("resent-cc", &cfg->headers);
406 	add_hashed_header ("resent-from", &cfg->headers);
407 	add_hashed_header ("resent-sender", &cfg->headers);
408 	add_hashed_header ("resent-message-id", &cfg->headers);
409 	add_hashed_header ("in-reply-to", &cfg->headers);
410 	add_hashed_header ("references", &cfg->headers);
411 	add_hashed_header ("list-id", &cfg->headers);
412 	add_hashed_header ("list-owner", &cfg->headers);
413 	add_hashed_header ("list-unsubscribe", &cfg->headers);
414 	add_hashed_header ("list-subscribe", &cfg->headers);
415 	add_hashed_header ("list-post", &cfg->headers);
416 	/* TODO: make it configurable */
417 #endif
418 }
419 
free_config(struct config_file * cfg)420 void free_config(struct config_file *cfg)
421 {
422 	unsigned int i;
423 	struct addr_list_entry *addr_cur, *addr_tmp;
424 
425 	if (cfg->pid_file) {
426 		free (cfg->pid_file);
427 	}
428 	if (cfg->temp_dir) {
429 		free (cfg->temp_dir);
430 	}
431 	if (cfg->sock_cred) {
432 		free (cfg->sock_cred);
433 	}
434 
435 	if (cfg->special_mid_re) {
436 		pcre_free (cfg->special_mid_re);
437 	}
438 
439 	for (i = 0; i < cfg->clamav_servers_num; i++) {
440 		free (cfg->clamav_servers[i].name);
441 	}
442 	for (i = 0; i < cfg->spamd_servers_num; i++) {
443 		free (cfg->spamd_servers[i].name);
444 	}
445 
446 	/* Free whitelists and bounce list*/
447 	clear_rcpt_whitelist (&cfg->wlist_rcpt_global);
448 	clear_rcpt_whitelist (&cfg->wlist_rcpt_limit);
449 	clear_rcpt_whitelist (&cfg->extended_rcpts);
450 
451 	HASH_ITER (hh, cfg->bounce_addrs, addr_cur, addr_tmp) {
452 		HASH_DEL (cfg->bounce_addrs, addr_cur);
453 		free (addr_cur->addr);
454 		free (addr_cur);
455 	}
456 
457 	radix_destroy_compressed (cfg->grey_whitelist_tree);
458 	radix_destroy_compressed (cfg->spamd_whitelist);
459 	radix_destroy_compressed (cfg->clamav_whitelist);
460 	radix_destroy_compressed (cfg->limit_whitelist_tree);
461 	radix_destroy_compressed (cfg->dkim_ip_tree);
462 	radix_destroy_compressed (cfg->our_networks);
463 
464 	if (cfg->spamd_reject_message) {
465 		free (cfg->spamd_reject_message);
466 	}
467 	if (cfg->rspamd_metric) {
468 		free (cfg->rspamd_metric);
469 	}
470 	if (cfg->spam_header) {
471 		free (cfg->spam_header);
472 	}
473 	if (cfg->spam_header_value) {
474 		free (cfg->spam_header_value);
475 	}
476 	if (cfg->id_prefix) {
477 		free (cfg->id_prefix);
478 	}
479 	if (cfg->grey_prefix) {
480 		free (cfg->grey_prefix);
481 	}
482 	if (cfg->white_prefix) {
483 		free (cfg->white_prefix);
484 	}
485 	if (cfg->cache_password) {
486 		free (cfg->cache_password);
487 	}
488 	if (cfg->cache_dbname) {
489 		free (cfg->cache_dbname);
490 	}
491 	if (cfg->cache_copy_channel) {
492 		free (cfg->cache_copy_channel);
493 	}
494 	if (cfg->cache_spam_channel) {
495 		free (cfg->cache_spam_channel);
496 	}
497 	if (cfg->greylisted_message) {
498 		free (cfg->greylisted_message);
499 	}
500 	if (cfg->spam_bar_char) {
501 		free (cfg->spam_bar_char);
502 	}
503 
504 #ifdef WITH_DKIM
505 	struct dkim_hash_entry *curh, *tmph;
506 	struct dkim_domain_entry *curd, *tmpd;
507 
508 	if (cfg->dkim_lib) {
509 		dkim_close (cfg->dkim_lib);
510 	}
511 	HASH_ITER (hh, cfg->headers, curh, tmph) {
512 		HASH_DEL (cfg->headers, curh); /* delete; users advances to next */
513 		free (curh->name);
514 		free (curh);
515 	}
516 	HASH_ITER (hh, cfg->dkim_domains, curd, tmpd) {
517 		HASH_DEL (cfg->dkim_domains, curd); /* delete; users advances to next */
518 		if (curd->key != MAP_FAILED && curd->key != NULL) {
519 			munmap (curd->key, curd->keylen);
520 		}
521 		if (curd->domain) {
522 			free (curd->domain);
523 		}
524 		if (curd->selector) {
525 			free (curd->selector);
526 		}
527 		if (curd->keyfile) {
528 			free (curd->keyfile);
529 		}
530 		free (curd);
531 	}
532 #endif
533 }
534 void
add_rcpt_whitelist(struct whitelisted_rcpt_entry ** head,const char * rcpt)535 add_rcpt_whitelist (struct whitelisted_rcpt_entry **head, const char *rcpt)
536 {
537 	struct whitelisted_rcpt_entry *t;
538 	t = (struct whitelisted_rcpt_entry *) malloc (
539 			sizeof(struct whitelisted_rcpt_entry));
540 	if (*rcpt == '@') {
541 		t->type = WLIST_RCPT_DOMAIN;
542 		rcpt++;
543 	}
544 	else if (strchr (rcpt, '@') != NULL) {
545 		t->type = WLIST_RCPT_USERDOMAIN;
546 	}
547 	else {
548 		t->type = WLIST_RCPT_USER;
549 	}
550 	t->rcpt = strdup (rcpt);
551 	t->len = strlen (t->rcpt);
552 
553 	HASH_ADD_KEYPTR (hh, *head, t->rcpt, t->len, t);
554 }
555 
556 void
clear_rcpt_whitelist(struct whitelisted_rcpt_entry ** head)557 clear_rcpt_whitelist (struct whitelisted_rcpt_entry **head)
558 {
559 	struct whitelisted_rcpt_entry *t, *tmp;
560 
561 	HASH_ITER (hh, *head, t, tmp) {
562 		HASH_DEL (*head, t);
563 		free (t->rcpt);
564 		free (t);
565 	}
566 }
567 
568 int
is_whitelisted_rcpt(struct whitelisted_rcpt_entry ** head,const char * str)569 is_whitelisted_rcpt (struct whitelisted_rcpt_entry **head, const char *str)
570 {
571 	int len;
572 	struct whitelisted_rcpt_entry *entry, *list;
573 	char rcptbuf[ADDRLEN + 1], *domain;
574 
575 	if (*str == '<') {
576 		str++;
577 	}
578 
579 	len = strcspn (str, ">");
580 	rmilter_strlcpy (rcptbuf, str, MIN(len + 1, sizeof(rcptbuf)));
581 	rmilter_str_lc (rcptbuf, strlen (rcptbuf));
582 
583 	if (len > 0) {
584 		list = *head;
585 		/* Initially search for userdomain */
586 		HASH_FIND_STR(list, rcptbuf, entry);
587 		if (entry != NULL && entry->type == WLIST_RCPT_USERDOMAIN) {
588 			return 1;
589 		}
590 
591 		domain = strchr (rcptbuf, '@');
592 		if (domain == NULL && entry != NULL && entry->type == WLIST_RCPT_USER) {
593 			return 1;
594 		}
595 
596 		/* Search for user */
597 		if (domain != NULL) {
598 			*domain = '\0';
599 		}
600 
601 		HASH_FIND_STR (list, rcptbuf, entry);
602 		if (entry != NULL && entry->type == WLIST_RCPT_USER) {
603 			return 1;
604 		}
605 		if (domain != NULL) {
606 			/* Search for domain */
607 			domain++;
608 			HASH_FIND_STR (list, domain, entry);
609 			if (entry != NULL && entry->type == WLIST_RCPT_DOMAIN) {
610 				return 1;
611 			}
612 		}
613 	}
614 
615 	return 0;
616 }
617 
618 char *
trim_quotes(char * in)619 trim_quotes(char *in)
620 {
621 	char *res = in;
622 	size_t len;
623 
624 	assert(in != NULL);
625 
626 	len = strlen (in);
627 
628 	if (*in == '"') {
629 		res = strdup (in + 1);
630 		len = strlen (res);
631 		free (in);
632 	}
633 
634 	if (len > 1 && res[len - 1] == '"') {
635 		res[len - 1] = '\0';
636 	}
637 
638 	return res;
639 }
640 
641 /*
642  * vi:ts=4
643  */
644