xref: /illumos-gate/usr/src/lib/libresolv2/common/isc/ctl_srvr.c (revision 2a1fd0ffe121888d44fdec321c25b53dcfaa9118)
1 /*
2  * Copyright (C) 2004-2006, 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 #include <sys/un.h>
24 
25 #include <netinet/in.h>
26 #include <arpa/nameser.h>
27 #include <arpa/inet.h>
28 
29 #include <ctype.h>
30 #include <errno.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <time.h>
35 #include <unistd.h>
36 #include <fcntl.h>
37 #ifdef HAVE_MEMORY_H
38 #include <memory.h>
39 #endif
40 
41 #include <isc/assertions.h>
42 #include <isc/ctl.h>
43 #include <isc/eventlib.h>
44 #include <isc/list.h>
45 #include <isc/logging.h>
46 #include <isc/memcluster.h>
47 
48 #include "ctl_p.h"
49 
50 #include "port_after.h"
51 
52 #ifdef SPRINTF_CHAR
53 # define SPRINTF(x) strlen(sprintf/**/x)
54 #else
55 # define SPRINTF(x) ((size_t)sprintf x)
56 #endif
57 
58 /* Macros. */
59 
60 #define	lastverb_p(verb)	(verb->name == NULL || verb->func == NULL)
61 #define	address_expr		ctl_sa_ntop((struct sockaddr *)&sess->sa, \
62 					    tmp, sizeof tmp, ctx->logger)
63 
64 /* Types. */
65 
66 enum state {
67 	available = 0, initializing, writing, reading, reading_data,
68 	processing, idling, quitting, closing
69 };
70 
71 union sa_un {
72 	struct sockaddr_in in;
73 #ifndef NO_SOCKADDR_UN
74 	struct sockaddr_un un;
75 #endif
76 };
77 
78 struct ctl_sess {
79 	LINK(struct ctl_sess)	link;
80 	struct ctl_sctx *	ctx;
81 	enum state		state;
82 	int			sock;
83 	union sa_un		sa;
84 	evFileID		rdID;
85 	evStreamID		wrID;
86 	evTimerID		rdtiID;
87 	evTimerID		wrtiID;
88 	struct ctl_buf		inbuf;
89 	struct ctl_buf		outbuf;
90 	const struct ctl_verb *	verb;
91 	u_int			helpcode;
92 	const void *		respctx;
93 	u_int			respflags;
94 	ctl_srvrdone		donefunc;
95 	void *			uap;
96 	void *			csctx;
97 };
98 
99 struct ctl_sctx {
100 	evContext		ev;
101 	void *			uctx;
102 	u_int			unkncode;
103 	u_int			timeoutcode;
104 	const struct ctl_verb *	verbs;
105 	const struct ctl_verb *	connverb;
106 	int			sock;
107 	int			max_sess;
108 	int			cur_sess;
109 	struct timespec		timeout;
110 	ctl_logfunc		logger;
111 	evConnID		acID;
112 	LIST(struct ctl_sess)	sess;
113 };
114 
115 /* Forward. */
116 
117 static void			ctl_accept(evContext, void *, int,
118 					   const void *, int,
119 					   const void *, int);
120 static void			ctl_close(struct ctl_sess *);
121 static void			ctl_new_state(struct ctl_sess *,
122 					      enum state,
123 					      const char *);
124 static void			ctl_start_read(struct ctl_sess *);
125 static void			ctl_stop_read(struct ctl_sess *);
126 static void			ctl_readable(evContext, void *, int, int);
127 static void			ctl_rdtimeout(evContext, void *,
128 					      struct timespec,
129 					      struct timespec);
130 static void			ctl_wrtimeout(evContext, void *,
131 					      struct timespec,
132 					      struct timespec);
133 static void			ctl_docommand(struct ctl_sess *);
134 static void			ctl_writedone(evContext, void *, int, int);
135 static void			ctl_morehelp(struct ctl_sctx *,
136 					     struct ctl_sess *,
137 					     const struct ctl_verb *,
138 					     const char *,
139 					     u_int, const void *, void *);
140 static void			ctl_signal_done(struct ctl_sctx *,
141 						struct ctl_sess *);
142 
143 /* Private data. */
144 
145 static const char *		state_names[] = {
146 	"available", "initializing", "writing", "reading",
147 	"reading_data", "processing", "idling", "quitting", "closing"
148 };
149 
150 static const char		space[] = " ";
151 
152 static const struct ctl_verb	fakehelpverb = {
153 	"fakehelp", ctl_morehelp , NULL
154 };
155 
156 /* Public. */
157 
158 /*%
159  * void
160  * ctl_server()
161  *	create, condition, and start a listener on the control port.
162  */
163 struct ctl_sctx *
164 ctl_server(evContext lev, const struct sockaddr *sap, size_t sap_len,
165 	   const struct ctl_verb *verbs,
166 	   u_int unkncode, u_int timeoutcode,
167 	   u_int timeout, int backlog, int max_sess,
168 	   ctl_logfunc logger, void *uctx)
169 {
170 	static const char me[] = "ctl_server";
171 	static const int on = 1;
172 	const struct ctl_verb *connverb;
173 	struct ctl_sctx *ctx;
174 	int save_errno;
175 
176 	if (logger == NULL)
177 		logger = ctl_logger;
178 	for (connverb = verbs;
179 	     connverb->name != NULL && connverb->func != NULL;
180 	     connverb++)
181 		if (connverb->name[0] == '\0')
182 			break;
183 	if (connverb->func == NULL) {
184 		(*logger)(ctl_error, "%s: no connection verb found", me);
185 		return (NULL);
186 	}
187 	ctx = memget(sizeof *ctx);
188 	if (ctx == NULL) {
189 		(*logger)(ctl_error, "%s: getmem: %s", me, strerror(errno));
190 		return (NULL);
191 	}
192 	ctx->ev = lev;
193 	ctx->uctx = uctx;
194 	ctx->unkncode = unkncode;
195 	ctx->timeoutcode = timeoutcode;
196 	ctx->verbs = verbs;
197 	ctx->timeout = evConsTime(timeout, 0);
198 	ctx->logger = logger;
199 	ctx->connverb = connverb;
200 	ctx->max_sess = max_sess;
201 	ctx->cur_sess = 0;
202 	INIT_LIST(ctx->sess);
203 	ctx->sock = socket(sap->sa_family, SOCK_STREAM, PF_UNSPEC);
204 	if (ctx->sock > evHighestFD(ctx->ev)) {
205 		ctx->sock = -1;
206 		errno = ENOTSOCK;
207 	}
208 	if (ctx->sock < 0) {
209 		save_errno = errno;
210 		(*ctx->logger)(ctl_error, "%s: socket: %s",
211 			       me, strerror(errno));
212 		memput(ctx, sizeof *ctx);
213 		errno = save_errno;
214 		return (NULL);
215 	}
216 	if (ctx->sock > evHighestFD(lev)) {
217 		close(ctx->sock);
218 		(*ctx->logger)(ctl_error, "%s: file descriptor > evHighestFD");
219 		errno = ENFILE;
220 		memput(ctx, sizeof *ctx);
221 		return (NULL);
222 	}
223 #ifdef NO_UNIX_REUSEADDR
224 	if (sap->sa_family != AF_UNIX)
225 #endif
226 		if (setsockopt(ctx->sock, SOL_SOCKET, SO_REUSEADDR,
227 			       (const char *)&on, sizeof on) != 0) {
228 			(*ctx->logger)(ctl_warning,
229 				       "%s: setsockopt(REUSEADDR): %s",
230 				       me, strerror(errno));
231 		}
232 	if (bind(ctx->sock, sap, sap_len) < 0) {
233 		char tmp[MAX_NTOP];
234 		save_errno = errno;
235 		(*ctx->logger)(ctl_error, "%s: bind: %s: %s",
236 			       me, ctl_sa_ntop((const struct sockaddr *)sap,
237 			       tmp, sizeof tmp, ctx->logger),
238 			       strerror(save_errno));
239 		close(ctx->sock);
240 		memput(ctx, sizeof *ctx);
241 		errno = save_errno;
242 		return (NULL);
243 	}
244 	if (fcntl(ctx->sock, F_SETFD, 1) < 0) {
245 		(*ctx->logger)(ctl_warning, "%s: fcntl: %s", me,
246 			       strerror(errno));
247 	}
248 	if (evListen(lev, ctx->sock, backlog, ctl_accept, ctx,
249 		     &ctx->acID) < 0) {
250 		save_errno = errno;
251 		(*ctx->logger)(ctl_error, "%s: evListen(fd %d): %s",
252 			       me, ctx->sock, strerror(errno));
253 		close(ctx->sock);
254 		memput(ctx, sizeof *ctx);
255 		errno = save_errno;
256 		return (NULL);
257 	}
258 	(*ctx->logger)(ctl_debug, "%s: new ctx %p, sock %d",
259 		       me, ctx, ctx->sock);
260 	return (ctx);
261 }
262 
263 /*%
264  * void
265  * ctl_endserver(ctx)
266  *	if the control listener is open, close it.  clean out all eventlib
267  *	stuff.  close all active sessions.
268  */
269 void
270 ctl_endserver(struct ctl_sctx *ctx) {
271 	static const char me[] = "ctl_endserver";
272 	struct ctl_sess *this, *next;
273 
274 	(*ctx->logger)(ctl_debug, "%s: ctx %p, sock %d, acID %p, sess %p",
275 		       me, ctx, ctx->sock, ctx->acID.opaque, ctx->sess);
276 	if (ctx->acID.opaque != NULL) {
277 		(void)evCancelConn(ctx->ev, ctx->acID);
278 		ctx->acID.opaque = NULL;
279 	}
280 	if (ctx->sock != -1) {
281 		(void) close(ctx->sock);
282 		ctx->sock = -1;
283 	}
284 	for (this = HEAD(ctx->sess); this != NULL; this = next) {
285 		next = NEXT(this, link);
286 		ctl_close(this);
287 	}
288 	memput(ctx, sizeof *ctx);
289 }
290 
291 /*%
292  * If body is non-NULL then it we add a "." line after it.
293  * Caller must have  escaped lines with leading ".".
294  */
295 void
296 ctl_response(struct ctl_sess *sess, u_int code, const char *text,
297 	     u_int flags, const void *respctx, ctl_srvrdone donefunc,
298 	     void *uap, const char *body, size_t bodylen)
299 {
300 	static const char me[] = "ctl_response";
301 	struct iovec iov[3], *iovp = iov;
302 	struct ctl_sctx *ctx = sess->ctx;
303 	char tmp[MAX_NTOP], *pc;
304 	int n;
305 
306 	REQUIRE(sess->state == initializing ||
307 		sess->state == processing ||
308 		sess->state == reading_data ||
309 		sess->state == writing);
310 	REQUIRE(sess->wrtiID.opaque == NULL);
311 	REQUIRE(sess->wrID.opaque == NULL);
312 	ctl_new_state(sess, writing, me);
313 	sess->donefunc = donefunc;
314 	sess->uap = uap;
315 	if (!allocated_p(sess->outbuf) &&
316 	    ctl_bufget(&sess->outbuf, ctx->logger) < 0) {
317 		(*ctx->logger)(ctl_error, "%s: %s: cant get an output buffer",
318 			       me, address_expr);
319 		goto untimely;
320 	}
321 	if (sizeof "000-\r\n" + strlen(text) > (size_t)MAX_LINELEN) {
322 		(*ctx->logger)(ctl_error, "%s: %s: output buffer ovf, closing",
323 			       me, address_expr);
324 		goto untimely;
325 	}
326 	sess->outbuf.used = SPRINTF((sess->outbuf.text, "%03d%c%s\r\n",
327 				     code, (flags & CTL_MORE) != 0 ? '-' : ' ',
328 				     text));
329 	for (pc = sess->outbuf.text, n = 0;
330 	     n < (int)sess->outbuf.used-2; pc++, n++)
331 		if (!isascii((unsigned char)*pc) ||
332 		    !isprint((unsigned char)*pc))
333 			*pc = '\040';
334 	*iovp++ = evConsIovec(sess->outbuf.text, sess->outbuf.used);
335 	if (body != NULL) {
336 		char *tmp;
337 		DE_CONST(body, tmp);
338 		*iovp++ = evConsIovec(tmp, bodylen);
339 		DE_CONST(".\r\n", tmp);
340 		*iovp++ = evConsIovec(tmp, 3);
341 	}
342 	(*ctx->logger)(ctl_debug, "%s: [%d] %s", me,
343 		       sess->outbuf.used, sess->outbuf.text);
344 	if (evWrite(ctx->ev, sess->sock, iov, iovp - iov,
345 		    ctl_writedone, sess, &sess->wrID) < 0) {
346 		(*ctx->logger)(ctl_error, "%s: %s: evWrite: %s", me,
347 			       address_expr, strerror(errno));
348 		goto untimely;
349 	}
350 	if (evSetIdleTimer(ctx->ev, ctl_wrtimeout, sess, ctx->timeout,
351 			   &sess->wrtiID) < 0)
352 	{
353 		(*ctx->logger)(ctl_error, "%s: %s: evSetIdleTimer: %s", me,
354 			       address_expr, strerror(errno));
355 		goto untimely;
356 	}
357 	if (evTimeRW(ctx->ev, sess->wrID, sess->wrtiID) < 0) {
358 		(*ctx->logger)(ctl_error, "%s: %s: evTimeRW: %s", me,
359 			       address_expr, strerror(errno));
360  untimely:
361 		ctl_signal_done(ctx, sess);
362 		ctl_close(sess);
363 		return;
364 	}
365 	sess->respctx = respctx;
366 	sess->respflags = flags;
367 }
368 
369 void
370 ctl_sendhelp(struct ctl_sess *sess, u_int code) {
371 	static const char me[] = "ctl_sendhelp";
372 	struct ctl_sctx *ctx = sess->ctx;
373 
374 	sess->helpcode = code;
375 	sess->verb = &fakehelpverb;
376 	ctl_morehelp(ctx, sess, NULL, me, CTL_MORE,
377 		     (const void *)ctx->verbs, NULL);
378 }
379 
380 void *
381 ctl_getcsctx(struct ctl_sess *sess) {
382 	return (sess->csctx);
383 }
384 
385 void *
386 ctl_setcsctx(struct ctl_sess *sess, void *csctx) {
387 	void *old = sess->csctx;
388 
389 	sess->csctx = csctx;
390 	return (old);
391 }
392 
393 /* Private functions. */
394 
395 static void
396 ctl_accept(evContext lev, void *uap, int fd,
397 	   const void *lav, int lalen,
398 	   const void *rav, int ralen)
399 {
400 	static const char me[] = "ctl_accept";
401 	struct ctl_sctx *ctx = uap;
402 	struct ctl_sess *sess = NULL;
403 	char tmp[MAX_NTOP];
404 
405 	UNUSED(lev);
406 	UNUSED(lalen);
407 	UNUSED(ralen);
408 
409 	if (fd < 0) {
410 		(*ctx->logger)(ctl_error, "%s: accept: %s",
411 			       me, strerror(errno));
412 		return;
413 	}
414 	if (ctx->cur_sess == ctx->max_sess) {
415 		(*ctx->logger)(ctl_error, "%s: %s: too many control sessions",
416 			       me, ctl_sa_ntop((const struct sockaddr *)rav,
417 					       tmp, sizeof tmp,
418 					       ctx->logger));
419 		(void) close(fd);
420 		return;
421 	}
422 	sess = memget(sizeof *sess);
423 	if (sess == NULL) {
424 		(*ctx->logger)(ctl_error, "%s: memget: %s", me,
425 			       strerror(errno));
426 		(void) close(fd);
427 		return;
428 	}
429 	if (fcntl(fd, F_SETFD, 1) < 0) {
430 		(*ctx->logger)(ctl_warning, "%s: fcntl: %s", me,
431 			       strerror(errno));
432 	}
433 	ctx->cur_sess++;
434 	INIT_LINK(sess, link);
435 	APPEND(ctx->sess, sess, link);
436 	sess->ctx = ctx;
437 	sess->sock = fd;
438 	sess->wrID.opaque = NULL;
439 	sess->rdID.opaque = NULL;
440 	sess->wrtiID.opaque = NULL;
441 	sess->rdtiID.opaque = NULL;
442 	sess->respctx = NULL;
443 	sess->csctx = NULL;
444 	if (((const struct sockaddr *)rav)->sa_family == AF_UNIX)
445 		ctl_sa_copy((const struct sockaddr *)lav,
446 			    (struct sockaddr *)&sess->sa);
447 	else
448 		ctl_sa_copy((const struct sockaddr *)rav,
449 			    (struct sockaddr *)&sess->sa);
450 	sess->donefunc = NULL;
451 	buffer_init(sess->inbuf);
452 	buffer_init(sess->outbuf);
453 	sess->state = available;
454 	ctl_new_state(sess, initializing, me);
455 	sess->verb = ctx->connverb;
456 	(*ctx->logger)(ctl_debug, "%s: %s: accepting (fd %d)",
457 		       me, address_expr, sess->sock);
458 	(*ctx->connverb->func)(ctx, sess, ctx->connverb, "", 0,
459 			       (const struct sockaddr *)rav, ctx->uctx);
460 }
461 
462 static void
463 ctl_new_state(struct ctl_sess *sess, enum state new_state, const char *reason)
464 {
465 	static const char me[] = "ctl_new_state";
466 	struct ctl_sctx *ctx = sess->ctx;
467 	char tmp[MAX_NTOP];
468 
469 	(*ctx->logger)(ctl_debug, "%s: %s: %s -> %s (%s)",
470 		       me, address_expr,
471 		       state_names[sess->state],
472 		       state_names[new_state], reason);
473 	sess->state = new_state;
474 }
475 
476 static void
477 ctl_close(struct ctl_sess *sess) {
478 	static const char me[] = "ctl_close";
479 	struct ctl_sctx *ctx = sess->ctx;
480 	char tmp[MAX_NTOP];
481 
482 	REQUIRE(sess->state == initializing ||
483 		sess->state == writing ||
484 		sess->state == reading ||
485 		sess->state == processing ||
486 		sess->state == reading_data ||
487 		sess->state == idling);
488 	REQUIRE(sess->sock != -1);
489 	if (sess->state == reading || sess->state == reading_data)
490 		ctl_stop_read(sess);
491 	else if (sess->state == writing) {
492 		if (sess->wrID.opaque != NULL) {
493 			(void) evCancelRW(ctx->ev, sess->wrID);
494 			sess->wrID.opaque = NULL;
495 		}
496 		if (sess->wrtiID.opaque != NULL) {
497 			(void) evClearIdleTimer(ctx->ev, sess->wrtiID);
498 			sess->wrtiID.opaque = NULL;
499 		}
500 	}
501 	ctl_new_state(sess, closing, me);
502 	(void) close(sess->sock);
503 	if (allocated_p(sess->inbuf))
504 		ctl_bufput(&sess->inbuf);
505 	if (allocated_p(sess->outbuf))
506 		ctl_bufput(&sess->outbuf);
507 	(*ctx->logger)(ctl_debug, "%s: %s: closed (fd %d)",
508 		       me, address_expr, sess->sock);
509 	UNLINK(ctx->sess, sess, link);
510 	memput(sess, sizeof *sess);
511 	ctx->cur_sess--;
512 }
513 
514 static void
515 ctl_start_read(struct ctl_sess *sess) {
516 	static const char me[] = "ctl_start_read";
517 	struct ctl_sctx *ctx = sess->ctx;
518 	char tmp[MAX_NTOP];
519 
520 	REQUIRE(sess->state == initializing ||
521 		sess->state == writing ||
522 		sess->state == processing ||
523 		sess->state == idling);
524 	REQUIRE(sess->rdtiID.opaque == NULL);
525 	REQUIRE(sess->rdID.opaque == NULL);
526 	sess->inbuf.used = 0;
527 	if (evSetIdleTimer(ctx->ev, ctl_rdtimeout, sess, ctx->timeout,
528 			   &sess->rdtiID) < 0)
529 	{
530 		(*ctx->logger)(ctl_error, "%s: %s: evSetIdleTimer: %s", me,
531 			       address_expr, strerror(errno));
532 		ctl_close(sess);
533 		return;
534 	}
535 	if (evSelectFD(ctx->ev, sess->sock, EV_READ,
536 		       ctl_readable, sess, &sess->rdID) < 0) {
537 		(*ctx->logger)(ctl_error, "%s: %s: evSelectFD: %s", me,
538 			       address_expr, strerror(errno));
539 		return;
540 	}
541 	ctl_new_state(sess, reading, me);
542 }
543 
544 static void
545 ctl_stop_read(struct ctl_sess *sess) {
546 	static const char me[] = "ctl_stop_read";
547 	struct ctl_sctx *ctx = sess->ctx;
548 
549 	REQUIRE(sess->state == reading || sess->state == reading_data);
550 	REQUIRE(sess->rdID.opaque != NULL);
551 	(void) evDeselectFD(ctx->ev, sess->rdID);
552 	sess->rdID.opaque = NULL;
553 	if (sess->rdtiID.opaque != NULL) {
554 		(void) evClearIdleTimer(ctx->ev, sess->rdtiID);
555 		sess->rdtiID.opaque = NULL;
556 	}
557 	ctl_new_state(sess, idling, me);
558 }
559 
560 static void
561 ctl_readable(evContext lev, void *uap, int fd, int evmask) {
562 	static const char me[] = "ctl_readable";
563 	struct ctl_sess *sess = uap;
564 	struct ctl_sctx *ctx;
565 	char *eos, tmp[MAX_NTOP];
566 	ssize_t n;
567 
568 	REQUIRE(sess != NULL);
569 	REQUIRE(fd >= 0);
570 	REQUIRE(evmask == EV_READ);
571 	REQUIRE(sess->state == reading || sess->state == reading_data);
572 
573 	ctx = sess->ctx;
574 	evTouchIdleTimer(lev, sess->rdtiID);
575 	if (!allocated_p(sess->inbuf) &&
576 	    ctl_bufget(&sess->inbuf, ctx->logger) < 0) {
577 		(*ctx->logger)(ctl_error, "%s: %s: cant get an input buffer",
578 			       me, address_expr);
579 		ctl_close(sess);
580 		return;
581 	}
582 	n = read(sess->sock, sess->inbuf.text + sess->inbuf.used,
583 		 MAX_LINELEN - sess->inbuf.used);
584 	if (n <= 0) {
585 		(*ctx->logger)(ctl_debug, "%s: %s: read: %s",
586 			       me, address_expr,
587 			       (n == 0) ? "Unexpected EOF" : strerror(errno));
588 		ctl_close(sess);
589 		return;
590 	}
591 	sess->inbuf.used += n;
592 	eos = memchr(sess->inbuf.text, '\n', sess->inbuf.used);
593 	if (eos != NULL && eos != sess->inbuf.text && eos[-1] == '\r') {
594 		eos[-1] = '\0';
595 		if ((sess->respflags & CTL_DATA) != 0) {
596 			INSIST(sess->verb != NULL);
597 			(*sess->verb->func)(sess->ctx, sess, sess->verb,
598 					    sess->inbuf.text,
599 					    CTL_DATA, sess->respctx,
600 					    sess->ctx->uctx);
601 		} else {
602 			ctl_stop_read(sess);
603 			ctl_docommand(sess);
604 		}
605 		sess->inbuf.used -= ((eos - sess->inbuf.text) + 1);
606 		if (sess->inbuf.used == 0U)
607 			ctl_bufput(&sess->inbuf);
608 		else
609 			memmove(sess->inbuf.text, eos + 1, sess->inbuf.used);
610 		return;
611 	}
612 	if (sess->inbuf.used == (size_t)MAX_LINELEN) {
613 		(*ctx->logger)(ctl_error, "%s: %s: line too long, closing",
614 			       me, address_expr);
615 		ctl_close(sess);
616 	}
617 }
618 
619 static void
620 ctl_wrtimeout(evContext lev, void *uap,
621 	      struct timespec due,
622 	      struct timespec itv)
623 {
624 	static const char me[] = "ctl_wrtimeout";
625 	struct ctl_sess *sess = uap;
626 	struct ctl_sctx *ctx = sess->ctx;
627 	char tmp[MAX_NTOP];
628 
629 	UNUSED(lev);
630 	UNUSED(due);
631 	UNUSED(itv);
632 
633 	REQUIRE(sess->state == writing);
634 	sess->wrtiID.opaque = NULL;
635 	(*ctx->logger)(ctl_warning, "%s: %s: write timeout, closing",
636 		       me, address_expr);
637 	if (sess->wrID.opaque != NULL) {
638 		(void) evCancelRW(ctx->ev, sess->wrID);
639 		sess->wrID.opaque = NULL;
640 	}
641 	ctl_signal_done(ctx, sess);
642 	ctl_new_state(sess, processing, me);
643 	ctl_close(sess);
644 }
645 
646 static void
647 ctl_rdtimeout(evContext lev, void *uap,
648 	      struct timespec due,
649 	      struct timespec itv)
650 {
651 	static const char me[] = "ctl_rdtimeout";
652 	struct ctl_sess *sess = uap;
653 	struct ctl_sctx *ctx = sess->ctx;
654 	char tmp[MAX_NTOP];
655 
656 	UNUSED(lev);
657 	UNUSED(due);
658 	UNUSED(itv);
659 
660 	REQUIRE(sess->state == reading);
661 	sess->rdtiID.opaque = NULL;
662 	(*ctx->logger)(ctl_warning, "%s: %s: timeout, closing",
663 		       me, address_expr);
664 	if (sess->state == reading || sess->state == reading_data)
665 		ctl_stop_read(sess);
666 	ctl_signal_done(ctx, sess);
667 	ctl_new_state(sess, processing, me);
668 	ctl_response(sess, ctx->timeoutcode, "Timeout.", CTL_EXIT, NULL,
669 		     NULL, NULL, NULL, 0);
670 }
671 
672 static void
673 ctl_docommand(struct ctl_sess *sess) {
674 	static const char me[] = "ctl_docommand";
675 	char *name, *rest, tmp[MAX_NTOP];
676 	struct ctl_sctx *ctx = sess->ctx;
677 	const struct ctl_verb *verb;
678 
679 	REQUIRE(allocated_p(sess->inbuf));
680 	(*ctx->logger)(ctl_debug, "%s: %s: \"%s\" [%u]",
681 		       me, address_expr,
682 		       sess->inbuf.text, (u_int)sess->inbuf.used);
683 	ctl_new_state(sess, processing, me);
684 	name = sess->inbuf.text + strspn(sess->inbuf.text, space);
685 	rest = name + strcspn(name, space);
686 	if (*rest != '\0') {
687 		*rest++ = '\0';
688 		rest += strspn(rest, space);
689 	}
690 	for (verb = ctx->verbs;
691 	     verb != NULL && verb->name != NULL && verb->func != NULL;
692 	     verb++)
693 		if (verb->name[0] != '\0' && strcasecmp(name, verb->name) == 0)
694 			break;
695 	if (verb != NULL && verb->name != NULL && verb->func != NULL) {
696 		sess->verb = verb;
697 		(*verb->func)(ctx, sess, verb, rest, 0, NULL, ctx->uctx);
698 	} else {
699 		char buf[1100];
700 
701 		if (sizeof "Unrecognized command \"\" (args \"\")" +
702 		    strlen(name) + strlen(rest) > sizeof buf)
703 			strcpy(buf, "Unrecognized command (buf ovf)");
704 		else
705 			sprintf(buf,
706 				"Unrecognized command \"%s\" (args \"%s\")",
707 				name, rest);
708 		ctl_response(sess, ctx->unkncode, buf, 0, NULL, NULL, NULL,
709 			     NULL, 0);
710 	}
711 }
712 
713 static void
714 ctl_writedone(evContext lev, void *uap, int fd, int bytes) {
715 	static const char me[] = "ctl_writedone";
716 	struct ctl_sess *sess = uap;
717 	struct ctl_sctx *ctx = sess->ctx;
718 	char tmp[MAX_NTOP];
719 	int save_errno = errno;
720 
721 	UNUSED(lev);
722 	UNUSED(uap);
723 
724 	REQUIRE(sess->state == writing);
725 	REQUIRE(fd == sess->sock);
726 	REQUIRE(sess->wrtiID.opaque != NULL);
727 	sess->wrID.opaque = NULL;
728 	(void) evClearIdleTimer(ctx->ev, sess->wrtiID);
729 	sess->wrtiID.opaque = NULL;
730 	if (bytes < 0) {
731 		(*ctx->logger)(ctl_error, "%s: %s: %s",
732 			       me, address_expr, strerror(save_errno));
733 		ctl_close(sess);
734 		return;
735 	}
736 
737 	INSIST(allocated_p(sess->outbuf));
738 	ctl_bufput(&sess->outbuf);
739 	if ((sess->respflags & CTL_EXIT) != 0) {
740 		ctl_signal_done(ctx, sess);
741 		ctl_close(sess);
742 		return;
743 	} else if ((sess->respflags & CTL_MORE) != 0) {
744 		INSIST(sess->verb != NULL);
745 		(*sess->verb->func)(sess->ctx, sess, sess->verb, "",
746 				    CTL_MORE, sess->respctx, sess->ctx->uctx);
747 	} else {
748 		ctl_signal_done(ctx, sess);
749 		ctl_start_read(sess);
750 	}
751 }
752 
753 static void
754 ctl_morehelp(struct ctl_sctx *ctx, struct ctl_sess *sess,
755 	     const struct ctl_verb *verb, const char *text,
756 	     u_int respflags, const void *respctx, void *uctx)
757 {
758 	const struct ctl_verb *this = respctx, *next = this + 1;
759 
760 	UNUSED(ctx);
761 	UNUSED(verb);
762 	UNUSED(text);
763 	UNUSED(uctx);
764 
765 	REQUIRE(!lastverb_p(this));
766 	REQUIRE((respflags & CTL_MORE) != 0);
767 	if (lastverb_p(next))
768 		respflags &= ~CTL_MORE;
769 	ctl_response(sess, sess->helpcode, this->help, respflags, next,
770 		     NULL, NULL, NULL, 0);
771 }
772 
773 static void
774 ctl_signal_done(struct ctl_sctx *ctx, struct ctl_sess *sess) {
775 	if (sess->donefunc != NULL) {
776 		(*sess->donefunc)(ctx, sess, sess->uap);
777 		sess->donefunc = NULL;
778 	}
779 }
780 
781 /*! \file */
782