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  * All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions are met:
10  * 1. Redistributions of source code must retain the above copyright notice,
11  *    this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright notice,
13  *    this list of conditions and the following disclaimer in the documentation
14  *    and/or other materials provided with the distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS ``AS IS''
17  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19  * ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
20  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26  * POSSIBILITY OF SUCH DAMAGE.
27  */
28 
29 #include "proxy.h"
30 
31 #include "privsep.h"
32 #include "pxythrmgr.h"
33 #include "pxyconn.h"
34 #include "prototcp.h"
35 #include "protossl.h"
36 #include "protohttp.h"
37 #include "protopop3.h"
38 #include "protosmtp.h"
39 #include "protoautossl.h"
40 #include "cachemgr.h"
41 #include "opts.h"
42 #include "log.h"
43 #include "attrib.h"
44 
45 #include <sys/types.h>
46 #include <sys/socket.h>
47 #include <netinet/in.h>
48 #include <signal.h>
49 #include <stdlib.h>
50 #include <stdio.h>
51 #include <string.h>
52 #include <errno.h>
53 
54 #include <event2/event.h>
55 #include <event2/listener.h>
56 #include <event2/bufferevent.h>
57 #include <event2/bufferevent_ssl.h>
58 #include <event2/buffer.h>
59 #include <event2/thread.h>
60 
61 /*
62  * Proxy engine, built around libevent 2.x.
63  */
64 
65 static int signals[] = { SIGTERM, SIGQUIT, SIGHUP, SIGINT, SIGPIPE, SIGUSR1 };
66 
67 struct proxy_ctx {
68 	pxy_thrmgr_ctx_t *thrmgr;
69 	struct event_base *evbase;
70 	struct event *sev[sizeof(signals)/sizeof(int)];
71 	struct event *gcev;
72 	struct proxy_listener_ctx *lctx;
73 	global_t *global;
74 	int loopbreak_reason;
75 };
76 
77 static proxy_listener_ctx_t * MALLOC
proxy_listener_ctx_new(pxy_thrmgr_ctx_t * thrmgr,proxyspec_t * spec,global_t * global)78 proxy_listener_ctx_new(pxy_thrmgr_ctx_t *thrmgr, proxyspec_t *spec,
79                        global_t *global)
80 {
81 	proxy_listener_ctx_t *ctx = malloc(sizeof(proxy_listener_ctx_t));
82 	if (!ctx)
83 		return NULL;
84 	memset(ctx, 0, sizeof(proxy_listener_ctx_t));
85 	ctx->thrmgr = thrmgr;
86 	ctx->spec = spec;
87 	ctx->global = global;
88 	return ctx;
89 }
90 
91 static void NONNULL(1)
proxy_listener_ctx_free(proxy_listener_ctx_t * ctx)92 proxy_listener_ctx_free(proxy_listener_ctx_t *ctx)
93 {
94 	if (ctx->evcl) {
95 		evconnlistener_free(ctx->evcl);
96 	}
97 	if (ctx->next) {
98 		proxy_listener_ctx_free(ctx->next);
99 	}
100 	free(ctx);
101 }
102 
103 static protocol_t NONNULL(1)
proxy_setup_proto(pxy_conn_ctx_t * ctx)104 proxy_setup_proto(pxy_conn_ctx_t *ctx)
105 {
106 	ctx->protoctx = malloc(sizeof(proto_ctx_t));
107 	if (!ctx->protoctx) {
108 		return PROTO_ERROR;
109 	}
110 	memset(ctx->protoctx, 0, sizeof(proto_ctx_t));
111 
112 	// Default to tcp
113 	prototcp_setup(ctx);
114 
115 	protocol_t proto;
116 	if (ctx->spec->upgrade) {
117 		proto = protoautossl_setup(ctx);
118 	} else if (ctx->spec->http) {
119 		if (ctx->spec->ssl) {
120 			proto = protohttps_setup(ctx);
121 		} else {
122 			proto = protohttp_setup(ctx);
123 		}
124 	} else if (ctx->spec->pop3) {
125 		if (ctx->spec->ssl) {
126 			proto = protopop3s_setup(ctx);
127 		} else {
128 			proto = protopop3_setup(ctx);
129 		}
130 	} else if (ctx->spec->smtp) {
131 		if (ctx->spec->ssl) {
132 			proto = protosmtps_setup(ctx);
133 		} else {
134 			proto = protosmtp_setup(ctx);
135 		}
136 	} else if (ctx->spec->ssl) {
137 		proto = protossl_setup(ctx);
138 	} else {
139 		proto = PROTO_TCP;
140 	}
141 
142 	if (proto == PROTO_ERROR) {
143 		free(ctx->protoctx);
144 	}
145 	return proto;
146 }
147 
148 pxy_conn_ctx_t *
proxy_conn_ctx_new(evutil_socket_t fd,pxy_thrmgr_ctx_t * thrmgr,proxyspec_t * spec,global_t * global,evutil_socket_t clisock)149 proxy_conn_ctx_new(evutil_socket_t fd,
150                  pxy_thrmgr_ctx_t *thrmgr,
151                  proxyspec_t *spec, global_t *global
152 #ifndef WITHOUT_USERAUTH
153                  , evutil_socket_t clisock
154 #endif /* !WITHOUT_USERAUTH */
155                  )
156 {
157 	log_finest_main_va("ENTER, fd=%d", fd);
158 
159 	pxy_conn_ctx_t *ctx = malloc(sizeof(pxy_conn_ctx_t));
160 	if (!ctx) {
161 		return NULL;
162 	}
163 	memset(ctx, 0, sizeof(pxy_conn_ctx_t));
164 
165 	ctx->type = CONN_TYPE_PARENT;
166 #ifdef DEBUG_PROXY
167 	ctx->id = thrmgr->conn_count++;
168 #endif /* DEBUG_PROXY */
169 	ctx->conn = ctx;
170 	ctx->fd = fd;
171 	ctx->thrmgr = thrmgr;
172 	ctx->spec = spec;
173 	ctx->conn_opts = spec->conn_opts;
174 	ctx->divert = spec->opts->divert;
175 
176 	// Enable all logging for conn if proxyspec does not have any filter
177 	if (!spec->opts->filter) {
178 		ctx->log_connect = 1;
179 		ctx->log_master = 1;
180 		ctx->log_cert = 1;
181 		ctx->log_content = 1;
182 		ctx->log_pcap = 1;
183 #ifndef WITHOUT_MIRROR
184 		ctx->log_mirror = 1;
185 #endif /* !WITHOUT_MIRROR */
186 	}
187 
188 	ctx->proto = proxy_setup_proto(ctx);
189 	if (ctx->proto == PROTO_ERROR) {
190 		free(ctx);
191 		return NULL;
192 	}
193 
194 	ctx->global = global;
195 #ifndef WITHOUT_USERAUTH
196 	ctx->clisock = clisock;
197 #endif /* !WITHOUT_USERAUTH */
198 
199 #ifdef HAVE_LOCAL_PROCINFO
200 	ctx->lproc.pid = -1;
201 #endif /* HAVE_LOCAL_PROCINFO */
202 
203 	log_finest("Created new conn");
204 	return ctx;
205 }
206 
207 /*
208  * Does minimal clean-up, called on error by proxy_listener_acceptcb() only.
209  * We call this function instead of pxy_conn_ctx_free(), because
210  * proxy_listener_acceptcb() runs on thrmgr, whereas pxy_conn_ctx_free()
211  * runs on conn handling thr. This is necessary to prevent multithreading issues.
212  */
213 static void NONNULL(1)
proxy_conn_ctx_free(pxy_conn_ctx_t * ctx)214 proxy_conn_ctx_free(pxy_conn_ctx_t *ctx)
215 {
216 	log_finest("ENTER");
217 
218 	if (ctx->ev) {
219 		event_free(ctx->ev);
220 	}
221 	// If the proto doesn't have special args, proto_free() callback is NULL
222 	if (ctx->protoctx->proto_free) {
223 		ctx->protoctx->proto_free(ctx);
224 	}
225 	free(ctx->protoctx);
226 	free(ctx);
227 }
228 
229 /*
230  * Callback for accept events on the socket listener bufferevent.
231  * Called when a new incoming connection has been accepted.
232  * Initiates the connection to the server.  The incoming connection
233  * from the client is not being activated until we have a successful
234  * connection to the server, because we need the server's certificate
235  * in order to set up the SSL session to the client.
236  * For consistency, plain TCP works the same way, even if we could
237  * start reading from the client while waiting on the connection to
238  * the server to connect.
239  */
240 static void
proxy_listener_acceptcb(UNUSED struct evconnlistener * listener,evutil_socket_t fd,struct sockaddr * peeraddr,int peeraddrlen,void * arg)241 proxy_listener_acceptcb(UNUSED struct evconnlistener *listener,
242                         evutil_socket_t fd,
243                         struct sockaddr *peeraddr, int peeraddrlen,
244                         void *arg)
245 {
246 	proxy_listener_ctx_t *lctx = arg;
247 
248 	log_finest_main_va("ENTER, fd=%d", fd);
249 
250 	/* create per connection state */
251 	pxy_conn_ctx_t *ctx = proxy_conn_ctx_new(fd, lctx->thrmgr, lctx->spec, lctx->global
252 #ifndef WITHOUT_USERAUTH
253 			, lctx->clisock
254 #endif /* !WITHOUT_USERAUTH */
255 			);
256 	if (!ctx) {
257 		log_err_level_printf(LOG_CRIT, "Error allocating ctx memory\n");
258 		evutil_closesocket(fd);
259 		return;
260 	}
261 
262 	// Choose the conn handling thr
263 	pxy_thrmgr_assign_thr(ctx);
264 
265 	/* prepare logging part 1 and user auth */
266 	ctx->srcaddrlen = peeraddrlen;
267 	memcpy(&ctx->srcaddr, peeraddr, ctx->srcaddrlen);
268 
269 	// Switch from thrmgr to connection handling thread, i.e. change the event base, asap
270 	// This prevents possible multithreading issues between thrmgr and conn handling threads
271 	ctx->ev = event_new(ctx->thr->evbase, -1, 0, ctx->protoctx->init_conn, ctx);
272 	if (!ctx->ev) {
273 		log_err_level(LOG_CRIT, "Error creating initial event, aborting connection");
274 		goto out;
275 	}
276 	// The only purpose of this event is to change the event base, so it is a one-shot event
277 	if (event_add(ctx->ev, NULL) == -1)
278 		goto out;
279 	event_active(ctx->ev, 0, 0);
280 	return;
281 out:
282 	evutil_closesocket(fd);
283 	proxy_conn_ctx_free(ctx);
284 }
285 
286 /*
287  * Callback for error events on the socket listener bufferevent.
288  */
289 void
proxy_listener_errorcb(struct evconnlistener * listener,UNUSED void * arg)290 proxy_listener_errorcb(struct evconnlistener *listener, UNUSED void *arg)
291 {
292 	struct event_base *evbase = evconnlistener_get_base(listener);
293 	int err = EVUTIL_SOCKET_ERROR();
294 	log_err_level_printf(LOG_CRIT, "Error %d on listener: %s\n", err,
295 	               evutil_socket_error_to_string(err));
296 	/* Do not break the event loop if out of fds:
297 	 * Too many open files (24) */
298 	if (err == 24) {
299 		return;
300 	}
301 	event_base_loopbreak(evbase);
302 }
303 
304 /*
305  * Dump a description of an evbase to debugging code.
306  */
307 static void
proxy_debug_base(const struct event_base * ev_base)308 proxy_debug_base(const struct event_base *ev_base)
309 {
310 	log_dbg_printf("Using libevent backend '%s'\n",
311 	               event_base_get_method(ev_base));
312 
313 	enum event_method_feature f;
314 	f = event_base_get_features(ev_base);
315 	log_dbg_printf("Event base supports: edge %s, O(1) %s, anyfd %s\n",
316 	               ((f & EV_FEATURE_ET) ? "yes" : "no"),
317 	               ((f & EV_FEATURE_O1) ? "yes" : "no"),
318 	               ((f & EV_FEATURE_FDS) ? "yes" : "no"));
319 }
320 
321 /*
322  * Set up the listener for a single proxyspec and add it to evbase.
323  * Returns the proxy_listener_ctx_t pointer if successful, NULL otherwise.
324  */
325 static proxy_listener_ctx_t *
proxy_listener_setup(struct event_base * evbase,pxy_thrmgr_ctx_t * thrmgr,proxyspec_t * spec,global_t * global,evutil_socket_t clisock)326 proxy_listener_setup(struct event_base *evbase, pxy_thrmgr_ctx_t *thrmgr,
327                      proxyspec_t *spec, global_t *global, evutil_socket_t clisock)
328 {
329 	log_finest_main("ENTER");
330 
331 	int fd;
332 	if ((fd = privsep_client_opensock(clisock, spec)) == -1) {
333 		log_err_level_printf(LOG_CRIT, "Error opening socket: %s (%i)\n",
334 		               strerror(errno), errno);
335 		return NULL;
336 	}
337 
338 	proxy_listener_ctx_t *lctx = proxy_listener_ctx_new(thrmgr, spec, global);
339 	if (!lctx) {
340 		log_err_level_printf(LOG_CRIT, "Error creating listener context\n");
341 		evutil_closesocket(fd);
342 		return NULL;
343 	}
344 
345 #ifndef WITHOUT_USERAUTH
346 	lctx->clisock = clisock;
347 #endif /* !WITHOUT_USERAUTH */
348 
349 	// @attention Do not pass NULL as user-supplied pointer
350 	lctx->evcl = evconnlistener_new(evbase, proxy_listener_acceptcb,
351 	                               lctx, LEV_OPT_CLOSE_ON_FREE, 1024, fd);
352 	if (!lctx->evcl) {
353 		log_err_level_printf(LOG_CRIT, "Error creating evconnlistener: %s\n",
354 		               strerror(errno));
355 		proxy_listener_ctx_free(lctx);
356 		evutil_closesocket(fd);
357 		return NULL;
358 	}
359 	evconnlistener_set_error_cb(lctx->evcl, proxy_listener_errorcb);
360 	return lctx;
361 }
362 
363 /*
364  * Signal handler for SIGTERM, SIGQUIT, SIGINT, SIGHUP, SIGPIPE and SIGUSR1.
365  */
366 static void
proxy_signal_cb(evutil_socket_t fd,UNUSED short what,void * arg)367 proxy_signal_cb(evutil_socket_t fd, UNUSED short what, void *arg)
368 {
369 	proxy_ctx_t *ctx = arg;
370 
371 	if (OPTS_DEBUG(ctx->global)) {
372 		log_dbg_printf("Received signal %i\n", fd);
373 	}
374 
375 	switch(fd) {
376 	case SIGTERM:
377 	case SIGQUIT:
378 	case SIGINT:
379 		proxy_loopbreak(ctx, fd);
380 		break;
381 	case SIGHUP:
382 	case SIGUSR1:
383 		if (log_reopen() == -1) {
384 			log_err_level_printf(LOG_WARNING, "Failed to reopen logs\n");
385 		} else {
386 			log_dbg_printf("Reopened log files\n");
387 		}
388 		break;
389 	case SIGPIPE:
390 		log_err_level_printf(LOG_WARNING, "Received SIGPIPE; ignoring.\n");
391 		break;
392 	default:
393 		log_err_level_printf(LOG_WARNING, "Received unexpected signal %i\n", fd);
394 		break;
395 	}
396 }
397 
398 /*
399  * Garbage collection handler.
400  */
401 static void
proxy_gc_cb(UNUSED evutil_socket_t fd,UNUSED short what,void * arg)402 proxy_gc_cb(UNUSED evutil_socket_t fd, UNUSED short what, void *arg)
403 {
404 	proxy_ctx_t *ctx = arg;
405 
406 	if (OPTS_DEBUG(ctx->global))
407 		log_dbg_printf("Garbage collecting caches started.\n");
408 
409 	cachemgr_gc();
410 
411 	if (OPTS_DEBUG(ctx->global))
412 		log_dbg_printf("Garbage collecting caches done.\n");
413 }
414 
415 /*
416  * Set up the core event loop.
417  * Socket clisock is the privsep client socket used for binding to ports.
418  * Returns ctx on success, or NULL on error.
419  */
420 proxy_ctx_t *
proxy_new(global_t * global,int clisock)421 proxy_new(global_t *global, int clisock)
422 {
423 	proxy_listener_ctx_t *head;
424 	proxy_ctx_t *ctx;
425 	struct evdns_base *dnsbase;
426 	int rc;
427 
428 	/* adds locking, only required if accessed from separate threads */
429 	evthread_use_pthreads();
430 
431 #ifndef PURIFY
432 	if (OPTS_DEBUG(global)) {
433 		event_enable_debug_mode();
434 	}
435 #endif /* PURIFY */
436 
437 	ctx = malloc(sizeof(proxy_ctx_t));
438 	if (!ctx) {
439 		log_err_level_printf(LOG_CRIT, "Error allocating memory\n");
440 		goto leave0;
441 	}
442 	memset(ctx, 0, sizeof(proxy_ctx_t));
443 
444 	ctx->global = global;
445 	ctx->evbase = event_base_new();
446 	if (!ctx->evbase) {
447 		log_err_level_printf(LOG_CRIT, "Error getting event base\n");
448 		goto leave1;
449 	}
450 
451 	if (global_has_dns_spec(global)) {
452 		/* create a dnsbase here purely for being able to test parsing
453 		 * resolv.conf while we can still alert the user about it. */
454 		dnsbase = evdns_base_new(ctx->evbase, 0);
455 		if (!dnsbase) {
456 			log_err_level_printf(LOG_CRIT, "Error creating dns event base\n");
457 			goto leave1b;
458 		}
459 		rc = evdns_base_resolv_conf_parse(dnsbase, DNS_OPTIONS_ALL,
460 		                                  "/etc/resolv.conf");
461 		evdns_base_free(dnsbase, 0);
462 		if (rc != 0) {
463 			log_err_level_printf(LOG_CRIT, "evdns cannot parse resolv.conf: "
464 			               "%s (%d)\n",
465 			               rc == 1 ? "failed to open file" :
466 			               rc == 2 ? "failed to stat file" :
467 			               rc == 3 ? "file too large" :
468 			               rc == 4 ? "out of memory" :
469 			               rc == 5 ? "short read from file" :
470 			               rc == 6 ? "no nameservers in file" :
471 			               "unknown error", rc);
472 			goto leave1b;
473 		}
474 	}
475 
476 	if (OPTS_DEBUG(global)) {
477 		proxy_debug_base(ctx->evbase);
478 	}
479 
480 	ctx->thrmgr = pxy_thrmgr_new(global);
481 	if (!ctx->thrmgr) {
482 		log_err_level_printf(LOG_CRIT, "Error creating thread manager\n");
483 		goto leave1b;
484 	}
485 
486 	head = ctx->lctx = NULL;
487 	for (proxyspec_t *spec = global->spec; spec; spec = spec->next) {
488 		head = proxy_listener_setup(ctx->evbase, ctx->thrmgr,
489 		                            spec, global, clisock);
490 		if (!head)
491 			goto leave2;
492 		head->next = ctx->lctx;
493 		ctx->lctx = head;
494 	}
495 
496 	for (size_t i = 0; i < (sizeof(signals) / sizeof(int)); i++) {
497 		ctx->sev[i] = evsignal_new(ctx->evbase, signals[i],
498 		                           proxy_signal_cb, ctx);
499 		if (!ctx->sev[i])
500 			goto leave3;
501 		evsignal_add(ctx->sev[i], NULL);
502 	}
503 
504 	struct timeval gc_delay = {60, 0};
505 	ctx->gcev = event_new(ctx->evbase, -1, EV_PERSIST, proxy_gc_cb, ctx);
506 	if (!ctx->gcev)
507 		goto leave4;
508 	evtimer_add(ctx->gcev, &gc_delay);
509 
510 	// @attention Do not close privsep sock if the USERAUTH feature is compiled in, we use it to update user atime
511 #ifdef WITHOUT_USERAUTH
512 	privsep_client_close(clisock);
513 #endif /* !WITHOUT_USERAUTH */
514 	return ctx;
515 
516 leave4:
517 	if (ctx->gcev) {
518 		event_free(ctx->gcev);
519 	}
520 
521 leave3:
522 	for (size_t i = 0; i < (sizeof(ctx->sev) / sizeof(ctx->sev[0])); i++) {
523 		if (ctx->sev[i]) {
524 			event_free(ctx->sev[i]);
525 		}
526 	}
527 leave2:
528 	if (ctx->lctx) {
529 		proxy_listener_ctx_free(ctx->lctx);
530 	}
531 	pxy_thrmgr_free(ctx->thrmgr);
532 leave1b:
533 	event_base_free(ctx->evbase);
534 leave1:
535 	free(ctx);
536 leave0:
537 	return NULL;
538 }
539 
540 /*
541  * Run the event loop.
542  * Returns 0 on non-signal termination, signal number when the event loop was
543  * canceled by a signal, or -1 on failure.
544  */
545 int
proxy_run(proxy_ctx_t * ctx)546 proxy_run(proxy_ctx_t *ctx)
547 {
548 	if (ctx->global->detach) {
549 		event_reinit(ctx->evbase);
550 	}
551 #ifndef PURIFY
552 	if (OPTS_DEBUG(ctx->global)) {
553 		event_base_dump_events(ctx->evbase, stderr);
554 	}
555 #endif /* PURIFY */
556 	if (pxy_thrmgr_run(ctx->thrmgr) == -1) {
557 		log_err_level_printf(LOG_CRIT, "Failed to start thread manager\n");
558 		return -1;
559 	}
560 	if (OPTS_DEBUG(ctx->global)) {
561 		log_dbg_printf("Starting main event loop.\n");
562 	}
563 	event_base_dispatch(ctx->evbase);
564 	if (OPTS_DEBUG(ctx->global)) {
565 		log_dbg_printf("Main event loop stopped (reason=%i).\n",
566 		               ctx->loopbreak_reason);
567 	}
568 	return ctx->loopbreak_reason;
569 }
570 
571 /*
572  * Break the loop of the proxy, causing the proxy_run to return, returning
573  * the reason given in reason (signal number, 0 for success, -1 for error).
574  */
575 void
proxy_loopbreak(proxy_ctx_t * ctx,int reason)576 proxy_loopbreak(proxy_ctx_t *ctx, int reason)
577 {
578 	ctx->loopbreak_reason = reason;
579 	event_base_loopbreak(ctx->evbase);
580 }
581 
582 /*
583  * Free the proxy data structures.
584  */
585 void
proxy_free(proxy_ctx_t * ctx)586 proxy_free(proxy_ctx_t *ctx)
587 {
588 	if (ctx->gcev) {
589 		event_free(ctx->gcev);
590 	}
591 	if (ctx->lctx) {
592 		proxy_listener_ctx_free(ctx->lctx);
593 	}
594 	for (size_t i = 0; i < (sizeof(ctx->sev) / sizeof(ctx->sev[0])); i++) {
595 		if (ctx->sev[i]) {
596 			event_free(ctx->sev[i]);
597 		}
598 	}
599 	if (ctx->thrmgr) {
600 		pxy_thrmgr_free(ctx->thrmgr);
601 	}
602 	if (ctx->evbase) {
603 		event_base_free(ctx->evbase);
604 	}
605 	free(ctx);
606 }
607 
608 /* vim: set noet ft=c: */
609