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