xref: /openbsd-src/usr.sbin/ypldap/ldapclient.c (revision 850e275390052b330d93020bf619a739a3c277ac)
1 /* $OpenBSD: ldapclient.c,v 1.2 2008/07/02 17:36:15 pyr Exp $ */
2 
3 /*
4  * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@openbsd.org>
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/types.h>
20 #include <sys/param.h>
21 #include <sys/queue.h>
22 #include <sys/socket.h>
23 #include <sys/tree.h>
24 
25 #include <netinet/in.h>
26 #include <arpa/inet.h>
27 
28 #include <errno.h>
29 #include <event.h>
30 #include <fcntl.h>
31 #include <unistd.h>
32 #include <pwd.h>
33 #include <stdio.h>
34 #include <stdlib.h>
35 #include <string.h>
36 
37 #define LDAP_DEPRECATED 1
38 #include <ldap.h>
39 
40 #include "ypldap.h"
41 
42 void    client_sig_handler(int, short, void *);
43 void    client_dispatch_parent(int, short, void *);
44 void    client_shutdown(void);
45 void    client_connect(int, short, void *);
46 void    client_configure(struct env *);
47 void    client_configure_wrapper(int, short, void *);
48 int	client_try_idm(struct env *, struct idm *);
49 void	client_try_idm_wrapper(int, short, void *);
50 void	client_try_server_wrapper(int, short, void *);
51 
52 void
53 client_sig_handler(int sig, short event, void *p)
54 {
55 	switch (sig) {
56 	case SIGINT:
57 	case SIGTERM:
58 		client_shutdown();
59 		break;
60 	default:
61 		fatalx("unexpected signal");
62 	}
63 }
64 
65 void
66 client_dispatch_parent(int fd, short event, void *p)
67 {
68 	int			 n;
69 	int			 shut = 0;
70 	struct imsg		 imsg;
71 	struct env		*env = p;
72 	struct imsgbuf		*ibuf = env->sc_ibuf;
73 
74 
75 	switch (event) {
76 	case EV_READ:
77 		if ((n = imsg_read(ibuf)) == -1)
78 			fatal("imsg_read error");
79 		if (n == 0)
80 			shut = 1;
81 		break;
82 	case EV_WRITE:
83 		if (msgbuf_write(&ibuf->w) == -1)
84 			fatal("msgbuf_write");
85 		imsg_event_add(ibuf);
86 		return;
87 	default:
88 		fatalx("unknown event");
89 	}
90 
91 	for (;;) {
92 		if ((n = imsg_get(ibuf, &imsg)) == -1)
93 			fatal("client_dispatch_parent: imsg_read_error");
94 		if (n == 0)
95 			break;
96 
97 		switch (imsg.hdr.type) {
98 		case IMSG_CONF_START: {
99 			struct env	params;
100 
101 			if (env->sc_flags & F_CONFIGURING) {
102 				log_warnx("configuration already in progress");
103 				break;
104 			}
105 			memcpy(&params, imsg.data, sizeof(params));
106 			log_debug("configuration starting");
107 			env->sc_flags |= F_CONFIGURING;
108 			purge_config(env);
109 			memcpy(&env->sc_conf_tv, &params.sc_conf_tv,
110 			    sizeof(env->sc_conf_tv));
111 			env->sc_flags |= params.sc_flags;
112 			break;
113 		}
114 		case IMSG_CONF_IDM: {
115 			struct idm	*idm;
116 
117 			if (!(env->sc_flags & F_CONFIGURING))
118 				break;
119 			if ((idm = calloc(1, sizeof(*idm))) == NULL)
120 				fatal(NULL);
121 			memcpy(idm, imsg.data, sizeof(*idm));
122 			idm->idm_env = env;
123 			TAILQ_INSERT_TAIL(&env->sc_idms, idm, idm_entry);
124 			break;
125 		}
126 		case IMSG_CONF_END:
127 			env->sc_flags &= ~F_CONFIGURING;
128 			log_debug("applying configuration");
129 			client_configure(env);
130 			break;
131 		default:
132 			log_debug("client_dispatch_parent: unexpect imsg %d",
133 			    imsg.hdr.type);
134 
135 			break;
136 		}
137 		imsg_free(&imsg);
138 	}
139 	if (!shut)
140 		imsg_event_add(ibuf);
141 	else {
142 		/* this pipe is dead, so remove the event handler */
143 		event_del(&ibuf->ev);
144 		event_loopexit(NULL);
145 	}
146 }
147 
148 void
149 client_shutdown(void)
150 {
151 	log_info("ldap client exiting");
152 	_exit(0);
153 }
154 
155 pid_t
156 ldapclient(int pipe_main2client[2])
157 {
158 	pid_t		 pid;
159 	struct passwd	*pw;
160 	struct event	 ev_sigint;
161 	struct event	 ev_sigterm;
162 	struct env	 env;
163 
164 	switch (pid = fork()) {
165 	case -1:
166 		fatal("cannot fork");
167 		break;
168 	case 0:
169 		break;
170 	default:
171 		return (pid);
172 	}
173 
174 	bzero(&env, sizeof(env));
175 	TAILQ_INIT(&env.sc_idms);
176 
177 	if ((pw = getpwnam(YPLDAP_USER)) == NULL)
178 		fatal("getpwnam");
179 
180 #ifndef DEBUG
181 	if (chroot(pw->pw_dir) == -1)
182 		fatal("chroot");
183 	if (chdir("/") == -1)
184 		fatal("chdir");
185 #else
186 #warning disabling chrooting in DEBUG mode
187 #endif
188 	setproctitle("ldap client");
189 	ypldap_process = PROC_CLIENT;
190 
191 #ifndef DEBUG
192 	if (setgroups(1, &pw->pw_gid) ||
193 	    setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) ||
194 	    setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid))
195 		fatal("cannot drop privileges");
196 #else
197 #warning disabling privilege revocation in DEBUG mode
198 #endif
199 
200 	event_init();
201 	signal_set(&ev_sigint, SIGINT, client_sig_handler, NULL);
202 	signal_set(&ev_sigterm, SIGTERM, client_sig_handler, NULL);
203 	signal_add(&ev_sigint, NULL);
204 	signal_add(&ev_sigterm, NULL);
205 
206 	close(pipe_main2client[0]);
207 	if ((env.sc_ibuf = calloc(1, sizeof(*env.sc_ibuf))) == NULL)
208 		fatal(NULL);
209 
210 	env.sc_ibuf->events = EV_READ;
211 	env.sc_ibuf->data = &env;
212 	imsg_init(env.sc_ibuf, pipe_main2client[1], client_dispatch_parent);
213 	event_set(&env.sc_ibuf->ev, env.sc_ibuf->fd, env.sc_ibuf->events,
214 	    env.sc_ibuf->handler, &env);
215 	event_add(&env.sc_ibuf->ev, NULL);
216 
217 	event_dispatch();
218 	client_shutdown();
219 
220 	return (0);
221 
222 }
223 
224 void
225 client_configure_wrapper(int fd, short event, void *p)
226 {
227 	struct env	*env = p;
228 
229 	client_configure(env);
230 }
231 
232 int
233 client_try_idm(struct env *env, struct idm *idm)
234 {
235 	int		 i;
236 	int		 j;
237 	int		 msgid;
238 	size_t		 n;
239 	LDAP		*ld;
240 	LDAPMessage	*lm;
241 	char		**ldap_attrs;
242 	char		*attrs[ATTR_MAX+1];
243 	const char	*where;
244 	struct idm_req	 ir;
245 
246 	log_debug("trying directory: %s", idm->idm_name);
247 
248 	bzero(&ir, sizeof(ir));
249 	imsg_compose(env->sc_ibuf, IMSG_START_UPDATE, 0, 0, &ir, sizeof(ir));
250 
251 	where = "ldap_open";
252 	if ((ld = ldap_open(idm->idm_name, LDAP_PORT)) == NULL)
253 		goto bad;
254 
255 	where = "ldap_bind";
256 	if (ldap_bind(ld, idm->idm_binddn,
257 	    idm->idm_bindcred, LDAP_AUTH_SIMPLE) < 0)
258 		goto bad;
259 
260 	bzero(attrs, sizeof(attrs));
261 	for (i = 0, j = 0; i < ATTR_MAX; i++) {
262 		if (idm->idm_flags & F_FIXED_ATTR(i))
263 			continue;
264 		attrs[j++] = idm->idm_attrs[i];
265 	}
266 	attrs[j] = NULL;
267 
268 	where = "ldap_search";
269 	if ((msgid = ldap_search(ld, idm->idm_binddn, LDAP_SCOPE_SUBTREE,
270 	    idm->idm_filters[FILTER_USER], attrs, 0)) < 0)
271 		goto bad;
272 
273 	where = "ldap_result";
274 	if (ldap_result(ld, msgid, 1, NULL, &lm) == -1)
275 		goto bad;
276 
277 	where = "ldap_result_message";
278 	if (lm == NULL)
279 		goto bad;
280 	where = "ldap_first_message";
281 	if ((lm = ldap_first_message(ld, lm)) == NULL)
282 		goto bad;
283 
284 	/*
285 	 * build password line.
286 	 */
287 	n = ldap_count_entries(ld, lm);
288 	while (n-- > 0) {
289 		bzero(&ir, sizeof(ir));
290 		for (i = 0, j = 0; i < ATTR_MAX; i++) {
291 			if (idm->idm_flags & F_FIXED_ATTR(i)) {
292 				if (strlcat(ir.ir_line, idm->idm_attrs[i],
293 				    sizeof(ir.ir_line)) >= sizeof(ir.ir_line))
294 					/*
295 					 * entry yields a line > 1024, trash it.
296 					 */
297 					goto next_entry;
298 				if (i == ATTR_UID) {
299 					ir.ir_key.ik_uid = strtonum(
300 					    idm->idm_attrs[i], 0,
301 					    UID_MAX, NULL);
302 				}
303 			} else {
304 				ldap_attrs = (char **)ldap_get_values(ld,
305 				    lm, attrs[j++]);
306 				if (ldap_attrs == NULL)
307 					goto next_entry;
308 				if (strlcat(ir.ir_line, ldap_attrs[0],
309 				    sizeof(ir.ir_line)) >= sizeof(ir.ir_line))
310 					goto next_entry;
311 				if (i == ATTR_UID) {
312 					ir.ir_key.ik_uid = strtonum(
313 					    ldap_attrs[0], 0, UID_MAX, NULL);
314 				}
315 				ldap_value_free(ldap_attrs);
316 			}
317 			if (i != ATTR_SHELL)
318 				if (strlcat(ir.ir_line, ":",
319 				    sizeof(ir.ir_line)) >= sizeof(ir.ir_line))
320 					goto next_entry;
321 
322 		}
323 		imsg_compose(env->sc_ibuf, IMSG_PW_ENTRY, 0, 0,
324 		    &ir, sizeof(ir));
325 next_entry:
326 		where = "ldap_next_message";
327 		if ((lm = ldap_next_message(ld, lm)) == NULL)
328 			goto bad;
329 	}
330 
331 	/*
332 	 * exact same code but for groups.
333 	 */
334 
335 	bzero(attrs, sizeof(attrs));
336 	for (i = ATTR_GR_MIN, j = 0; i < ATTR_GR_MAX; i++) {
337 		if (idm->idm_flags & F_FIXED_ATTR(i))
338 			continue;
339 		attrs[j++] = idm->idm_attrs[i];
340 	}
341 	attrs[j] = NULL;
342 
343 	where = "ldap_search";
344 	if ((msgid = ldap_search(ld, idm->idm_binddn, LDAP_SCOPE_SUBTREE,
345 	    idm->idm_filters[FILTER_GROUP], attrs, 0)) < 0)
346 		goto bad;
347 
348 	where = "ldap_result";
349 	if (ldap_result(ld, msgid, 1, NULL, &lm) == -1)
350 		goto bad;
351 
352 	where = "ldap_result_message";
353 	if (lm == NULL)
354 		goto bad;
355 
356 	where = "ldap_first_message";
357 	if ((lm = ldap_first_message(ld, lm)) == NULL)
358 		goto bad;
359 
360 	/*
361 	 * build group line.
362 	 */
363 
364 	n = ldap_count_entries(ld, lm);
365 	while (n--) {
366 		bzero(&ir, sizeof(ir));
367 		for (i = ATTR_GR_MIN, j = 0; i < ATTR_GR_MAX; i++) {
368 			if (idm->idm_flags & F_FIXED_ATTR(i)) {
369 				if (strlcat(ir.ir_line, idm->idm_attrs[i],
370 				    sizeof(ir.ir_line)) >= sizeof(ir.ir_line))
371 					/*
372 					 * entry yields a line > 1024, trash it.
373 					 */
374 					goto next_group_entry;
375 				if (i == ATTR_GR_GID) {
376 					ir.ir_key.ik_gid = strtonum(
377 					    idm->idm_attrs[i], 0,
378 					    GID_MAX, NULL);
379 				}
380 			} else {
381 				ldap_attrs = (char **)ldap_get_values(ld,
382 				    lm, attrs[j++]);
383 				if (ldap_attrs == NULL)
384 					goto next_group_entry;
385 				if (strlcat(ir.ir_line, ldap_attrs[0],
386 				    sizeof(ir.ir_line)) >= sizeof(ir.ir_line))
387 					goto next_group_entry;
388 				if (i == ATTR_GR_GID) {
389 					ir.ir_key.ik_gid = strtonum(
390 					    ldap_attrs[0], 0, GID_MAX, NULL);
391 				}
392 				ldap_value_free(ldap_attrs);
393 			}
394 			if (i != ATTR_GR_MEMBERS)
395 				if (strlcat(ir.ir_line, ":",
396 				    sizeof(ir.ir_line)) >= sizeof(ir.ir_line))
397 					goto next_group_entry;
398 
399 		}
400 		imsg_compose(env->sc_ibuf, IMSG_GRP_ENTRY, 0, 0,
401 		    &ir, sizeof(ir));
402 next_group_entry:
403 		where = "ldap_next_message";
404 		if ((lm = ldap_next_message(ld, lm)) == NULL)
405 			goto bad;
406 	}
407 
408 	return (0);
409 bad:
410 	log_debug("directory %s errored out in %s", idm->idm_name, where);
411 	return (-1);
412 }
413 
414 void
415 client_configure(struct env *env)
416 {
417 	enum imsg_type	 finish;
418 	struct timeval	 tv;
419 	struct idm	*idm;
420 
421 	log_debug("connecting to directories");
422 	finish = IMSG_END_UPDATE;
423 	imsg_compose(env->sc_ibuf, IMSG_START_UPDATE, 0, 0, NULL, 0);
424 	TAILQ_FOREACH(idm, &env->sc_idms, idm_entry)
425 		if (client_try_idm(env, idm) == -1) {
426 			finish = IMSG_TRASH_UPDATE;
427 			break;
428 		}
429 	imsg_compose(env->sc_ibuf, finish, 0, 0, NULL, 0);
430 	tv.tv_sec = env->sc_conf_tv.tv_sec;
431 	tv.tv_usec = env->sc_conf_tv.tv_usec;
432 	evtimer_set(&env->sc_conf_ev, client_configure_wrapper, env);
433 	evtimer_add(&env->sc_conf_ev, &tv);
434 }
435