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