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