xref: /freebsd/usr.sbin/ypldap/yp.c (revision 10ff414c)
1 /*	$OpenBSD: yp.c,v 1.14 2015/02/11 01:26:00 pelikan Exp $ */
2 /*	$FreeBSD$ */
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/select.h>
24 #include <sys/tree.h>
25 
26 #include <netinet/in.h>
27 #include <arpa/inet.h>
28 
29 #include <errno.h>
30 #include <event.h>
31 #include <fcntl.h>
32 #include <unistd.h>
33 #include <pwd.h>
34 #include <stdio.h>
35 #include <stdlib.h>
36 #include <string.h>
37 #include <limits.h>
38 
39 #include <rpc/rpc.h>
40 #include <rpc/xdr.h>
41 #include <rpc/pmap_clnt.h>
42 #include <rpc/pmap_prot.h>
43 #include <rpc/pmap_rmt.h>
44 #include <rpcsvc/yp.h>
45 #include <rpcsvc/ypclnt.h>
46 
47 #include "ypldap.h"
48 
49 void	yp_dispatch(struct svc_req *, SVCXPRT *);
50 void	yp_disable_events(void);
51 void	yp_fd_event(int, short, void *);
52 int	yp_check(struct svc_req *);
53 int	yp_valid_domain(char *, struct ypresp_val *);
54 void	yp_make_val(struct ypresp_val *, char *, int);
55 void	yp_make_keyval(struct ypresp_key_val *, char *, char *);
56 
57 static struct env	*env;
58 
59 struct yp_event {
60 	TAILQ_ENTRY(yp_event)	 ye_entry;
61 	struct event		 ye_event;
62 };
63 
64 struct yp_data {
65 	SVCXPRT			*yp_trans_udp;
66 	SVCXPRT			*yp_trans_tcp;
67 	TAILQ_HEAD(, yp_event)	 yd_events;
68 };
69 
70 void
71 yp_disable_events(void)
72 {
73 	struct yp_event	*ye;
74 
75 	while ((ye = TAILQ_FIRST(&env->sc_yp->yd_events)) != NULL) {
76 		TAILQ_REMOVE(&env->sc_yp->yd_events, ye, ye_entry);
77 		event_del(&ye->ye_event);
78 		free(ye);
79 	}
80 }
81 
82 void
83 yp_enable_events(void)
84 {
85 	int i;
86 	struct yp_event	*ye;
87 
88 	for (i = 0; i < getdtablesize(); i++) {
89 		if ((ye = calloc(1, sizeof(*ye))) == NULL)
90 			fatal(NULL);
91 		event_set(&ye->ye_event, i, EV_READ, yp_fd_event, NULL);
92 		event_add(&ye->ye_event, NULL);
93 		TAILQ_INSERT_TAIL(&env->sc_yp->yd_events, ye, ye_entry);
94 	}
95 }
96 
97 void
98 yp_fd_event(int fd, short event, void *p)
99 {
100 	svc_getreq_common(fd);
101 	yp_disable_events();
102 	yp_enable_events();
103 }
104 
105 void
106 yp_init(struct env *x_env)
107 {
108 	struct yp_data	*yp;
109 
110 	if ((yp = calloc(1, sizeof(*yp))) == NULL)
111 		fatal(NULL);
112 	TAILQ_INIT(&yp->yd_events);
113 
114 	env = x_env;
115 	env->sc_yp = yp;
116 
117 	(void)pmap_unset(YPPROG, YPVERS);
118 
119 	if ((yp->yp_trans_udp = svcudp_create(RPC_ANYSOCK)) == NULL)
120 		fatal("cannot create udp service");
121 	if ((yp->yp_trans_tcp = svctcp_create(RPC_ANYSOCK, 0, 0)) == NULL)
122 		fatal("cannot create tcp service");
123 
124 	if (!svc_register(yp->yp_trans_udp, YPPROG, YPVERS,
125 	    yp_dispatch, IPPROTO_UDP)) {
126 		fatal("unable to register (YPPROG, YPVERS, udp)");
127 	}
128 	if (!svc_register(yp->yp_trans_tcp, YPPROG, YPVERS,
129 	    yp_dispatch, IPPROTO_TCP)) {
130 		fatal("unable to register (YPPROG, YPVERS, tcp)");
131 	}
132 }
133 
134 /*
135  * lots of inspiration from ypserv by Mats O Jansson
136  */
137 void
138 yp_dispatch(struct svc_req *req, SVCXPRT *trans)
139 {
140 	xdrproc_t		 xdr_argument;
141 	xdrproc_t		 xdr_result;
142 	char			*result;
143 	char			*(*cb)(char *, struct svc_req *);
144         union {
145 		domainname	 ypproc_domain_2_arg;
146 		domainname	 ypproc_domain_nonack_2_arg;
147 		ypreq_key	 ypproc_match_2_arg;
148 		ypreq_nokey	 ypproc_first_2_arg;
149 		ypreq_key	 ypproc_next_2_arg;
150 		ypreq_xfr	 ypproc_xfr_2_arg;
151 		ypreq_nokey	 ypproc_all_2_arg;
152 		ypreq_nokey	 ypproc_master_2_arg;
153 		ypreq_nokey	 ypproc_order_2_arg;
154 		domainname	 ypproc_maplist_2_arg;
155 	} argument;
156 
157 	xdr_argument = (xdrproc_t) xdr_void;
158 	xdr_result = (xdrproc_t) xdr_void;
159 	cb = NULL;
160 	switch (req->rq_proc) {
161 	case YPPROC_NULL:
162 		xdr_argument = (xdrproc_t) xdr_void;
163 		xdr_result = (xdrproc_t) xdr_void;
164 		if (yp_check(req) == -1)
165 			return;
166 		result = NULL;
167 		if (!svc_sendreply(trans, (xdrproc_t) xdr_void,
168 		    (void *)&result))
169 			svcerr_systemerr(trans);
170 		return;
171 	case YPPROC_DOMAIN:
172 		xdr_argument = (xdrproc_t) xdr_domainname;
173 		xdr_result = (xdrproc_t) xdr_bool;
174 		if (yp_check(req) == -1)
175 			return;
176 		cb = (void *)ypproc_domain_2_svc;
177 		break;
178 	case YPPROC_DOMAIN_NONACK:
179 		xdr_argument = (xdrproc_t) xdr_domainname;
180 		xdr_result = (xdrproc_t) xdr_bool;
181 		if (yp_check(req) == -1)
182 			return;
183 		cb = (void *)ypproc_domain_nonack_2_svc;
184 		break;
185 	case YPPROC_MATCH:
186 		xdr_argument = (xdrproc_t) xdr_ypreq_key;
187 		xdr_result = (xdrproc_t) xdr_ypresp_val;
188 		if (yp_check(req) == -1)
189 			return;
190 		cb = (void *)ypproc_match_2_svc;
191 		break;
192 	case YPPROC_FIRST:
193 		xdr_argument = (xdrproc_t) xdr_ypreq_nokey;
194 		xdr_result = (xdrproc_t) xdr_ypresp_key_val;
195 		if (yp_check(req) == -1)
196 			return;
197 		cb = (void *)ypproc_first_2_svc;
198 		break;
199 	case YPPROC_NEXT:
200 		xdr_argument = (xdrproc_t) xdr_ypreq_key;
201 		xdr_result = (xdrproc_t) xdr_ypresp_key_val;
202 		if (yp_check(req) == -1)
203 			return;
204 		cb = (void *)ypproc_next_2_svc;
205 		break;
206 	case YPPROC_XFR:
207 		if (yp_check(req) == -1)
208 			return;
209 		svcerr_noproc(trans);
210 		return;
211 	case YPPROC_CLEAR:
212 		log_debug("ypproc_clear");
213 		if (yp_check(req) == -1)
214 			return;
215 		svcerr_noproc(trans);
216 		return;
217 	case YPPROC_ALL:
218 		log_debug("ypproc_all");
219 		xdr_argument = (xdrproc_t) xdr_ypreq_nokey;
220 		xdr_result = (xdrproc_t) xdr_ypresp_all;
221 		if (yp_check(req) == -1)
222 			return;
223 		cb = (void *)ypproc_all_2_svc;
224 		break;
225 	case YPPROC_MASTER:
226 		log_debug("ypproc_master");
227 		xdr_argument = (xdrproc_t) xdr_ypreq_nokey;
228 		xdr_result = (xdrproc_t) xdr_ypresp_master;
229 		if (yp_check(req) == -1)
230 			return;
231 		cb = (void *)ypproc_master_2_svc;
232 		break;
233 	case YPPROC_ORDER:
234 		log_debug("ypproc_order");
235 		if (yp_check(req) == -1)
236 			return;
237 		svcerr_noproc(trans);
238 		return;
239 	case YPPROC_MAPLIST:
240 		log_debug("ypproc_maplist");
241 		xdr_argument = (xdrproc_t) xdr_domainname;
242 		xdr_result = (xdrproc_t) xdr_ypresp_maplist;
243 		if (yp_check(req) == -1)
244 			return;
245 		cb = (void *)ypproc_maplist_2_svc;
246 		break;
247 	default:
248 		svcerr_noproc(trans);
249 		return;
250 	}
251 	(void)memset(&argument, 0, sizeof(argument));
252 
253 	if (!svc_getargs(trans, xdr_argument, (caddr_t)&argument)) {
254 		svcerr_decode(trans);
255 		return;
256 	}
257 	result = (*cb)((char *)&argument, req);
258 	if (result != NULL && !svc_sendreply(trans, xdr_result, result))
259 		svcerr_systemerr(trans);
260 	if (!svc_freeargs(trans, xdr_argument, (caddr_t)&argument)) {
261 		/*
262 		 * ypserv does it too.
263 		 */
264 		fatal("unable to free arguments");
265 	}
266 }
267 
268 int
269 yp_check(struct svc_req *req)
270 {
271 	struct sockaddr_in	*caller;
272 
273 	caller = svc_getcaller(req->rq_xprt);
274 	/*
275 	 * We might want to know who we allow here.
276 	 */
277 	return (0);
278 }
279 
280 int
281 yp_valid_domain(char *domain, struct ypresp_val *res)
282 {
283 	if (domain == NULL) {
284 		log_debug("NULL domain !");
285 		return (-1);
286 	}
287 	if (strcmp(domain, env->sc_domainname) != 0) {
288 		res->stat = YP_NODOM;
289 		return (-1);
290 	}
291 	return (0);
292 }
293 
294 bool_t *
295 ypproc_domain_2_svc(domainname *arg, struct svc_req *req)
296 {
297 	static bool_t	res;
298 
299 	res = (bool_t)1;
300 	if (strcmp(*arg, env->sc_domainname) != 0)
301 		res = (bool_t)0;
302 	return (&res);
303 }
304 
305 bool_t *
306 ypproc_domain_nonack_2_svc(domainname *arg, struct svc_req *req)
307 {
308 	static bool_t	res;
309 
310 	if (strcmp(*arg, env->sc_domainname) != 0)
311 		return NULL;
312 	res = (bool_t)1;
313 	return (&res);
314 }
315 
316 ypresp_val *
317 ypproc_match_2_svc(ypreq_key *arg, struct svc_req *req)
318 {
319 	struct userent		 ukey;
320 	struct userent		*ue;
321 	struct groupent		 gkey;
322 	struct groupent		*ge;
323 	static struct ypresp_val res;
324 	const char		*estr;
325 	char			*bp, *cp;
326 	char			 *key;
327 
328 	log_debug("matching '%.*s' in map %s", arg->key.keydat_len,
329 	   arg->key.keydat_val, arg->map);
330 
331 	if (yp_valid_domain(arg->domain, (struct ypresp_val *)&res) == -1)
332 		return (&res);
333 
334 	if (env->sc_user_names == NULL) {
335 		/*
336 		 * tree not ready.
337 		 */
338 		return (NULL);
339 	}
340 
341 	if (arg->key.keydat_len > YPMAXRECORD) {
342 		log_debug("argument too long");
343 		return (NULL);
344 	}
345 	key = calloc(arg->key.keydat_len + 1, 1);
346 	if (key == NULL)
347 		return (NULL);
348 	(void)strncpy(key, arg->key.keydat_val, arg->key.keydat_len);
349 
350 	if (strcmp(arg->map, "passwd.byname") == 0 ||
351 	    strcmp(arg->map, "master.passwd.byname") == 0) {
352 		ukey.ue_line = key;
353 		if ((ue = RB_FIND(user_name_tree, env->sc_user_names,
354 		    &ukey)) == NULL) {
355 			res.stat = YP_NOKEY;
356 			goto out;
357 		}
358 
359 		yp_make_val(&res, ue->ue_line, 1);
360 		goto out;
361 	} else if (strcmp(arg->map, "passwd.byuid") == 0 ||
362 		   strcmp(arg->map, "master.passwd.byuid") == 0) {
363 		ukey.ue_uid = strtonum(key, 0, UID_MAX, &estr);
364 		if (estr) {
365 			res.stat = YP_BADARGS;
366 			goto out;
367 		}
368 
369 		if ((ue = RB_FIND(user_uid_tree, &env->sc_user_uids,
370 		    &ukey)) == NULL) {
371 			res.stat = YP_NOKEY;
372 			goto out;
373 		}
374 
375 		yp_make_val(&res, ue->ue_line, 1);
376 		return (&res);
377 	} else if (strcmp(arg->map, "group.bygid") == 0) {
378 		gkey.ge_gid = strtonum(key, 0, GID_MAX, &estr);
379 		if (estr) {
380 			res.stat = YP_BADARGS;
381 			goto out;
382 		}
383 		if ((ge = RB_FIND(group_gid_tree, &env->sc_group_gids,
384 		    &gkey)) == NULL) {
385 			res.stat = YP_NOKEY;
386 			goto out;
387 		}
388 
389 		yp_make_val(&res, ge->ge_line, 1);
390 		return (&res);
391 	} else if (strcmp(arg->map, "group.byname") == 0) {
392 		gkey.ge_line = key;
393 		if ((ge = RB_FIND(group_name_tree, env->sc_group_names,
394 		    &gkey)) == NULL) {
395 			res.stat = YP_NOKEY;
396 			goto out;
397 		}
398 
399 		yp_make_val(&res, ge->ge_line, 1);
400 		return (&res);
401 	} else if (strcmp(arg->map, "netid.byname") == 0) {
402 		bp = cp = key;
403 
404 		if (strncmp(bp, "unix.", strlen("unix.")) != 0) {
405 			res.stat = YP_BADARGS;
406 			goto out;
407 		}
408 
409 		bp += strlen("unix.");
410 
411 		if (*bp == '\0') {
412 			res.stat = YP_BADARGS;
413 			goto out;
414 		}
415 
416 		if (!(cp = strsep(&bp, "@"))) {
417 			res.stat = YP_BADARGS;
418 			goto out;
419 		}
420 
421 		if (strcmp(bp, arg->domain) != 0) {
422 			res.stat = YP_BADARGS;
423 			goto out;
424 		}
425 
426 		ukey.ue_uid = strtonum(cp, 0, UID_MAX, &estr);
427 		if (estr) {
428 			res.stat = YP_BADARGS;
429 			goto out;
430 		}
431 
432 		if ((ue = RB_FIND(user_uid_tree, &env->sc_user_uids,
433 		    &ukey)) == NULL) {
434 			res.stat = YP_NOKEY;
435 			goto out;
436 		}
437 
438 		yp_make_val(&res, ue->ue_netid_line, 0);
439 		goto out;
440 
441 	} else {
442 		log_debug("unknown map %s", arg->map);
443 		res.stat = YP_NOMAP;
444 		goto out;
445 	}
446 out:
447 	free(key);
448 	return (&res);
449 }
450 
451 ypresp_key_val *
452 ypproc_first_2_svc(ypreq_nokey *arg, struct svc_req *req)
453 {
454 	static struct ypresp_key_val	res;
455 
456 	if (yp_valid_domain(arg->domain, (struct ypresp_val *)&res) == -1)
457 		return (&res);
458 
459 	if (strcmp(arg->map, "passwd.byname") == 0 ||
460 	    strcmp(arg->map, "master.passwd.byname") == 0) {
461 		if (env->sc_user_lines == NULL)
462 			return (NULL);
463 
464 		yp_make_keyval(&res, env->sc_user_lines, env->sc_user_lines);
465 	} else if (strcmp(arg->map, "group.byname") == 0) {
466 		if (env->sc_group_lines == NULL)
467 			return (NULL);
468 
469 		yp_make_keyval(&res, env->sc_group_lines, env->sc_group_lines);
470 	} else {
471 		log_debug("unknown map %s", arg->map);
472 		res.stat = YP_NOMAP;
473 	}
474 
475 	return (&res);
476 }
477 
478 ypresp_key_val *
479 ypproc_next_2_svc(ypreq_key *arg, struct svc_req *req)
480 {
481 	struct userent			 ukey;
482 	struct userent			*ue;
483 	struct groupent			 gkey;
484 	struct groupent			*ge;
485 	char				*line;
486 	static struct ypresp_key_val	 res;
487 	char				 *key;
488 
489 	if (yp_valid_domain(arg->domain, (struct ypresp_val *)&res) == -1)
490 		return (&res);
491 
492 	key = NULL;
493 	if (strcmp(arg->map, "passwd.byname") == 0 ||
494 	    strcmp(arg->map, "master.passwd.byname") == 0) {
495 		key = calloc(arg->key.keydat_len + 1, 1);
496 		if (key == NULL) {
497 			res.stat = YP_YPERR;
498 			return (&res);
499 		}
500 		(void)strncpy(key, arg->key.keydat_val,
501 		    arg->key.keydat_len);
502 		ukey.ue_line = key;
503 		if ((ue = RB_FIND(user_name_tree, env->sc_user_names,
504 		    &ukey)) == NULL) {
505 			/*
506 			 * canacar's trick:
507 			 * the user might have been deleted in between calls
508 			 * to next since the tree may be modified by a reload.
509 			 * next should still return the next user in
510 			 * lexicographical order, hence insert the search key
511 			 * and look up the next field, then remove it again.
512 			 */
513 			RB_INSERT(user_name_tree, env->sc_user_names, &ukey);
514 			if ((ue = RB_NEXT(user_name_tree, &env->sc_user_names,
515 			    &ukey)) == NULL) {
516 				RB_REMOVE(user_name_tree, env->sc_user_names,
517 				    &ukey);
518 				res.stat = YP_NOKEY;
519 				free(key);
520 				return (&res);
521 			}
522 			RB_REMOVE(user_name_tree, env->sc_user_names, &ukey);
523 		}
524 		line = ue->ue_line + (strlen(ue->ue_line) + 1);
525 		line = line + (strlen(line) + 1);
526 		yp_make_keyval(&res, line, line);
527 		free(key);
528 		return (&res);
529 
530 
531 	} else if (strcmp(arg->map, "group.byname") == 0) {
532 		key = calloc(arg->key.keydat_len + 1, 1);
533 		if (key == NULL) {
534 			res.stat = YP_YPERR;
535 			return (&res);
536 		}
537 		(void)strncpy(key, arg->key.keydat_val,
538 		    arg->key.keydat_len);
539 
540 		gkey.ge_line = key;
541 		if ((ge = RB_FIND(group_name_tree, env->sc_group_names,
542 		    &gkey)) == NULL) {
543 			/*
544 			 * canacar's trick reloaded.
545 			 */
546 			RB_INSERT(group_name_tree, env->sc_group_names, &gkey);
547 			if ((ge = RB_NEXT(group_name_tree, &env->sc_group_names,
548 			    &gkey)) == NULL) {
549 				RB_REMOVE(group_name_tree, env->sc_group_names,
550 				    &gkey);
551 				res.stat = YP_NOKEY;
552 				free(key);
553 				return (&res);
554 			}
555 			RB_REMOVE(group_name_tree, env->sc_group_names, &gkey);
556 		}
557 
558 		line = ge->ge_line + (strlen(ge->ge_line) + 1);
559 		line = line + (strlen(line) + 1);
560 		yp_make_keyval(&res, line, line);
561 		free(key);
562 		return (&res);
563 	} else {
564 		log_debug("unknown map %s", arg->map);
565 		res.stat = YP_NOMAP;
566 		return (&res);
567 	}
568 }
569 
570 ypresp_all *
571 ypproc_all_2_svc(ypreq_nokey *arg, struct svc_req *req)
572 {
573 	static struct ypresp_all	res;
574 
575 	if (yp_valid_domain(arg->domain, (struct ypresp_val *)&res) == -1)
576 		return (&res);
577 
578 	svcerr_auth(req->rq_xprt, AUTH_FAILED);
579 	return (NULL);
580 }
581 
582 ypresp_master *
583 ypproc_master_2_svc(ypreq_nokey *arg, struct svc_req *req)
584 {
585 	static struct ypresp_master	 res;
586 	static char master[YPMAXPEER + 1];
587 
588 	memset(&res, 0, sizeof(res));
589 	if (yp_valid_domain(arg->domain, (struct ypresp_val *)&res) == -1)
590 		return (&res);
591 
592 	if (gethostname(master, sizeof(master)) == 0) {
593 		res.peer = (peername)master;
594 		res.stat = YP_TRUE;
595 	} else
596 		res.stat = YP_NOKEY;
597 
598 	return (&res);
599 }
600 
601 ypresp_maplist *
602 ypproc_maplist_2_svc(domainname *arg, struct svc_req *req)
603 {
604 	size_t			 i;
605 	static struct {
606 		char		*name;
607 		int		 cond;
608 	}			 mapnames[] = {
609 		{ "passwd.byname",		YPMAP_PASSWD_BYNAME },
610 		{ "passwd.byuid",		YPMAP_PASSWD_BYUID },
611 		{ "master.passwd.byname",	YPMAP_MASTER_PASSWD_BYNAME },
612 		{ "master.passwd.byuid",	YPMAP_MASTER_PASSWD_BYUID },
613 		{ "group.byname",		YPMAP_GROUP_BYNAME },
614 		{ "group.bygid",		YPMAP_GROUP_BYGID },
615 		{ "netid.byname",		YPMAP_NETID_BYNAME },
616 	};
617 	static ypresp_maplist	 res;
618 	static struct ypmaplist	 maps[nitems(mapnames)];
619 
620 	if (yp_valid_domain(*arg, (struct ypresp_val *)&res) == -1)
621 		return (&res);
622 
623 	res.stat = YP_TRUE;
624 	res.maps = NULL;
625 	for (i = 0; i < nitems(mapnames); i++) {
626 		if (!(env->sc_flags & mapnames[i].cond))
627 			continue;
628 		maps[i].map = mapnames[i].name;
629 		maps[i].next = res.maps;
630 		res.maps = &maps[i];
631 	}
632 
633 	return (&res);
634 }
635 
636 void
637 yp_make_val(struct ypresp_val *res, char *line, int replacecolon)
638 {
639 	static char		 buf[LINE_WIDTH];
640 
641 	memset(buf, 0, sizeof(buf));
642 
643 	if (replacecolon)
644 		line[strlen(line)] = ':';
645 	(void)strlcpy(buf, line, sizeof(buf));
646 	if (replacecolon)
647 		line[strcspn(line, ":")] = '\0';
648 	log_debug("sending out %s", buf);
649 
650 	res->stat = YP_TRUE;
651 	res->val.valdat_len = strlen(buf);
652 	res->val.valdat_val = buf;
653 }
654 
655 void
656 yp_make_keyval(struct ypresp_key_val *res, char *key, char *line)
657 {
658 	static char	keybuf[YPMAXRECORD+1];
659 	static char	buf[LINE_WIDTH];
660 
661 	memset(keybuf, 0, sizeof(keybuf));
662 	memset(buf, 0, sizeof(buf));
663 
664 	(void)strlcpy(keybuf, key, sizeof(keybuf));
665 	res->key.keydat_len = strlen(keybuf);
666 	res->key.keydat_val = keybuf;
667 
668 	if (*line == '\0') {
669 		res->stat = YP_NOMORE;
670 		return;
671 	}
672 	res->stat = YP_TRUE;
673 	line[strlen(line)] = ':';
674 	(void)strlcpy(buf, line, sizeof(buf));
675 	line[strcspn(line, ":")] = '\0';
676 	log_debug("sending out %s => %s", keybuf, buf);
677 
678 	res->val.valdat_len = strlen(buf);
679 	res->val.valdat_val = buf;
680 }
681