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