xref: /illumos-gate/usr/src/lib/libresolv2/common/isc/ctl_clnt.c (revision 458f44a49dc56cd17a39815122214e7a1b4793e3)
1 /*
2  * Copyright (C) 2004, 2005, 2007, 2008  Internet Systems Consortium, Inc. ("ISC")
3  * Copyright (C) 1998-2003  Internet Software Consortium.
4  *
5  * Permission to use, copy, modify, and/or distribute this software for any
6  * purpose with or without fee is hereby granted, provided that the above
7  * copyright notice and this permission notice appear in all copies.
8  *
9  * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
10  * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
11  * AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
12  * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
13  * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
14  * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
15  * PERFORMANCE OF THIS SOFTWARE.
16  */
17 
18 #include "port_before.h"
19 
20 #include <sys/param.h>
21 #include <sys/file.h>
22 #include <sys/socket.h>
23 
24 #include <netinet/in.h>
25 #include <arpa/nameser.h>
26 #include <arpa/inet.h>
27 
28 #include <ctype.h>
29 #include <errno.h>
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <string.h>
33 #include <time.h>
34 #include <unistd.h>
35 #ifdef HAVE_MEMORY_H
36 #include <memory.h>
37 #endif
38 
39 #include <isc/assertions.h>
40 #include <isc/ctl.h>
41 #include <isc/eventlib.h>
42 #include <isc/list.h>
43 #include <isc/memcluster.h>
44 
45 #include "ctl_p.h"
46 
47 #include "port_after.h"
48 
49 /* Constants. */
50 
51 
52 /* Macros. */
53 
54 #define donefunc_p(ctx) ((ctx).donefunc != NULL)
55 #define arpacode_p(line) (isdigit((unsigned char)(line[0])) && \
56 			  isdigit((unsigned char)(line[1])) && \
57 			  isdigit((unsigned char)(line[2])))
58 #define arpacont_p(line) (line[3] == '-')
59 #define arpadone_p(line) (line[3] == ' ' || line[3] == '\t' || \
60 			  line[3] == '\r' || line[3] == '\0')
61 
62 /* Types. */
63 
64 enum state {
65 	initializing = 0, connecting, connected, destroyed
66 };
67 
68 struct ctl_tran {
69 	LINK(struct ctl_tran)	link;
70 	LINK(struct ctl_tran)	wlink;
71 	struct ctl_cctx *	ctx;
72 	struct ctl_buf		outbuf;
73 	ctl_clntdone		donefunc;
74 	void *			uap;
75 };
76 
77 struct ctl_cctx {
78 	enum state		state;
79 	evContext		ev;
80 	int			sock;
81 	ctl_logfunc		logger;
82 	ctl_clntdone		donefunc;
83 	void *			uap;
84 	evConnID		coID;
85 	evTimerID		tiID;
86 	evFileID		rdID;
87 	evStreamID		wrID;
88 	struct ctl_buf		inbuf;
89 	struct timespec		timeout;
90 	LIST(struct ctl_tran)	tran;
91 	LIST(struct ctl_tran)	wtran;
92 };
93 
94 /* Forward. */
95 
96 static struct ctl_tran *new_tran(struct ctl_cctx *, ctl_clntdone, void *, int);
97 static void		start_write(struct ctl_cctx *);
98 static void		destroy(struct ctl_cctx *, int);
99 static void		error(struct ctl_cctx *);
100 static void		new_state(struct ctl_cctx *, enum state);
101 static void		conn_done(evContext, void *, int,
102 				  const void *, int,
103 				  const void *, int);
104 static void		write_done(evContext, void *, int, int);
105 static void		start_read(struct ctl_cctx *);
106 static void		stop_read(struct ctl_cctx *);
107 static void		readable(evContext, void *, int, int);
108 static void		start_timer(struct ctl_cctx *);
109 static void		stop_timer(struct ctl_cctx *);
110 static void		touch_timer(struct ctl_cctx *);
111 static void		timer(evContext, void *,
112 			      struct timespec, struct timespec);
113 
114 #ifndef HAVE_MEMCHR
115 static void *
memchr(const void * b,int c,size_t len)116 memchr(const void *b, int c, size_t len) {
117 	const unsigned char *p = b;
118 	size_t i;
119 
120 	for (i = 0; i < len; i++, p++)
121 		if (*p == (unsigned char)c)
122 			return ((void *)p);
123 	return (NULL);
124 }
125 #endif
126 
127 /* Private data. */
128 
129 static const char * const state_names[] = {
130 	"initializing", "connecting", "connected", "destroyed"
131 };
132 
133 /* Public. */
134 
135 /*%
136  * void
137  * ctl_client()
138  *	create, condition, and connect to a listener on the control port.
139  */
140 struct ctl_cctx *
ctl_client(evContext lev,const struct sockaddr * cap,size_t cap_len,const struct sockaddr * sap,size_t sap_len,ctl_clntdone donefunc,void * uap,u_int timeout,ctl_logfunc logger)141 ctl_client(evContext lev, const struct sockaddr *cap, size_t cap_len,
142 	   const struct sockaddr *sap, size_t sap_len,
143 	   ctl_clntdone donefunc, void *uap,
144 	   u_int timeout, ctl_logfunc logger)
145 {
146 	static const char me[] = "ctl_client";
147 	static const int on = 1;
148 	struct ctl_cctx *ctx;
149 	struct sockaddr *captmp;
150 
151 	if (logger == NULL)
152 		logger = ctl_logger;
153 	ctx = memget(sizeof *ctx);
154 	if (ctx == NULL) {
155 		(*logger)(ctl_error, "%s: getmem: %s", me, strerror(errno));
156 		goto fatal;
157 	}
158 	ctx->state = initializing;
159 	ctx->ev = lev;
160 	ctx->logger = logger;
161 	ctx->timeout = evConsTime(timeout, 0);
162 	ctx->donefunc = donefunc;
163 	ctx->uap = uap;
164 	ctx->coID.opaque = NULL;
165 	ctx->tiID.opaque = NULL;
166 	ctx->rdID.opaque = NULL;
167 	ctx->wrID.opaque = NULL;
168 	buffer_init(ctx->inbuf);
169 	INIT_LIST(ctx->tran);
170 	INIT_LIST(ctx->wtran);
171 	ctx->sock = socket(sap->sa_family, SOCK_STREAM, PF_UNSPEC);
172 	if (ctx->sock > evHighestFD(ctx->ev)) {
173 		ctx->sock = -1;
174 		errno = ENOTSOCK;
175 	}
176 	if (ctx->sock < 0) {
177 		(*ctx->logger)(ctl_error, "%s: socket: %s",
178 			       me, strerror(errno));
179 		goto fatal;
180 	}
181 	if (cap != NULL) {
182 		if (setsockopt(ctx->sock, SOL_SOCKET, SO_REUSEADDR,
183 			       (const char *)&on, sizeof on) != 0) {
184 			(*ctx->logger)(ctl_warning,
185 				       "%s: setsockopt(REUSEADDR): %s",
186 				       me, strerror(errno));
187 		}
188 		DE_CONST(cap, captmp);
189 		if (bind(ctx->sock, captmp, cap_len) < 0) {
190 			(*ctx->logger)(ctl_error, "%s: bind: %s", me,
191 				       strerror(errno));
192 			goto fatal;
193 		}
194 	}
195 	if (evConnect(lev, ctx->sock, (const struct sockaddr *)sap, sap_len,
196 		      conn_done, ctx, &ctx->coID) < 0) {
197 		(*ctx->logger)(ctl_error, "%s: evConnect(fd %d): %s",
198 			       me, ctx->sock, strerror(errno));
199  fatal:
200 		if (ctx != NULL) {
201 			if (ctx->sock >= 0)
202 				close(ctx->sock);
203 			memput(ctx, sizeof *ctx);
204 		}
205 		return (NULL);
206 	}
207 	new_state(ctx, connecting);
208 	return (ctx);
209 }
210 
211 /*%
212  * void
213  * ctl_endclient(ctx)
214  *	close a client and release all of its resources.
215  */
216 void
ctl_endclient(struct ctl_cctx * ctx)217 ctl_endclient(struct ctl_cctx *ctx) {
218 	if (ctx->state != destroyed)
219 		destroy(ctx, 0);
220 	memput(ctx, sizeof *ctx);
221 }
222 
223 /*%
224  * int
225  * ctl_command(ctx, cmd, len, donefunc, uap)
226  *	Queue a transaction, which will begin with sending cmd
227  *	and complete by calling donefunc with the answer.
228  */
229 int
ctl_command(struct ctl_cctx * ctx,const char * cmd,size_t len,ctl_clntdone donefunc,void * uap)230 ctl_command(struct ctl_cctx *ctx, const char *cmd, size_t len,
231 	    ctl_clntdone donefunc, void *uap)
232 {
233 	struct ctl_tran *tran;
234 	char *pc;
235 	unsigned int n;
236 
237 	switch (ctx->state) {
238 	case destroyed:
239 		errno = ENOTCONN;
240 		return (-1);
241 	case connecting:
242 	case connected:
243 		break;
244 	default:
245 		abort();
246 	}
247 	if (len >= (size_t)MAX_LINELEN) {
248 		errno = EMSGSIZE;
249 		return (-1);
250 	}
251 	tran = new_tran(ctx, donefunc, uap, 1);
252 	if (tran == NULL)
253 		return (-1);
254 	if (ctl_bufget(&tran->outbuf, ctx->logger) < 0)
255 		return (-1);
256 	memcpy(tran->outbuf.text, cmd, len);
257 	tran->outbuf.used = len;
258 	for (pc = tran->outbuf.text, n = 0; n < tran->outbuf.used; pc++, n++)
259 		if (!isascii((unsigned char)*pc) ||
260 		    !isprint((unsigned char)*pc))
261 			*pc = '\040';
262 	start_write(ctx);
263 	return (0);
264 }
265 
266 /* Private. */
267 
268 static struct ctl_tran *
new_tran(struct ctl_cctx * ctx,ctl_clntdone donefunc,void * uap,int w)269 new_tran(struct ctl_cctx *ctx, ctl_clntdone donefunc, void *uap, int w) {
270 	struct ctl_tran *new = memget(sizeof *new);
271 
272 	if (new == NULL)
273 		return (NULL);
274 	new->ctx = ctx;
275 	buffer_init(new->outbuf);
276 	new->donefunc = donefunc;
277 	new->uap = uap;
278 	INIT_LINK(new, link);
279 	INIT_LINK(new, wlink);
280 	APPEND(ctx->tran, new, link);
281 	if (w)
282 		APPEND(ctx->wtran, new, wlink);
283 	return (new);
284 }
285 
286 static void
start_write(struct ctl_cctx * ctx)287 start_write(struct ctl_cctx *ctx) {
288 	static const char me[] = "isc/ctl_clnt::start_write";
289 	struct ctl_tran *tran;
290 	struct iovec iov[2], *iovp = iov;
291 	char * tmp;
292 
293 	REQUIRE(ctx->state == connecting || ctx->state == connected);
294 	/* If there is a write in progress, don't try to write more yet. */
295 	if (ctx->wrID.opaque != NULL)
296 		return;
297 	/* If there are no trans, make sure timer is off, and we're done. */
298 	if (EMPTY(ctx->wtran)) {
299 		if (ctx->tiID.opaque != NULL)
300 			stop_timer(ctx);
301 		return;
302 	}
303 	/* Pull it off the head of the write queue. */
304 	tran = HEAD(ctx->wtran);
305 	UNLINK(ctx->wtran, tran, wlink);
306 	/* Since there are some trans, make sure timer is successfully "on". */
307 	if (ctx->tiID.opaque != NULL)
308 		touch_timer(ctx);
309 	else
310 		start_timer(ctx);
311 	if (ctx->state == destroyed)
312 		return;
313 	/* Marshall a newline-terminated message and clock it out. */
314 	*iovp++ = evConsIovec(tran->outbuf.text, tran->outbuf.used);
315 	DE_CONST("\r\n", tmp);
316 	*iovp++ = evConsIovec(tmp, 2);
317 	if (evWrite(ctx->ev, ctx->sock, iov, iovp - iov,
318 		    write_done, tran, &ctx->wrID) < 0) {
319 		(*ctx->logger)(ctl_error, "%s: evWrite: %s", me,
320 			       strerror(errno));
321 		error(ctx);
322 		return;
323 	}
324 	if (evTimeRW(ctx->ev, ctx->wrID, ctx->tiID) < 0) {
325 		(*ctx->logger)(ctl_error, "%s: evTimeRW: %s", me,
326 			       strerror(errno));
327 		error(ctx);
328 		return;
329 	}
330 }
331 
332 static void
destroy(struct ctl_cctx * ctx,int notify)333 destroy(struct ctl_cctx *ctx, int notify) {
334 	struct ctl_tran *this, *next;
335 
336 	if (ctx->sock != -1) {
337 		(void) close(ctx->sock);
338 		ctx->sock = -1;
339 	}
340 	switch (ctx->state) {
341 	case connecting:
342 		REQUIRE(ctx->wrID.opaque == NULL);
343 		REQUIRE(EMPTY(ctx->tran));
344 		/*
345 		 * This test is nec'y since destroy() can be called from
346 		 * start_read() while the state is still "connecting".
347 		 */
348 		if (ctx->coID.opaque != NULL) {
349 			(void)evCancelConn(ctx->ev, ctx->coID);
350 			ctx->coID.opaque = NULL;
351 		}
352 		break;
353 	case connected:
354 		REQUIRE(ctx->coID.opaque == NULL);
355 		if (ctx->wrID.opaque != NULL) {
356 			(void)evCancelRW(ctx->ev, ctx->wrID);
357 			ctx->wrID.opaque = NULL;
358 		}
359 		if (ctx->rdID.opaque != NULL)
360 			stop_read(ctx);
361 		break;
362 	case destroyed:
363 		break;
364 	default:
365 		abort();
366 	}
367 	if (allocated_p(ctx->inbuf))
368 		ctl_bufput(&ctx->inbuf);
369 	for (this = HEAD(ctx->tran); this != NULL; this = next) {
370 		next = NEXT(this, link);
371 		if (allocated_p(this->outbuf))
372 			ctl_bufput(&this->outbuf);
373 		if (notify && this->donefunc != NULL)
374 			(*this->donefunc)(ctx, this->uap, NULL, 0);
375 		memput(this, sizeof *this);
376 	}
377 	if (ctx->tiID.opaque != NULL)
378 		stop_timer(ctx);
379 	new_state(ctx, destroyed);
380 }
381 
382 static void
error(struct ctl_cctx * ctx)383 error(struct ctl_cctx *ctx) {
384 	REQUIRE(ctx->state != destroyed);
385 	destroy(ctx, 1);
386 }
387 
388 static void
new_state(struct ctl_cctx * ctx,enum state new_state)389 new_state(struct ctl_cctx *ctx, enum state new_state) {
390 	static const char me[] = "isc/ctl_clnt::new_state";
391 
392 	(*ctx->logger)(ctl_debug, "%s: %s -> %s", me,
393 		       state_names[ctx->state], state_names[new_state]);
394 	ctx->state = new_state;
395 }
396 
397 static void
conn_done(evContext ev,void * uap,int fd,const void * la,int lalen,const void * ra,int ralen)398 conn_done(evContext ev, void *uap, int fd,
399 	  const void *la, int lalen,
400 	  const void *ra, int ralen)
401 {
402 	static const char me[] = "isc/ctl_clnt::conn_done";
403 	struct ctl_cctx *ctx = uap;
404 	struct ctl_tran *tran;
405 
406 	UNUSED(ev);
407 	UNUSED(la);
408 	UNUSED(lalen);
409 	UNUSED(ra);
410 	UNUSED(ralen);
411 
412 	ctx->coID.opaque = NULL;
413 	if (fd < 0) {
414 		(*ctx->logger)(ctl_error, "%s: evConnect: %s", me,
415 			       strerror(errno));
416 		error(ctx);
417 		return;
418 	}
419 	new_state(ctx, connected);
420 	tran = new_tran(ctx, ctx->donefunc, ctx->uap, 0);
421 	if (tran == NULL) {
422 		(*ctx->logger)(ctl_error, "%s: new_tran failed: %s", me,
423 			       strerror(errno));
424 		error(ctx);
425 		return;
426 	}
427 	start_read(ctx);
428 	if (ctx->state == destroyed) {
429 		(*ctx->logger)(ctl_error, "%s: start_read failed: %s",
430 			       me, strerror(errno));
431 		error(ctx);
432 		return;
433 	}
434 }
435 
436 static void
write_done(evContext lev,void * uap,int fd,int bytes)437 write_done(evContext lev, void *uap, int fd, int bytes) {
438 	struct ctl_tran *tran = (struct ctl_tran *)uap;
439 	struct ctl_cctx *ctx = tran->ctx;
440 
441 	UNUSED(lev);
442 	UNUSED(fd);
443 
444 	ctx->wrID.opaque = NULL;
445 	if (ctx->tiID.opaque != NULL)
446 		touch_timer(ctx);
447 	ctl_bufput(&tran->outbuf);
448 	start_write(ctx);
449 	if (bytes < 0)
450 		destroy(ctx, 1);
451 	else
452 		start_read(ctx);
453 }
454 
455 static void
start_read(struct ctl_cctx * ctx)456 start_read(struct ctl_cctx *ctx) {
457 	static const char me[] = "isc/ctl_clnt::start_read";
458 
459 	REQUIRE(ctx->state == connecting || ctx->state == connected);
460 	REQUIRE(ctx->rdID.opaque == NULL);
461 	if (evSelectFD(ctx->ev, ctx->sock, EV_READ, readable, ctx,
462 		       &ctx->rdID) < 0)
463 	{
464 		(*ctx->logger)(ctl_error, "%s: evSelect(fd %d): %s", me,
465 			       ctx->sock, strerror(errno));
466 		error(ctx);
467 		return;
468 	}
469 }
470 
471 static void
stop_read(struct ctl_cctx * ctx)472 stop_read(struct ctl_cctx *ctx) {
473 	REQUIRE(ctx->coID.opaque == NULL);
474 	REQUIRE(ctx->rdID.opaque != NULL);
475 	(void)evDeselectFD(ctx->ev, ctx->rdID);
476 	ctx->rdID.opaque = NULL;
477 }
478 
479 static void
readable(evContext ev,void * uap,int fd,int evmask)480 readable(evContext ev, void *uap, int fd, int evmask) {
481 	static const char me[] = "isc/ctl_clnt::readable";
482 	struct ctl_cctx *ctx = uap;
483 	struct ctl_tran *tran;
484 	ssize_t n;
485 	char *eos;
486 
487 	UNUSED(ev);
488 
489 	REQUIRE(ctx != NULL);
490 	REQUIRE(fd >= 0);
491 	REQUIRE(evmask == EV_READ);
492 	REQUIRE(ctx->state == connected);
493 	REQUIRE(!EMPTY(ctx->tran));
494 	tran = HEAD(ctx->tran);
495 	if (!allocated_p(ctx->inbuf) &&
496 	    ctl_bufget(&ctx->inbuf, ctx->logger) < 0) {
497 		(*ctx->logger)(ctl_error, "%s: can't get an input buffer", me);
498 		error(ctx);
499 		return;
500 	}
501 	n = read(ctx->sock, ctx->inbuf.text + ctx->inbuf.used,
502 		 MAX_LINELEN - ctx->inbuf.used);
503 	if (n <= 0) {
504 		(*ctx->logger)(ctl_warning, "%s: read: %s", me,
505 			       (n == 0) ? "Unexpected EOF" : strerror(errno));
506 		error(ctx);
507 		return;
508 	}
509 	if (ctx->tiID.opaque != NULL)
510 		touch_timer(ctx);
511 	ctx->inbuf.used += n;
512 	(*ctx->logger)(ctl_debug, "%s: read %d, used %d", me,
513 		       n, ctx->inbuf.used);
514  again:
515 	eos = memchr(ctx->inbuf.text, '\n', ctx->inbuf.used);
516 	if (eos != NULL && eos != ctx->inbuf.text && eos[-1] == '\r') {
517 		int done = 0;
518 
519 		eos[-1] = '\0';
520 		if (!arpacode_p(ctx->inbuf.text)) {
521 			/* XXX Doesn't FTP do this sometimes? Is it legal? */
522 			(*ctx->logger)(ctl_error, "%s: no arpa code (%s)", me,
523 				       ctx->inbuf.text);
524 			error(ctx);
525 			return;
526 		}
527 		if (arpadone_p(ctx->inbuf.text))
528 			done = 1;
529 		else if (arpacont_p(ctx->inbuf.text))
530 			done = 0;
531 		else {
532 			/* XXX Doesn't FTP do this sometimes? Is it legal? */
533 			(*ctx->logger)(ctl_error, "%s: no arpa flag (%s)", me,
534 				       ctx->inbuf.text);
535 			error(ctx);
536 			return;
537 		}
538 		(*tran->donefunc)(ctx, tran->uap, ctx->inbuf.text,
539 				  (done ? 0 : CTL_MORE));
540 		ctx->inbuf.used -= ((eos - ctx->inbuf.text) + 1);
541 		if (ctx->inbuf.used == 0U)
542 			ctl_bufput(&ctx->inbuf);
543 		else
544 			memmove(ctx->inbuf.text, eos + 1, ctx->inbuf.used);
545 		if (done) {
546 			UNLINK(ctx->tran, tran, link);
547 			memput(tran, sizeof *tran);
548 			stop_read(ctx);
549 			start_write(ctx);
550 			return;
551 		}
552 		if (allocated_p(ctx->inbuf))
553 			goto again;
554 		return;
555 	}
556 	if (ctx->inbuf.used == (size_t)MAX_LINELEN) {
557 		(*ctx->logger)(ctl_error, "%s: line too long (%-10s...)", me,
558 			       ctx->inbuf.text);
559 		error(ctx);
560 	}
561 }
562 
563 /* Timer related stuff. */
564 
565 static void
start_timer(struct ctl_cctx * ctx)566 start_timer(struct ctl_cctx *ctx) {
567 	static const char me[] = "isc/ctl_clnt::start_timer";
568 
569 	REQUIRE(ctx->tiID.opaque == NULL);
570 	if (evSetIdleTimer(ctx->ev, timer, ctx, ctx->timeout, &ctx->tiID) < 0){
571 		(*ctx->logger)(ctl_error, "%s: evSetIdleTimer: %s", me,
572 			       strerror(errno));
573 		error(ctx);
574 		return;
575 	}
576 }
577 
578 static void
stop_timer(struct ctl_cctx * ctx)579 stop_timer(struct ctl_cctx *ctx) {
580 	static const char me[] = "isc/ctl_clnt::stop_timer";
581 
582 	REQUIRE(ctx->tiID.opaque != NULL);
583 	if (evClearIdleTimer(ctx->ev, ctx->tiID) < 0) {
584 		(*ctx->logger)(ctl_error, "%s: evClearIdleTimer: %s", me,
585 			       strerror(errno));
586 		error(ctx);
587 		return;
588 	}
589 	ctx->tiID.opaque = NULL;
590 }
591 
592 static void
touch_timer(struct ctl_cctx * ctx)593 touch_timer(struct ctl_cctx *ctx) {
594 	REQUIRE(ctx->tiID.opaque != NULL);
595 
596 	evTouchIdleTimer(ctx->ev, ctx->tiID);
597 }
598 
599 static void
timer(evContext ev,void * uap,struct timespec due,struct timespec itv)600 timer(evContext ev, void *uap, struct timespec due, struct timespec itv) {
601 	static const char me[] = "isc/ctl_clnt::timer";
602 	struct ctl_cctx *ctx = uap;
603 
604 	UNUSED(ev);
605 	UNUSED(due);
606 	UNUSED(itv);
607 
608 	ctx->tiID.opaque = NULL;
609 	(*ctx->logger)(ctl_error, "%s: timeout after %u seconds while %s", me,
610 		       ctx->timeout.tv_sec, state_names[ctx->state]);
611 	error(ctx);
612 }
613 
614 /*! \file */
615