conn.c revision 1.5
1/*	$OpenBSD: conn.c,v 1.5 2010/07/01 20:09:34 martinh Exp $ */
2
3/*
4 * Copyright (c) 2009, 2010 Martin Hedenfalk <martin@bzero.se>
5 *
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18
19#include <sys/queue.h>
20#include <sys/types.h>
21
22#include <stdlib.h>
23#include <errno.h>
24#include <unistd.h>
25
26#include "ldapd.h"
27
28int			 conn_dispatch(struct conn *conn);
29unsigned long		 ldap_application(struct ber_element *elm);
30
31struct conn_list	 conn_list;
32
33unsigned long
34ldap_application(struct ber_element *elm)
35{
36	return BER_TYPE_OCTETSTRING;
37}
38
39void
40request_free(struct request *req)
41{
42	if (req->root != NULL)
43		ber_free_elements(req->root);
44	free(req);
45}
46
47void
48conn_close(struct conn *conn)
49{
50	struct search	*search, *next;
51
52	log_debug("closing connection %d", conn->fd);
53
54	/* Cancel any ongoing searches on this connection. */
55	for (search = TAILQ_FIRST(&conn->searches); search; search = next) {
56		next = TAILQ_NEXT(search, next);
57		search_close(search);
58	}
59
60	/* Cancel any queued requests on this connection. */
61	namespace_cancel_conn(conn);
62
63	ssl_session_destroy(conn);
64
65	TAILQ_REMOVE(&conn_list, conn, next);
66	ber_free(&conn->ber);
67	if (conn->bev != NULL)
68		bufferevent_free(conn->bev);
69	close(conn->fd);
70	free(conn->binddn);
71	free(conn);
72
73	--stats.conns;
74}
75
76/* Marks a connection for disconnect. The connection will be closed when
77 * any remaining data has been flushed to the socket.
78 */
79void
80conn_disconnect(struct conn *conn)
81{
82	conn->disconnect = 1;
83	bufferevent_enable(conn->bev, EV_WRITE);
84}
85
86void
87request_dispatch(struct request *req)
88{
89	unsigned long		 i;
90	struct {
91		unsigned long	 type;
92		int (*fn)(struct request *);
93	} requests[] = {
94		{ LDAP_REQ_SEARCH,	ldap_search },
95		{ LDAP_REQ_BIND,	ldap_bind },
96		{ LDAP_REQ_COMPARE,	ldap_compare },
97		{ LDAP_REQ_ADD,		ldap_add },
98		{ LDAP_REQ_UNBIND_30,	ldap_unbind },
99		{ LDAP_REQ_MODIFY,	ldap_modify },
100		{ LDAP_REQ_ABANDON_30,	ldap_abandon },
101		{ LDAP_REQ_DELETE_30,	ldap_delete },
102		{ LDAP_REQ_EXTENDED,	ldap_extended },
103		{ 0,			NULL }
104	};
105
106	/* RFC4511, section 4.2.1 says we shouldn't process other requests
107	 * while binding. A bind operation can, however, be aborted by sending
108	 * another bind operation.
109	 */
110	if (req->conn->bind_req != NULL && req->type != LDAP_REQ_BIND) {
111		log_warnx("got request while bind in progress");
112		ldap_respond(req, LDAP_SASL_BIND_IN_PROGRESS);
113		return;
114	}
115
116	for (i = 0; requests[i].fn != NULL; i++) {
117		if (requests[i].type == req->type) {
118			requests[i].fn(req);
119			break;
120		}
121	}
122
123	if (requests[i].fn == NULL) {
124		log_warnx("unhandled request %d (not implemented)", req->type);
125		ldap_respond(req, LDAP_PROTOCOL_ERROR);
126	}
127}
128
129int
130conn_dispatch(struct conn *conn)
131{
132	int			 class;
133	struct request		*req;
134
135	++stats.requests;
136
137	if ((req = calloc(1, sizeof(*req))) == NULL) {
138		log_warn("calloc");
139		conn_disconnect(conn);
140		return -1;
141	}
142
143	req->conn = conn;
144
145	if ((req->root = ber_read_elements(&conn->ber, NULL)) == NULL) {
146		if (errno != ECANCELED) {
147			log_warnx("protocol error");
148			conn_disconnect(conn);
149		}
150		request_free(req);
151		return -1;
152	}
153
154	/* Read message id and request type.
155	 */
156	if (ber_scanf_elements(req->root, "{ite",
157	    &req->msgid, &class, &req->type, &req->op) != 0) {
158		log_warnx("protocol error");
159		conn_disconnect(conn);
160		request_free(req);
161		return -1;
162	}
163
164	log_debug("got request type %d, id %lld", req->type, req->msgid);
165	request_dispatch(req);
166	return 0;
167}
168
169void
170conn_read(struct bufferevent *bev, void *data)
171{
172	size_t			 nused = 0;
173	struct conn		*conn = data;
174	struct evbuffer		*input;
175
176	input = EVBUFFER_INPUT(bev);
177	ber_set_readbuf(&conn->ber,
178	    EVBUFFER_DATA(input), EVBUFFER_LENGTH(input));
179
180	while (conn->ber.br_rend - conn->ber.br_rptr > 0) {
181		if (conn_dispatch(conn) == 0)
182			nused += conn->ber.br_rptr - conn->ber.br_rbuf;
183		else
184			break;
185	}
186
187	evbuffer_drain(input, nused);
188}
189
190void
191conn_write(struct bufferevent *bev, void *data)
192{
193	struct search	*search, *next;
194	struct conn	*conn = data;
195
196	/* Continue any ongoing searches.
197	 * Note that the search may be unlinked and freed by conn_search.
198	 */
199	for (search = TAILQ_FIRST(&conn->searches); search; search = next) {
200		next = TAILQ_NEXT(search, next);
201		conn_search(search);
202	}
203
204	if (conn->disconnect)
205		conn_close(conn);
206	else if (conn->s_flags & F_STARTTLS) {
207		conn->s_flags &= ~F_STARTTLS;
208		bufferevent_free(conn->bev);
209		conn->bev = NULL;
210		ssl_session_init(conn);
211	}
212}
213
214void
215conn_err(struct bufferevent *bev, short why, void *data)
216{
217	struct conn	*conn = data;
218
219	if ((why & EVBUFFER_EOF) == EVBUFFER_EOF)
220		log_debug("end-of-file on connection %i", conn->fd);
221	else if ((why & EVBUFFER_TIMEOUT) == EVBUFFER_TIMEOUT)
222		log_debug("timeout on connection %i", conn->fd);
223	else
224		log_warnx("error 0x%02X on connection %i", why, conn->fd);
225
226	conn_close(conn);
227}
228
229void
230conn_accept(int fd, short why, void *data)
231{
232	int			 afd;
233	socklen_t		 addrlen;
234	struct conn		*conn;
235	struct listener		*l = data;
236	struct sockaddr_storage	 remote_addr;
237	char			 host[128];
238
239	addrlen = sizeof(remote_addr);
240	afd = accept(fd, (struct sockaddr *)&remote_addr, &addrlen);
241	if (afd == -1) {
242		log_warn("accept");
243		return;
244	}
245
246	if (l->ss.ss_family == AF_UNIX) {
247		uid_t		 euid;
248		gid_t		 egid;
249
250		if (getpeereid(afd, &euid, &egid) == -1)
251			log_warnx("conn_accept: getpeereid");
252		else
253			log_debug("accepted local connection by uid %d", euid);
254	} else {
255		print_host(&remote_addr, host, sizeof(host));
256		log_debug("accepted connection from %s on fd %d", host, afd);
257	}
258
259	fd_nonblock(afd);
260
261	if ((conn = calloc(1, sizeof(*conn))) == NULL) {
262		log_warn("malloc");
263		close(afd);
264		return;
265	}
266	conn->ber.fd = -1;
267	conn->s_l = l;
268	ber_set_application(&conn->ber, ldap_application);
269	conn->fd = afd;
270	conn->listener = l;
271
272	if (l->flags & F_LDAPS) {
273		ssl_session_init(conn);
274	} else {
275		conn->bev = bufferevent_new(afd, conn_read, conn_write,
276		    conn_err, conn);
277		if (conn->bev == NULL) {
278			log_warn("conn_accept: bufferevent_new");
279			close(afd);
280			free(conn);
281			return;
282		}
283		bufferevent_enable(conn->bev, EV_READ);
284		bufferevent_settimeout(conn->bev, 0, 60);
285	}
286
287	TAILQ_INIT(&conn->searches);
288	TAILQ_INSERT_HEAD(&conn_list, conn, next);
289
290	if (l->flags & F_SECURE)
291		conn->s_flags |= F_SECURE;
292
293	++stats.conns;
294}
295
296struct conn *
297conn_by_fd(int fd)
298{
299	struct conn		*conn;
300
301	TAILQ_FOREACH(conn, &conn_list, next) {
302		if (conn->fd == fd)
303			return conn;
304	}
305	return NULL;
306}
307
308