1 /* $Id: urlcheck.c,v 1.43 2013/01/19 16:01:15 manu Exp $ */
2 
3 /*
4  * Copyright (c) 2006-2007 Emmanuel Dreyfus
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  * 3. All advertising materials mentioning features or use of this software
16  *    must display the following acknowledgement:
17  *        This product includes software developed by Emmanuel Dreyfus
18  *
19  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
20  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
21  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22  * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
23  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
27  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
29  * OF THE POSSIBILITY OF SUCH DAMAGE.
30  */
31 
32 #include "config.h"
33 
34 #ifdef USE_CURL
35 
36 #ifdef HAVE_SYS_CDEFS_H
37 #include <sys/cdefs.h>
38 #ifdef __RCSID
39 __RCSID("$Id: urlcheck.c,v 1.43 2013/01/19 16:01:15 manu Exp $");
40 #endif
41 #endif
42 
43 #include <stdio.h>
44 #include <unistd.h>
45 #include <stdlib.h>
46 #include <string.h>
47 #include <syslog.h>
48 #include <errno.h>
49 #include <ctype.h>
50 #include <sysexits.h>
51 #include <signal.h>
52 
53 #if defined(HAVE_OLD_QUEUE_H) || !defined(HAVE_SYS_QUEUE_H)
54 #include "queue.h"
55 #else
56 #include <sys/queue.h>
57 #endif
58 #include <sys/types.h>
59 
60 #include "milter-greylist.h"
61 #include "pending.h"
62 #include "spf.h"
63 #include "acl.h"
64 #include "conf.h"
65 #include "urlcheck.h"
66 #include "sync.h"
67 
68 #ifdef USE_DMALLOC
69 #include <dmalloc.h>
70 #endif
71 
72 struct urlcheck_data {
73 	struct iovec ud_iov;
74 	int ud_error;
75 };
76 
77 #define BOUNDARY_LEN	4
78 struct post_data {
79 	struct mlfi_priv *pd_priv;
80 	struct line *pd_curhdr;
81 	struct line *pd_curbody;
82 	char *pd_curptr;
83 	int pd_done;
84 	char pd_boundary[BOUNDARY_LEN + 1];
85 };
86 
87 /* Header and trailer for POST requests */
88 char post_header_templ[] =
89     "--%s\r\n"
90     "Content-Disposition: form-data; name=\"msg\"; filename=\"msg.txt\"\r\n"
91     "Content-Type: text/plain\r\n"
92     "Content-Transfer-Encoding: binary\r\n"
93     "\r\n";
94 char post_trailer_templ[] =
95     "\r\n"
96     "--%s--\r\n";
97 char post_header[sizeof(post_header_templ) - 2 + BOUNDARY_LEN];
98 char post_trailer[sizeof(post_trailer_templ) - 2 + BOUNDARY_LEN];
99 
100 int urlcheck_gflags = 0;
101 
102 
103 /*
104  * locking is done through the same lock as acllist: both are static
105  * configuration, which are readen or changed at the same times.
106  */
107 struct urlchecklist urlcheck_head;
108 
109 static size_t curl_outlet(void *, size_t, size_t, void *);
110 static int find_boundary(struct mlfi_priv *, char *);
111 static size_t curl_post(void *, size_t, size_t, void *);
112 static int answer_parse(struct iovec *, struct acl_param *, int,
113 			struct mlfi_priv *);
114 static struct urlcheck_cnx *get_cnx(struct urlcheck_entry *);
115 static void urlcheck_validate_pipe(struct urlcheck_data *,
116 				   struct urlcheck_entry *,
117 				   struct urlcheck_cnx *, char *);
118 static void urlcheck_validate_internal(struct urlcheck_data *,
119 				       struct urlcheck_entry *,
120 				       struct urlcheck_cnx *, char *,
121 				       acl_stage_t, struct mlfi_priv *);
122 static void urlcheck_helper_init(struct urlcheck_entry *,
123 				 struct urlcheck_cnx *);
124 static void urlcheck_validate_helper(struct urlcheck_entry *,
125 				     struct urlcheck_cnx *);
126 static void urlcheck_cleanup_pipe(struct urlcheck_cnx *);
127 void urlcheck_helper_timeout(int);
128 
129 #define URLCHECK_ANSWER_MAX	4096
130 
131 void
urlcheck_init(void)132 urlcheck_init(void) {
133 	LIST_INIT(&urlcheck_head);
134 	curl_global_init(CURL_GLOBAL_ALL);
135 	return;
136 }
137 
138 
139 void
urlcheck_def_add(name,url,max_cnx,flags)140 urlcheck_def_add(name, url, max_cnx, flags) /* acllist must be write locked */
141 	char *name;
142 	char *url;
143 	int max_cnx;
144 	int flags;
145 {
146 	struct urlcheck_entry *ue;
147 	struct urlcheck_cnx *uc;
148 	int postmsg;
149 	int getprop;
150 
151 	postmsg = flags & U_POSTMSG;
152 	getprop = flags & U_GETPROP;
153 
154 	if (urlcheck_byname(name) != NULL) {
155 		mg_log(LOG_ERR, "urlcheck \"%s\" defined twice at line %d",
156 		    name, conf_line - 1);
157 		exit(EX_DATAERR);
158 	}
159 
160 	if ((ue = malloc(sizeof(*ue))) == NULL) {
161 		mg_log(LOG_ERR, "malloc failed: %s", strerror(errno));
162 		exit(EX_OSERR);
163 	}
164 
165 	strncpy(ue->u_name, name, sizeof(ue->u_name));
166 	ue->u_name[sizeof(ue->u_name) - 1] = '\0';
167 
168 	strncpy(ue->u_url, url, sizeof(ue->u_url));
169 	ue->u_url[sizeof(ue->u_url) - 1] = '\0';
170 
171 	ue->u_maxcnx = max_cnx;
172 	ue->u_flags = flags;
173 
174 	if (postmsg && conf.c_maxpeek == 0)
175 		conf.c_maxpeek = -1;
176 
177 	if ((uc = malloc(max_cnx * sizeof(*uc))) == NULL) {
178 		mg_log(LOG_ERR, "malloc(%d) failed for URL check cnx pool: %s",
179 		    max_cnx * sizeof(*uc), strerror(errno));
180 		exit(EX_OSERR);
181 	}
182 	ue->u_cnxpool = uc;
183 
184 	while (max_cnx > 0) {
185 		uc->uc_hdl = NULL;
186 		uc->uc_old = 0;
187 		uc->uc_pipe_req[0] = -1;
188 		uc->uc_pipe_req[1] = -1;
189 		uc->uc_pipe_rep[0] = -1;
190 		uc->uc_pipe_rep[1] = -1;
191 		uc->uc_pid = -1;
192 		if (pthread_mutex_init(&uc->uc_lock, NULL) != 0) {
193 			mg_log(LOG_ERR, "pthread_mutex_init() failed: %s",
194 			    strerror(errno));
195 			exit(EX_OSERR);
196 		}
197 
198 		uc++;
199 		max_cnx--;
200 	}
201 
202 	LIST_INSERT_HEAD(&urlcheck_head, ue, u_list);
203 
204 	if (conf.c_debug || conf.c_acldebug) {
205 		mg_log(LOG_DEBUG, "load URL check \"%s\" \"%s\" %d%s%s%s%s",
206 		    ue->u_name, ue->u_url, ue->u_maxcnx,
207 		    getprop ? " getprop" : "",
208 		    (ue->u_flags & U_CLEARPROP) ? " clear" : "",
209 		    (ue->u_flags & U_FORK) ? " fork" : "",
210 		    postmsg ? " postmsg" : "");
211 	}
212 
213 	urlcheck_gflags = 0;
214 	return;
215 }
216 
217 struct urlcheck_entry *
urlcheck_byname(urlcheck)218 urlcheck_byname(urlcheck)	/* acllist must be read locked */
219 	char *urlcheck;
220 {
221 	struct urlcheck_entry *ue;
222 
223 	LIST_FOREACH(ue, &urlcheck_head, u_list) {
224 		if (strcasecmp(ue->u_name, urlcheck) == 0)
225 			break;
226 	}
227 
228 	return ue;
229 }
230 
231 void
urlcheck_clear(void)232 urlcheck_clear(void)	/* acllist must be write locked */
233 {
234 	struct urlcheck_entry *ue;
235 	struct urlcheck_cnx *uc;
236 
237 	while(!LIST_EMPTY(&urlcheck_head)) {
238 		ue = LIST_FIRST(&urlcheck_head);
239 		LIST_REMOVE(ue, u_list);
240 
241 		uc = ue->u_cnxpool;
242 		while (ue->u_maxcnx > 0) {
243 			/*
244 			 * Drain the lock. No other thread should be
245 			 * able to acquire it now since we removed
246 			 * ue from the list. XXX is that right?
247 			 */
248 
249 			if (pthread_mutex_lock(&uc->uc_lock) != 0) {
250 				mg_log(LOG_ERR, "pthread_mutex_lock failed "
251 				    "in urlcheck_clear: %s", strerror(errno));
252 				exit(EX_OSERR);
253 			}
254 
255 			if (pthread_mutex_unlock(&uc->uc_lock) != 0) {
256 				mg_log(LOG_ERR, "pthread_mutex_unlock failed "
257 				    "in urlcheck_clear: %s", strerror(errno));
258 				exit(EX_OSERR);
259 			}
260 
261 			if (uc->uc_hdl != NULL)
262 				curl_easy_cleanup(uc->uc_hdl);
263 
264 			urlcheck_cleanup_pipe(uc);
265 
266 			pthread_mutex_destroy(&uc->uc_lock);
267 
268 			uc++;
269 			ue->u_maxcnx--;
270 		}
271 		free(ue->u_cnxpool);
272 		free(ue);
273 	}
274 
275 	curl_global_cleanup();
276 
277 	urlcheck_init();
278 
279 	return;
280 }
281 
282 
283 
284 static int
find_boundary(priv,boundary)285 find_boundary(priv, boundary)
286 	struct mlfi_priv *priv;
287 	char *boundary;
288 {
289 	int i;
290 	struct line *l;
291 
292 	for (i = 0; i < BOUNDARY_LEN; i++)
293 		boundary[i] = 'a';
294 	boundary[BOUNDARY_LEN] = '\0';
295 
296 	do {
297 		TAILQ_FOREACH(l, &priv->priv_header, l_list)
298 			if (strstr(l->l_line, boundary) != NULL)
299 				goto next;
300 
301 		TAILQ_FOREACH(l, &priv->priv_body, l_list)
302 			if (strstr(l->l_line, boundary) != NULL)
303 				goto next;
304 
305 		return 0;
306 next:
307 		for (i = 0; i < BOUNDARY_LEN; i++) {
308 			if (boundary[i] == 'z') {
309 				boundary[i] = 'a';
310 			} else {
311 				boundary[i]++;
312 				break;
313 			}
314 		}
315 
316 		/* Failure to find a proper boundary */
317 		if (i == BOUNDARY_LEN)
318 			return -1;
319 
320 	} while (/*CONSTCOND*/ 1);
321 }
322 
323 static size_t
curl_post(buffer,size,nmemb,userp)324 curl_post(buffer, size, nmemb, userp)
325 	void  *buffer;
326 	size_t size;
327 	size_t nmemb;
328 	void *userp;
329 {
330 	struct post_data *pd;
331 	size_t len = 0;
332 
333 	pd = (struct post_data *)userp;
334 
335 	/* First time */
336 	if ((pd->pd_curhdr != NULL) && (pd->pd_curptr == NULL)) {
337 		len = sizeof(post_header) - 1;
338 		if (size * nmemb < len) {
339 			mg_log(LOG_ERR, "libcurl frag too small");
340 			exit(EX_OSERR);
341 		}
342 		snprintf(buffer, len, post_header_templ, pd->pd_boundary);
343 		pd->pd_curptr = pd->pd_curhdr->l_line;
344 		goto finish;
345 	}
346 
347 	/* We are currently doing headers */
348 	if (pd->pd_curhdr != NULL) {
349 		len = strlen(pd->pd_curptr);
350 		if (len <= size * nmemb) {
351 			/*
352 			 * we copy everything we need and move
353 			 * to the next header line.
354 			 */
355 			if (len > 0)
356 				memcpy(buffer, pd->pd_curptr, len);
357 
358 			pd->pd_curhdr = TAILQ_NEXT(pd->pd_curhdr, l_list);
359 
360 			/*
361 			 * If there are no more headers, we will move to
362 			 * the body on next time we are called.
363 			 */
364 			if (pd->pd_curhdr == NULL)
365 				pd->pd_curptr = pd->pd_curbody->l_line;
366 			else
367 				pd->pd_curptr = pd->pd_curhdr->l_line;
368 
369 		} else { /* (len > size * nmemb) */
370 			/*
371 			 * More data to write than buffer size,
372 			 * copy everything we can
373 			 */
374 			len = (size * nmemb);
375 			memcpy(buffer, pd->pd_curptr, len);
376 			pd->pd_curptr += len;
377 		}
378 		goto finish;
379 	}
380 
381 	/* We are currently processing body */
382 	if (pd->pd_curbody != NULL) {
383 		len = strlen(pd->pd_curptr);
384 
385 		if (len <= size * nmemb) {
386 			/* Copy the whole chunk */
387 			memcpy(buffer, pd->pd_curptr, len);
388 
389 			/* Move to the next one */
390 			pd->pd_curbody =
391 			    TAILQ_NEXT(pd->pd_curbody, l_list);
392 
393 			/* If it's not the last one... */
394 			if (pd->pd_curbody != NULL)
395 				pd->pd_curptr = pd->pd_curbody->l_line;
396 			else
397 				pd->pd_curptr = NULL;
398 		} else {
399 			/*
400 			 * Copy everything we have
401 			 * strncpy is used without adding the \0 on purpose
402 			 */
403 			strncpy(buffer, pd->pd_curptr, len);
404 
405 			pd->pd_curptr += len;
406 		}
407 		goto finish;
408 	}
409 
410 	/* Body is done, do trailer */
411 	if ((pd->pd_curbody == NULL) && (!pd->pd_done)) {
412 		len = sizeof(post_trailer) - 1;
413 		if (size * nmemb < len) {
414 			mg_log(LOG_ERR, "libcurl frag too small");
415 			exit(EX_OSERR);
416 		}
417 		snprintf(buffer, len, post_trailer_templ, pd->pd_boundary);
418 
419 		pd->pd_done = 1;
420 
421 		goto finish;
422 	}
423 
424 	/* Job completed */
425 	if (pd->pd_done)
426 		len = 0;
427 finish:
428 	return len;
429 }
430 
431 static size_t
curl_outlet(buffer,size,nmemb,userp)432 curl_outlet(buffer, size, nmemb, userp)
433 	void  *buffer;
434 	size_t size;
435 	size_t nmemb;
436 	void *userp;
437 {
438 	struct urlcheck_data *ud;
439 	void *newbuf;
440 	size_t newlen;
441 
442 	ud = (struct urlcheck_data *)userp;
443 
444 	newlen = ud->ud_iov.iov_len;
445 
446 	/*
447 	 * On first pass, add extra chars for adding
448 	 * a trailing \n\0 at the end of the buffer.
449 	 */
450 	if (newlen == 0)
451 		newlen = ud->ud_iov.iov_len = 2;
452 
453 	newlen += (size * nmemb);
454 
455 	if (newlen > URLCHECK_ANSWER_MAX) {
456 		mg_log(LOG_WARNING, "urlcheck answer too big, abort");
457 		if (ud->ud_iov.iov_base != NULL)
458 			free(ud->ud_iov.iov_base);
459 		ud->ud_iov.iov_base = NULL;
460 		ud->ud_iov.iov_len = 0;
461 		return 0;
462 	}
463 
464 	if ((newbuf = realloc(ud->ud_iov.iov_base, newlen)) == NULL) {
465 		mg_log(LOG_ERR, "realloc() failed");
466 		exit(EX_OSERR);
467 	}
468 	ud->ud_iov.iov_base = newbuf;
469 
470 	memcpy(ud->ud_iov.iov_base + ud->ud_iov.iov_len - 2,
471 	       buffer, size * nmemb);
472 	ud->ud_iov.iov_len = newlen;
473 
474 	return (size * nmemb);
475 }
476 
477 /* Return a locked connection */
478 static struct urlcheck_cnx *
get_cnx(ue)479 get_cnx(ue)
480 	struct urlcheck_entry *ue;
481 {
482 	struct urlcheck_cnx *uc = ue->u_cnxpool;
483 	int i;
484 	int error;
485 	time_t oldest_date;
486 	int oldest_cnx;
487 	struct urlcheck_cnx *cnx = NULL;
488 
489 	oldest_date = uc[0].uc_old;
490 	oldest_cnx = 0;
491 
492 	/* First, try to find a free one */
493 	for (i = 0; i < ue->u_maxcnx; i++) {
494 		error = pthread_mutex_trylock(&uc[i].uc_lock);
495 		if (error == EBUSY) {
496 			if (uc[i].uc_old < oldest_date) {
497 				oldest_date = uc[i].uc_old;
498 				oldest_cnx = i;
499 			}
500 			continue;
501 		}
502 		if (error != 0) {
503 			mg_log(LOG_ERR, "pthread_mutex_trylock failed in "
504 			    "get_cnx: %s", strerror(errno));
505 			exit(EX_OSERR);
506 		}
507 
508 		/* We got a lock */
509 		cnx = &uc[i];
510 		break;
511 	}
512 
513 	/*
514 	 * Nothing was free, we have to wait for a connection.
515 	 * Use the one that was locked for the longest time
516 	 */
517 	if (cnx == NULL) {
518 		mg_log(LOG_WARNING, "pool too small for URL check \"%s\"",
519 		    ue->u_name);
520 		cnx = &uc[oldest_cnx];
521 		if (pthread_mutex_lock(&cnx->uc_lock) != 0) {
522 			mg_log(LOG_ERR, "pthread_mutex_lock failed in "
523 			    "get_cnx: %s", strerror(errno));
524 			exit(EX_OSERR);
525 		}
526 	}
527 
528 	/*
529 	 * We now have a lock on a connection
530 	 * Record the time
531 	 */
532 	cnx->uc_old = time(NULL);
533 
534 	return cnx;
535 }
536 
537 char *
urlencode(str)538 urlencode(str)
539 	char *str;
540 {
541 	char *outstr;
542 	size_t origlen, len;
543 	char *cp;
544 	char *dp;
545 
546 	origlen = strlen(str) + 1;
547 	len = origlen;
548 	for (cp = str; *cp; cp++) {
549 		if (isalnum((int)*cp))
550 			continue;
551 
552 		switch(*cp) {
553 		case '-':
554 		case '_':
555 		case '.':
556 		case '~':
557 			break;
558 		default:
559 			len += 2;
560 			break;
561 		}
562 	}
563 
564 	if (len == origlen)
565 		return NULL;
566 
567 	if ((outstr = malloc(len)) == NULL) {
568 		mg_log(LOG_ERR, "malloc failed");
569 		exit (EX_OSERR);
570 	}
571 
572 	dp = outstr;
573 	for (cp = str; *cp; cp++) {
574 		if (isalnum((int)*cp)) {
575 			*dp++ = *cp;
576 			continue;
577 		}
578 
579 		switch(*cp) {
580 		case '-':
581 		case '_':
582 		case '.':
583 		case '~':
584 			*dp++ = *cp;
585 			break;
586 		default:
587 			(void)sprintf(dp, "%%%02X", *cp);
588 			dp += 3;
589 			break;
590 		}
591 	}
592 
593 	*dp = '\0';
594 
595 	return outstr;
596 }
597 
598 int
urlcheck_validate(ad,stage,ap,priv)599 urlcheck_validate(ad, stage, ap, priv)
600 	acl_data_t *ad;
601 	acl_stage_t stage;
602 	struct acl_param *ap;
603 	struct mlfi_priv *priv;
604 {
605 	char *rcpt;
606 	struct urlcheck_entry *ue;
607 	char *url;
608 	int retval = 0;
609 	struct urlcheck_data ud;
610 	struct urlcheck_cnx *cnx;
611 	struct timeval tv1, tv2, tv3;
612 	char *(*cv)(char *);
613 
614 	rcpt = priv->priv_cur_rcpt;
615 	ue = ad->urlcheck;
616 
617 	cv = (ue->u_flags & U_NOENCODE) ? NULL : *urlencode;
618 	url = fstring_expand(priv, rcpt, ue->u_url, cv);
619 
620 	if (conf.c_debug) {
621 		mg_log(LOG_DEBUG, "checking \"%s\"\n", url);
622 		gettimeofday(&tv1, NULL);
623 	}
624 
625 	cnx = get_cnx(ue);
626 
627 	ud.ud_iov.iov_base = NULL;
628 	ud.ud_iov.iov_len = 0;
629 	ud.ud_error = -1;
630 
631 	if (ue->u_flags & U_FORK)
632 		urlcheck_validate_pipe(&ud, ue, cnx, url);
633 	else
634 		urlcheck_validate_internal(&ud, ue, cnx, url, stage, priv);
635 
636 	if (ud.ud_iov.iov_base == NULL) {
637 		mg_log(LOG_WARNING, "urlcheck failed: no answer");
638 		goto out;
639 	}
640 
641 out:
642 	free(url);
643 
644 	if (pthread_mutex_unlock(&cnx->uc_lock) != 0) {
645 		mg_log(LOG_ERR, "pthread_mutex_unlock failed: %s",
646 		    strerror(errno));
647 		exit(EX_OSERR);
648 	}
649 
650 	if (ud.ud_iov.iov_base) {
651 		retval = answer_parse(&ud.ud_iov, ap, ue->u_flags,
652 		    (ue->u_flags & U_GETPROP) ? priv : NULL);
653 		free(ud.ud_iov.iov_base);
654 	}
655 
656 	if (ud.ud_error != 0)
657 		retval = ud.ud_error;
658 
659         if (conf.c_debug) {
660                 gettimeofday(&tv2, NULL);
661                 timersub(&tv2, &tv1, &tv3);
662                 mg_log(LOG_DEBUG, "urlcheck lookup performed in %ld.%06lds",
663                     tv3.tv_sec, tv3.tv_usec);
664         }
665 
666 	return retval;
667 }
668 
669 static void
urlcheck_validate_pipe(ud,ue,cnx,url)670 urlcheck_validate_pipe(ud, ue, cnx, url)
671 	struct urlcheck_data *ud;
672 	struct urlcheck_entry *ue;
673 	struct urlcheck_cnx *cnx;
674 	char *url;
675 {
676 	ssize_t size;
677 
678 	if (cnx->uc_pid == -1) {
679 		/* Fork a new helper */
680 
681 		if (pipe(cnx->uc_pipe_req) == -1) {
682 			mg_log(LOG_ERR, "pipe() failed: %s", strerror(errno));
683 			exit(EX_OSERR);
684 		}
685 		if (pipe(cnx->uc_pipe_rep) == -1) {
686 			mg_log(LOG_ERR, "pipe() failed: %s", strerror(errno));
687 			exit(EX_OSERR);
688 		}
689 
690 		switch(cnx->uc_pid = fork()) {
691 		case -1:
692 			mg_log(LOG_ERR, "fork() failed: %s", strerror(errno));
693 			exit(EX_OSERR);
694 			break;
695 		case 0:
696 			urlcheck_helper_init(ue, cnx);
697 			/* NOTREACHED */
698 			break;
699 		default:
700 			break;
701 		}
702 	}
703 
704 	size = strlen(url) + 1;
705 
706 	if (conf.c_debug)
707 		mg_log(LOG_DEBUG, "%s: %d bytes to write", __func__, size);
708 
709 	if ((write(cnx->uc_pipe_req[1], &size, sizeof(size)) != sizeof(size))) {
710 		urlcheck_cleanup_pipe(cnx);
711 		goto out;
712 	}
713 
714 	if (conf.c_debug)
715 		mg_log(LOG_DEBUG, "%s: write \"%s\"", __func__, url);
716 
717 	if (write(cnx->uc_pipe_req[1], url, size) != size) {
718 		urlcheck_cleanup_pipe(cnx);
719 		goto out;
720 	}
721 
722 	if (conf.c_debug)
723 		mg_log(LOG_DEBUG, "%s: awaiting reply", __func__);
724 
725 	if (read(cnx->uc_pipe_rep[0],
726 	    &ud->ud_error, sizeof(ud->ud_error)) != sizeof(size)) {
727 		urlcheck_cleanup_pipe(cnx);
728 		goto out;
729 	}
730 
731 	if (read(cnx->uc_pipe_rep[0], &size, sizeof(size)) != sizeof(size)) {
732 		urlcheck_cleanup_pipe(cnx);
733 		goto out;
734 	}
735 
736 	if (conf.c_debug)
737 		mg_log(LOG_DEBUG, "%s: %d bytes to read", __func__, size);
738 
739 	ud->ud_iov.iov_len = size;
740 	if ((ud->ud_iov.iov_base = malloc(size)) == NULL) {
741 		mg_log(LOG_ERR, "malloc() failed: %s", strerror(errno));
742 		exit(EX_OSERR);
743 	}
744 
745 	if (read(cnx->uc_pipe_rep[0], ud->ud_iov.iov_base, size) != size) {
746 		urlcheck_cleanup_pipe(cnx);
747 		goto out;
748 	}
749 out:
750 	return;
751 }
752 
753 static void
urlcheck_helper_init(ue,cnx)754 urlcheck_helper_init(ue, cnx)
755 	struct urlcheck_entry *ue;
756 	struct urlcheck_cnx *cnx;
757 {
758 	/*
759 	 * Close the MX sync socket so that we don't hold them
760 	 * if milter-greylist main process exits, thus preventing
761 	 * it from restarting
762 	 */
763 	sync_master_stop();
764 
765 	if (signal(SIGALRM,
766 	    *urlcheck_helper_timeout) == SIG_ERR)
767 		mg_log(LOG_ERR,
768 		    "signal(SIGALRM) failed: %s",
769 		    strerror(errno));
770 
771 	if (conf.c_debug)
772 		mg_log(LOG_DEBUG,
773 		    "started urlcheck helper (pid %d)",
774 		    getpid());
775 	while (1)
776 		urlcheck_validate_helper(ue, cnx);
777 
778 	/* NOTREACHED */
779 }
780 
781 void
urlcheck_helper_timeout(sig)782 urlcheck_helper_timeout(sig)
783 	int sig;
784 {
785 	if (conf.c_debug)
786 		mg_log(LOG_DEBUG, "urlcheck_helper_timeout");
787 
788 	if (getppid() == 1) {
789 		mg_log(LOG_INFO,
790 		    "parent died, urlcheck helper exit (pid %d)",
791 		    getpid());
792 		exit(EX_OK);
793 	}
794 
795 	(void)alarm(URLCHECK_HELPER_TIMEOUT);
796 
797 	return;
798 }
799 
800 static void
urlcheck_validate_helper(ue,cnx)801 urlcheck_validate_helper(ue, cnx)
802 	struct urlcheck_entry *ue;
803 	struct urlcheck_cnx *cnx;
804 {
805 	ssize_t size;
806 	char *url;
807 	struct urlcheck_data ud;
808 
809 	if (conf.c_debug)
810 		mg_log(LOG_DEBUG, "%s", __func__);
811 
812 	(void)alarm(URLCHECK_HELPER_TIMEOUT);
813 
814 	if (read(cnx->uc_pipe_req[0], &size, sizeof(size)) != sizeof(size)) {
815 		mg_log(LOG_ERR, "urlcheck helper I/O error (%d) "
816 				"size = %ld", __LINE__, size);
817 		exit(EX_OSERR);
818 	}
819 
820 	if (conf.c_debug)
821 		mg_log(LOG_DEBUG, "%s: %d bytes to read", __func__, size);
822 
823 	if ((url = malloc(size)) == NULL) {
824 		mg_log(LOG_ERR, "malloc() failed: %s", strerror(errno));
825 		exit(EX_OSERR);
826 	}
827 
828 	if (read(cnx->uc_pipe_req[0], url, size) != size) {
829 		mg_log(LOG_ERR, "urlcheck helper I/O error (%d)", __LINE__);
830 		exit(EX_OSERR);
831 	}
832 
833 	if (conf.c_debug)
834 		mg_log(LOG_DEBUG, "%s: url = \"%s\"", __func__, url);
835 
836 	ud.ud_iov.iov_base = NULL;
837 	ud.ud_iov.iov_len = 0;
838 	ud.ud_error = -1;
839 
840 	/*
841 	 * stage and priv are used for the postmsg option. For now
842 	 * it is not compatible with the fork option, so we just lie
843 	 * about it being AS_RCPT/NULL so that they are not used.
844 	 */
845 	urlcheck_validate_internal(&ud, ue, cnx, url, AS_RCPT, NULL);
846 
847 	size = ud.ud_iov.iov_len;
848 	if (write(cnx->uc_pipe_rep[1],
849 	    &ud.ud_error, sizeof(ud.ud_error)) != sizeof(size)) {
850 		mg_log(LOG_ERR, "urlcheck helper I/O error (%d)", __LINE__);
851 		exit(EX_OSERR);
852 	}
853 
854 	if (write(cnx->uc_pipe_rep[1], &size, sizeof(size)) != sizeof(size)) {
855 		mg_log(LOG_ERR, "urlcheck helper I/O error (%d)", __LINE__);
856 		exit(EX_OSERR);
857 	}
858 
859 	if (write(cnx->uc_pipe_rep[1], ud.ud_iov.iov_base, size) != size) {
860 		mg_log(LOG_ERR, "urlcheck helper I/O error (%d)", __LINE__);
861 		exit(EX_OSERR);
862 	}
863 
864 	if (ud.ud_iov.iov_base != NULL)
865 		free(ud.ud_iov.iov_base);
866 	free(url);
867 	return;
868 }
869 
870 static void
urlcheck_cleanup_pipe(cnx)871 urlcheck_cleanup_pipe(cnx)
872 	struct urlcheck_cnx *cnx;
873 {
874 	if (cnx->uc_pid == -1)
875 		return;
876 
877 	mg_log(LOG_ERR, "urlcheck I/O failed: %s", strerror(errno));
878 
879 	close(cnx->uc_pipe_req[0]);
880 	close(cnx->uc_pipe_req[1]);
881 	close(cnx->uc_pipe_rep[0]);
882 	close(cnx->uc_pipe_rep[1]);
883 
884 	mg_log(LOG_ERR, "killing helper at pid = %d", cnx->uc_pid);
885 
886 	kill(cnx->uc_pid, SIGKILL);
887 	cnx->uc_pid = -1;
888 
889 	return;
890 }
891 
892 
893 static void
urlcheck_validate_internal(ud,ue,cnx,url,stage,priv)894 urlcheck_validate_internal(ud, ue, cnx, url, stage, priv)
895 	struct urlcheck_data *ud;
896 	struct urlcheck_entry *ue;
897 	struct urlcheck_cnx *cnx;
898 	char *url;
899 	acl_stage_t stage;
900 	struct mlfi_priv *priv;
901 {
902 	CURL *ch;
903 	CURLcode cerr;
904 	struct curl_slist *headers = NULL;
905 	char *data_trailer;
906 
907 	if (cnx->uc_hdl == NULL) {
908 		if ((cnx->uc_hdl = curl_easy_init()) == NULL) {
909 			mg_log(LOG_ERR, "curl_easy_init() failed");
910 			exit(EX_SOFTWARE);
911 		}
912 	}
913 
914 	ch = cnx->uc_hdl;
915 
916 	if ((cerr = curl_easy_setopt(ch, CURLOPT_URL, url)) != CURLE_OK) {
917 		mg_log(LOG_WARNING, "curl_easy_setopt(CURLOPT_URL) failed; %s",
918 		    curl_easy_strerror(cerr));
919 		goto out;
920 	}
921 
922 	if ((cerr = curl_easy_setopt(ch,
923 	    CURLOPT_WRITEFUNCTION, curl_outlet)) != CURLE_OK) {
924 		mg_log(LOG_WARNING, "curl_easy_setopt(CURLOPT_WRITEFUNCTION) "
925 		    "failed; %s", curl_easy_strerror(cerr));
926 		goto out;
927 	}
928 
929 	if ((cerr = curl_easy_setopt(ch,
930 	    CURLOPT_WRITEDATA, (void *)ud)) != CURLE_OK) {
931 		mg_log(LOG_WARNING, "curl_easy_setopt(CURLOPT_WRITEDATA) "
932 		    "failed; %s", curl_easy_strerror(cerr));
933 		goto out;
934 	}
935 
936 	if ((stage == AS_DATA) &&
937 	    (ue->u_flags & U_POSTMSG) &&
938 	    !TAILQ_EMPTY(&priv->priv_header)) {
939 		struct post_data pd;
940 		size_t len;
941 		char head_templ[] =
942 		    "Content-Type: multipart/form-data; boundary=%s";
943 		char head[sizeof(head_templ) - 2 + BOUNDARY_LEN];
944 
945 		if (find_boundary(priv, pd.pd_boundary) == -1) {
946 			mg_log(LOG_WARNING,
947 			    "Unable to create a MIME boundary, "
948 			    "not posting message");
949 			goto out;
950 		}
951 
952 		sprintf(head, head_templ, pd.pd_boundary);
953 
954 		if (headers == NULL)
955 			headers = curl_slist_append(headers, head);
956 
957 		if ((cerr = curl_easy_setopt(ch,
958 		    CURLOPT_HTTPHEADER, headers)) != CURLE_OK) {
959 			mg_log(LOG_WARNING,
960 			    "curl_easy_setopt(CURLOPT_HTTPHEADERS) "
961 			    "failed; %s", curl_easy_strerror(cerr));
962 			goto out;
963 		}
964 
965 		if ((cerr = curl_easy_setopt(ch,
966 		    CURLOPT_POST, 1)) != CURLE_OK) {
967 			mg_log(LOG_WARNING,
968 			    "curl_easy_setopt(CURLOPT_POST) "
969 			    "failed; %s", curl_easy_strerror(cerr));
970 			goto out;
971 		}
972 
973 		len = sizeof(post_header) - 1
974 		    + priv->priv_msgcount
975 		    + sizeof(post_trailer) - 1;
976 		if ((cerr = curl_easy_setopt(ch,
977 		    CURLOPT_POSTFIELDSIZE, len)) != CURLE_OK) {
978 			mg_log(LOG_WARNING,
979 			    "curl_easy_setopt(CURLOPT_POSTFIELDSIZE) "
980 			    "failed; %s", curl_easy_strerror(cerr));
981 			goto out;
982 		}
983 
984 		if ((cerr = curl_easy_setopt(ch,
985 		    CURLOPT_READFUNCTION, curl_post)) != CURLE_OK) {
986 			mg_log(LOG_WARNING,
987 			    "curl_easy_setopt(CURLOPT_READFUNCTION) "
988 			    "failed; %s", curl_easy_strerror(cerr));
989 			goto out;
990 		}
991 
992 		pd.pd_priv = priv;
993 		pd.pd_curhdr = TAILQ_FIRST(&priv->priv_header);
994 		pd.pd_curbody = TAILQ_FIRST(&priv->priv_body);
995 		pd.pd_curptr = NULL;
996 		pd.pd_done = 0;
997 
998 		if ((cerr = curl_easy_setopt(ch,
999 		    CURLOPT_READDATA, (void *)&pd)) != CURLE_OK) {
1000 			mg_log(LOG_WARNING,
1001 			    "curl_easy_setopt(CURLOPT_READDATA) "
1002 			    "failed; %s", curl_easy_strerror(cerr));
1003 			goto out;
1004 		}
1005 	}
1006 
1007 	if ((cerr = curl_easy_perform(ch)) != CURLE_OK) {
1008 		mg_log(LOG_WARNING, "curl_easy_perform() failed; %s",
1009 		    curl_easy_strerror(cerr));
1010 		goto out;
1011 	}
1012 
1013 	if (ud->ud_iov.iov_base == NULL) {
1014 		ud->ud_error = 0;
1015 		mg_log(LOG_WARNING, "urlcheck failed: no answer");
1016 		goto out;
1017 	}
1018 
1019 	/*
1020 	 * Set a trailing \n\0
1021 	 */
1022 	data_trailer = (char *)ud->ud_iov.iov_base + ud->ud_iov.iov_len - 2;
1023 	data_trailer[0] = '\n';
1024 	data_trailer[1] = '\0';
1025 
1026 	ud->ud_error = 0;
1027 out:
1028 	if (headers != NULL)
1029 		curl_slist_free_all(headers);
1030 
1031 	if (cnx->uc_hdl)
1032 		curl_easy_cleanup(cnx->uc_hdl);
1033 	cnx->uc_hdl = NULL;
1034 
1035 	return;
1036 }
1037 
1038 static int
answer_parse(data,ap,flags,priv)1039 answer_parse(data, ap, flags, priv)
1040 	struct iovec *data;
1041 	struct acl_param *ap;
1042 	int flags;
1043 	struct mlfi_priv *priv;
1044 {
1045 	int idx;
1046 	char *buf;
1047 	size_t len;
1048 	char *linep;
1049 	char *valp;
1050 	int retval = 0;
1051 
1052 	buf = data->iov_base;
1053 	len = data->iov_len;
1054 	idx = 0;
1055 
1056 	if (len == 0) {
1057 		mg_log(LOG_DEBUG, "ignoring blank reply");
1058 		return 0;
1059 	}
1060 	buf[len - 1] = '\0';	/* Prevent run-away printf */
1061 	len--;			/* Stop before trailing 0 */
1062 
1063 	while (idx < len) {
1064 		/* strip spaces */
1065 		while ((idx < len) && isspace((int)buf[idx]))
1066 			idx++;
1067 		linep = buf + idx;
1068 
1069 		if (idx == len)
1070 			break;
1071 
1072 		if (buf[idx] == '\n') {
1073 			idx++;
1074 			continue;
1075 		}
1076 
1077 		/* find the : */
1078 		while ((idx < len) &&
1079 		       (buf[idx] != '\n') &&
1080 		       (buf[idx] != ':'))
1081 			idx++;
1082 
1083 		if (idx == len)
1084 			break;
1085 
1086 		if (buf[idx] == '\n') {
1087 			buf[idx] = '\0';
1088 			if (idx > 0 && buf[idx - 1] == '\r')
1089 			    buf[idx - 1] = '\0';
1090 			mg_log(LOG_DEBUG,
1091 			    "ignoring unexpected line with no value \"%s\"",
1092 			    linep);
1093 			idx++;
1094 			continue;
1095 		}
1096 
1097 		/* Cut linep */
1098 		buf[idx] = '\0';
1099 		idx++;
1100 
1101 		/* Strip spaces in valp */
1102 		while ((idx < len) && isspace((int)buf[idx]))
1103 			idx++;
1104 		valp = buf + idx;
1105 
1106 		if (idx == len)
1107 			break;
1108 
1109 		if (buf[idx] == '\n') {
1110 			buf[idx] = '\0';
1111 			if (idx > 0 && buf[idx - 1] == '\r')
1112 			    buf[idx - 1] = '\0';
1113 			mg_log(LOG_DEBUG, "ignoring line \"%s: %s\"",
1114 			    linep, valp);
1115 			idx++;
1116 			continue;
1117 		}
1118 
1119 		/* Look for end of line */
1120 		while ((idx < len) &&
1121 		       (buf[idx] != '\n'))
1122 			idx++;
1123 
1124 		if (idx == len) {
1125 			buf[idx] = '\0';
1126 			if (idx > 0 && buf[idx - 1] == '\r')
1127 			    buf[idx - 1] = '\0';
1128 			mg_log(LOG_DEBUG,
1129 			    "ignoring unexpected line \"%s: %s\"",
1130 			    linep, valp);
1131 			break;
1132 		}
1133 
1134 		/* cut valp */
1135 		buf[idx] = '\0';
1136 		if (idx > 0 && buf[idx - 1] == '\r')
1137 		    buf[idx - 1] = '\0';
1138 		idx++;
1139 
1140 		if (acl_modify_by_prop(linep, valp, ap) == -1) {
1141 			if (!(flags & U_GETPROP))
1142 				mg_log(LOG_DEBUG,
1143 				    "ignoring unexpected \"%s\" => \"%s\"",
1144 				    linep, valp);
1145 		} else {
1146 			/* We have a match! */
1147 			retval = 1;
1148 		}
1149 
1150 		if (flags & U_GETPROP)
1151 			prop_push(linep, valp, flags & U_CLEARPROP, priv);
1152 	}
1153 
1154 	/*
1155 	 * If we did not match, toss the gathered properties
1156 	 * otherwise clear the tmp flag
1157 	 */
1158 	if (flags & U_GETPROP) {
1159 		if (retval == 0)
1160 			prop_clear(priv, UP_TMPPROP);
1161 		else
1162 			prop_untmp(priv);
1163 	}
1164 
1165 	return retval;
1166 }
1167 
1168 #endif /* USE_CURL */
1169