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