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