xref: /titanic_41/usr/src/lib/nsswitch/nis/common/nis_common.c (revision 2b4a78020b9c38d1b95e2f3fefa6d6e4be382d1f)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 
22 /*
23  * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 /*
28  * nis_common.c
29  *
30  * Common code and structures used by name-service-switch "nis" backends.
31  */
32 
33 #include "nis_common.h"
34 #include <string.h>
35 #include <synch.h>
36 #include <rpcsvc/ypclnt.h>
37 #include <rpcsvc/yp_prot.h>
38 #include <thread.h>
39 #include <ctype.h>
40 #include <stdlib.h>
41 #include <signal.h>
42 
43 #ifndef	MT_UNSAFE_YP		/* Is the libnsl YP client code MT-unsafe? */
44 #define	MT_UNSAFE_YP	0	/* No, not any longer */
45 #endif
46 
47 #if	MT_UNSAFE_YP
48 static mutex_t	one_lane = DEFAULTMUTEX;
49 #endif
50 
51 /* <rpcsvc/ypclnt.h> uses (char *) where it should use (const char *) */
52 typedef char *grrr;
53 
54 /*
55  * The YP client code thinks it's being helpful by appending '\n' and '\0'
56  *   to the values returned by yp_match() et al.  In order to do this it
57  *   ends up doing more malloc()ing and data copying than would otherwise
58  *   be necessary.  If we're interested in performance we should provide
59  *   alternative library interfaces that skip the helpfulness and instead
60  *   let the XDR routines dump the value directly into the buffer where
61  *   we really want it.  For now, though, we just use the vanilla interface.
62  */
63 
64 static nss_status_t
switch_err(ypstatus,ismatch)65 switch_err(ypstatus, ismatch)
66 	int			ypstatus;
67 	int			ismatch;
68 {
69 	switch (ypstatus) {
70 	case 0:
71 		errno = 0;
72 		return (NSS_SUCCESS);
73 
74 	case YPERR_BADARGS:
75 	case YPERR_KEY:
76 		errno = 0;
77 		return (NSS_NOTFOUND);
78 
79 		/*
80 		 *  When the YP server is running in DNS forwarding mode,
81 		 *  the forwarder will return YPERR_NOMORE to us if it
82 		 *  is unable to contact a server (i.e., it has timed out).
83 		 *  The NSS_NISSERVDNS_TRYAGAIN is returned for timeout errors.
84 		 */
85 	case YPERR_NOMORE:
86 		if (ismatch)
87 			return (NSS_NISSERVDNS_TRYAGAIN);
88 		else
89 			return (NSS_NOTFOUND);
90 
91 	case YPERR_DOMAIN:
92 	case YPERR_YPSERV:
93 	case YPERR_BUSY:
94 		return (NSS_TRYAGAIN);
95 
96 	default:
97 		return (NSS_UNAVAIL);
98 	}
99 }
100 
101 /*ARGSUSED*/
102 nss_status_t
_nss_nis_setent(be,dummy)103 _nss_nis_setent(be, dummy)
104 	nis_backend_ptr_t	be;
105 	void			*dummy;
106 {
107 	if (be->enum_key != 0) {
108 		free(be->enum_key);
109 		be->enum_key = 0;
110 	}
111 	be->enum_keylen = 0;
112 	return (NSS_SUCCESS);
113 }
114 
115 nss_status_t
_nss_nis_endent(be,dummy)116 _nss_nis_endent(be, dummy)
117 	nis_backend_ptr_t	be;
118 	void			*dummy;
119 {
120 	return (_nss_nis_setent(be, dummy));
121 	/* Nothing else we can clean up, is there? */
122 }
123 
124 void
massage_netdb(const char ** valp,int * vallenp)125 massage_netdb(const char **valp, int *vallenp)
126 {
127 	const char		*first;
128 	const char		*last;
129 	const char		*val	= *valp;
130 	int			vallen	= *vallenp;
131 
132 	if ((last = memchr(val, '#', vallen)) == 0) {
133 		last = val + vallen;
134 	}
135 	for (first = val;  first < last && isspace(*first);  first++) {
136 		;
137 	}
138 	for (/* cstyle */;  first < last && isspace(last[-1]);  last--) {
139 		;
140 	}
141 	/*
142 	 * Don't check for an empty line because it shouldn't ever
143 	 *   have made it into the YP map.
144 	 */
145 	*valp = first;
146 	*vallenp = (int)(last - first);
147 }
148 
149 nss_status_t
_nss_nis_ypmatch(domain,map,key,valp,vallenp,ypstatusp)150 _nss_nis_ypmatch(domain, map, key, valp, vallenp, ypstatusp)
151 	const char		*domain;
152 	const char		*map;
153 	const char		*key;
154 	char			**valp;
155 	int			*vallenp;
156 	int			*ypstatusp;
157 {
158 	int			ypstatus;
159 
160 #if	MT_UNSAFE_YP
161 	sigset_t		oldmask, newmask;
162 
163 	(void) sigfillset(&newmask);
164 	(void) thr_sigsetmask(SIG_SETMASK, &newmask, &oldmask);
165 	(void) mutex_lock(&one_lane);
166 #endif
167 	ypstatus = __yp_match_cflookup((grrr)domain, (grrr)map,
168 			    (grrr)key, (int)strlen(key), valp, vallenp, 0);
169 #if	MT_UNSAFE_YP
170 	(void) mutex_unlock(&one_lane);
171 	(void) thr_sigsetmask(SIG_SETMASK, &oldmask, NULL);
172 #endif
173 
174 	if (ypstatusp != 0) {
175 		*ypstatusp = ypstatus;
176 	}
177 	return (switch_err(ypstatus, 1));
178 }
179 
180 /*
181  * XXX special version of _nss_nis_ypmatch() for handling C2 (passwd.adjunct)
182  * lookups when we need a reserved port.
183  */
184 
185 static nss_status_t
_nss_nis_ypmatch_rsvdport(domain,map,key,valp,vallenp,ypstatusp)186 _nss_nis_ypmatch_rsvdport(domain, map, key, valp, vallenp, ypstatusp)
187 	const char		*domain;
188 	const char		*map;
189 	const char		*key;
190 	char			**valp;
191 	int			*vallenp;
192 	int			*ypstatusp;
193 {
194 	int			ypstatus;
195 
196 #if	MT_UNSAFE_YP
197 	sigset_t		oldmask, newmask;
198 
199 	(void) sigfillset(&newmask);
200 	(void) thr_sigsetmask(SIG_SETMASK, &newmask, &oldmask);
201 	(void) mutex_lock(&one_lane);
202 #endif
203 	ypstatus = __yp_match_rsvdport_cflookup((grrr)domain, (grrr)map,
204 			    (grrr)key, strlen(key), valp, vallenp, 0);
205 #if	MT_UNSAFE_YP
206 	(void) mutex_unlock(&one_lane);
207 	(void) thr_sigsetmask(SIG_SETMASK, &oldmask, NULL);
208 #endif
209 
210 	if (ypstatusp != 0) {
211 		*ypstatusp = ypstatus;
212 	}
213 	return (switch_err(ypstatus, 1));
214 }
215 
216 nss_status_t
_nss_nis_lookup(be,args,netdb,map,key,ypstatusp)217 _nss_nis_lookup(be, args, netdb, map, key, ypstatusp)
218 	nis_backend_ptr_t	be;
219 	nss_XbyY_args_t		*args;
220 	int			netdb;
221 	const char		*map;
222 	const char		*key;
223 	int			*ypstatusp;
224 {
225 	nss_status_t		res;
226 	int			vallen;
227 	char			*val;
228 	char			*free_ptr;
229 	int			parsestat;
230 
231 	if ((res = _nss_nis_ypmatch(be->domain, map, key, &val, &vallen,
232 				    ypstatusp)) != NSS_SUCCESS) {
233 		return (res);
234 	}
235 
236 	parsestat = NSS_STR_PARSE_SUCCESS;
237 	if (strcmp(map, "passwd.byname") == 0 ||
238 	    strcmp(map, "passwd.byuid") == 0) {
239 		parsestat = validate_passwd_ids(&val, &vallen, 1);
240 	} else if (strcmp(map, "group.byname") == 0)
241 		parsestat = validate_group_ids(&val, &vallen, 1);
242 	if (parsestat != NSS_STR_PARSE_SUCCESS) {
243 		free(val);
244 		return (NSS_NOTFOUND);
245 	}
246 
247 	free_ptr = val;
248 
249 	if (netdb) {
250 		massage_netdb((const char **)&val, &vallen);
251 	}
252 
253 	args->returnval = NULL;
254 	args->returnlen = 0;
255 	parsestat = (*args->str2ent)(val, vallen,
256 			args->buf.result, args->buf.buffer, args->buf.buflen);
257 	if (parsestat == NSS_STR_PARSE_SUCCESS) {
258 		args->returnval = args->buf.result;
259 		args->returnlen = vallen;
260 		res = NSS_SUCCESS;
261 	} else if (parsestat == NSS_STR_PARSE_ERANGE) {
262 		args->erange = 1;
263 		/* We won't find this otherwise, anyway */
264 		res = NSS_NOTFOUND;
265 	} /* else if (parsestat == NSS_STR_PARSE_PARSE) won't happen ! */
266 
267 	free(free_ptr);
268 
269 	return (res);
270 }
271 
272 nss_status_t
_nss_nis_lookup_rsvdport(be,args,netdb,map,key,ypstatusp)273 _nss_nis_lookup_rsvdport(be, args, netdb, map, key, ypstatusp)
274 	nis_backend_ptr_t	be;
275 	nss_XbyY_args_t		*args;
276 	int			netdb;
277 	const char		*map;
278 	const char		*key;
279 	int			*ypstatusp;
280 {
281 	nss_status_t		res;
282 	int			vallen;
283 	char			*val;
284 	char			*free_ptr;
285 	int			parsestat;
286 
287 	if ((res = _nss_nis_ypmatch_rsvdport(be->domain, map, key, &val,
288 				    &vallen, ypstatusp)) != NSS_SUCCESS) {
289 		return (res);
290 	}
291 
292 	free_ptr = val;
293 
294 	if (netdb) {
295 		massage_netdb((const char **)&val, &vallen);
296 	}
297 
298 	args->returnval = NULL;
299 	args->returnlen = 0;
300 	parsestat = (*args->str2ent)(val, vallen,
301 			args->buf.result, args->buf.buffer, args->buf.buflen);
302 	if (parsestat == NSS_STR_PARSE_SUCCESS) {
303 		args->returnval = args->buf.result;
304 		args->returnlen = vallen;
305 		res = NSS_SUCCESS;
306 	} else if (parsestat == NSS_STR_PARSE_ERANGE) {
307 		args->erange = 1;
308 		/* We won't find this otherwise, anyway */
309 		res = NSS_NOTFOUND;
310 	} /* else if (parsestat == NSS_STR_PARSE_PARSE) won't happen ! */
311 
312 	free(free_ptr);
313 
314 	return (res);
315 }
316 
317 static nss_status_t
do_getent(be,args,netdb)318 do_getent(be, args, netdb)
319 	nis_backend_ptr_t	be;
320 	nss_XbyY_args_t		*args;
321 	int			netdb;
322 {
323 	nss_status_t		res;
324 	int			ypstatus;
325 	int			outkeylen, outvallen;
326 	char			*outkey, *outval;
327 	char			*free_ptr;
328 	int			parsestat;
329 
330 #if	MT_UNSAFE_YP
331 	sigset_t		oldmask, newmask;
332 
333 	(void) sigfillset(&newmask);
334 	(void) thr_sigsetmask(SIG_SETMASK, &newmask, &oldmask);
335 	(void) mutex_lock(&one_lane);
336 #endif
337 	if (be->enum_key == 0) {
338 		ypstatus = __yp_first_cflookup((grrr)be->domain,
339 					    (grrr)be->enum_map, &outkey,
340 					    &outkeylen, &outval,
341 					    &outvallen, 0);
342 	} else {
343 		ypstatus = __yp_next_cflookup((grrr)be->domain,
344 					    (grrr)be->enum_map, be->enum_key,
345 					    be->enum_keylen, &outkey,
346 					    &outkeylen, &outval,
347 					    &outvallen, 0);
348 	}
349 #if	MT_UNSAFE_YP
350 	(void) mutex_unlock(&one_lane);
351 	(void) thr_sigsetmask(SIG_SETMASK, &oldmask, NULL);
352 #endif
353 
354 	if ((res = switch_err(ypstatus, 0)) != NSS_SUCCESS) {
355 		return (res);
356 	}
357 
358 	free_ptr = outval;
359 
360 	if (netdb) {
361 		massage_netdb((const char **)&outval, &outvallen);
362 	}
363 
364 	args->returnval = NULL;
365 	args->returnlen = 0;
366 	parsestat = (*args->str2ent)(outval, outvallen,
367 			args->buf.result, args->buf.buffer, args->buf.buflen);
368 	if (parsestat == NSS_STR_PARSE_SUCCESS) {
369 		args->returnval = args->buf.result;
370 		args->returnlen = outvallen;
371 		res = NSS_SUCCESS;
372 	} else if (parsestat == NSS_STR_PARSE_ERANGE) {
373 		args->erange = 1;
374 		/* We won't find this otherwise, anyway */
375 		res = NSS_NOTFOUND;
376 	} /* else if (parsestat == NSS_STR_PARSE_PARSE) won't happen ! */
377 
378 	free(free_ptr);
379 
380 	if (be->enum_key != 0) {
381 		free(be->enum_key);
382 	}
383 	be->enum_key = outkey;
384 	be->enum_keylen = outkeylen;
385 
386 	return (res);
387 }
388 
389 nss_status_t
_nss_nis_getent_rigid(be,args)390 _nss_nis_getent_rigid(be, args)
391 	nis_backend_ptr_t	be;
392 	void			*args;
393 {
394 	return (do_getent(be, (nss_XbyY_args_t *)args, 0));
395 }
396 
397 nss_status_t
_nss_nis_getent_netdb(be,args)398 _nss_nis_getent_netdb(be, args)
399 	nis_backend_ptr_t	be;
400 	void			*args;
401 {
402 	return (do_getent(be, (nss_XbyY_args_t *)args, 1));
403 }
404 
405 
406 struct cb_data {
407 	void			*args;
408 	const char		*filter;
409 	nis_do_all_func_t	func;
410 	nss_status_t		result;
411 };
412 
413 enum { ITER_NEXT = 0, ITER_STOP = 1 };	/* Should be in <rpcsvc/ypclnt.h> */
414 
415 /*ARGSUSED*/
416 static int
do_cback(instatus,inkey,inkeylen,inval,invallen,indata)417 do_cback(instatus, inkey, inkeylen, inval, invallen, indata)
418 	int			instatus;
419 	const char		*inkey;
420 	int			inkeylen;
421 	const char		*inval;
422 	int			invallen;
423 	struct cb_data		*indata;
424 {
425 	nss_status_t		res;
426 
427 	if (instatus != YP_TRUE) {
428 		return (ITER_NEXT);	/* yp_all may decide otherwise... */
429 	}
430 
431 	if (indata->filter != 0 && strstr(inval, indata->filter) == 0) {
432 		/*
433 		 * Optimization:  if the entry doesn't contain the filter
434 		 *   string then it can't be the entry we want, so don't
435 		 *   bother looking more closely at it.
436 		 */
437 		return (ITER_NEXT);
438 	}
439 
440 	res = (*indata->func)(inval, invallen, indata->args);
441 
442 	if (res == NSS_NOTFOUND) {
443 		return (ITER_NEXT);
444 	} else {
445 		indata->result = res;
446 		return (ITER_STOP);
447 	}
448 }
449 
450 nss_status_t
_nss_nis_do_all(be,args,filter,func)451 _nss_nis_do_all(be, args, filter, func)
452 	nis_backend_ptr_t	be;
453 	void			*args;
454 	const char		*filter;
455 	nis_do_all_func_t	func;
456 {
457 	int			ypall_status;
458 	struct cb_data		data;
459 	struct ypall_callback	cback;
460 
461 	data.args	= args;
462 	data.filter	= filter;
463 	data.func	= func;
464 	data.result	= NSS_NOTFOUND;
465 
466 	cback.foreach	= do_cback;
467 	cback.data	= (char *)&data;
468 
469 #if	MT_UNSAFE_YP
470 	sigset_t		oldmask, newmask;
471 
472 	(void) sigfillset(&newmask);
473 	(void) thr_sigsetmask(SIG_SETMASK, &newmask, &oldmask);
474 	(void) mutex_lock(&one_lane);
475 #endif
476 	ypall_status = __yp_all_cflookup((grrr)be->domain,
477 			(grrr) be->enum_map, &cback, 0);
478 #if	MT_UNSAFE_YP
479 	(void) mutex_unlock(&one_lane);
480 	(void) thr_sigsetmask(SIG_SETMASK, &oldmask, NULL);
481 #endif
482 
483 	switch (ypall_status) {
484 	    case 0:
485 		return (data.result);
486 	    case YPERR_DOMAIN:
487 	    case YPERR_YPSERV:
488 	    case YPERR_BUSY:		/* Probably never get this, but... */
489 		return (NSS_TRYAGAIN);
490 	    default:
491 		return (NSS_UNAVAIL);
492 	}
493 }
494 
495 struct XbyY_data {
496 	nss_XbyY_args_t		*args;
497 	nis_XY_check_func	func;
498 	int			netdb;
499 };
500 
501 static nss_status_t
XbyY_iterator(instr,instr_len,a)502 XbyY_iterator(instr, instr_len, a)
503 	const char		*instr;
504 	int			instr_len;
505 	void			*a;
506 {
507 	struct XbyY_data	*xydata	= (struct XbyY_data *)a;
508 	nss_XbyY_args_t		*args	= xydata->args;
509 	nss_status_t		res;
510 	int			parsestat;
511 
512 	if (xydata->netdb) {
513 		massage_netdb(&instr, &instr_len);
514 	}
515 
516 	args->returnval = NULL;
517 	args->returnlen = 0;
518 	parsestat = (*args->str2ent)(instr, instr_len,
519 			args->buf.result, args->buf.buffer, args->buf.buflen);
520 	if (parsestat == NSS_STR_PARSE_SUCCESS) {
521 		args->returnval = args->buf.result;
522 		if ((*xydata->func)(args)) {
523 			res = NSS_SUCCESS;
524 			args->returnlen = instr_len;
525 		} else {
526 			res = NSS_NOTFOUND;
527 			args->returnval = 0;
528 		}
529 	} else if (parsestat == NSS_STR_PARSE_ERANGE) {
530 		/*
531 		 * If we got here because (*str2ent)() found that the buffer
532 		 * wasn't big enough, maybe we should quit and return erange.
533 		 * Instead we'll keep looking and eventually return "not
534 		 * found" -- it's a bug, but not an earth-shattering one.
535 		 */
536 		args->erange = 1;	/* <== Is this a good idea? */
537 		res = NSS_NOTFOUND;
538 	} /* else if (parsestat == NSS_STR_PARSE_PARSE) won't happen ! */
539 
540 	return (res);
541 }
542 
543 nss_status_t
_nss_nis_XY_all(be,args,netdb,filter,func)544 _nss_nis_XY_all(be, args, netdb, filter, func)
545 	nis_backend_ptr_t	be;
546 	nss_XbyY_args_t		*args;
547 	int			netdb;
548 	const char		*filter;
549 	nis_XY_check_func	func;
550 {
551 	struct XbyY_data	data;
552 
553 	data.args = args;
554 	data.func = func;
555 	data.netdb = netdb;
556 
557 	return (_nss_nis_do_all(be, &data, filter, XbyY_iterator));
558 	/* Now how many levels of callbacks was that? */
559 }
560 
561 
562 /*ARGSUSED*/
563 nss_status_t
_nss_nis_destr(be,dummy)564 _nss_nis_destr(be, dummy)
565 	nis_backend_ptr_t	be;
566 	void			*dummy;
567 {
568 	if (be != 0) {
569 		/* === Should change to invoke ops[ENDENT] ? */
570 		(void) _nss_nis_endent(be, 0);
571 		free(be);
572 	}
573 	return (NSS_SUCCESS);	/* In case anyone is dumb enough to check */
574 }
575 
576 /* We want to lock this even if the YP routines are MT-safe */
577 static mutex_t	yp_domain_lock = DEFAULTMUTEX;
578 static char	*yp_domain;
579 
580 const char *
_nss_nis_domain()581 _nss_nis_domain()
582 {
583 	char			*domain;
584 
585 	/*
586 	 * This much locking is probably more "by the book" than necessary...
587 	 */
588 	sigset_t		oldmask, newmask;
589 
590 	(void) sigfillset(&newmask);
591 	(void) thr_sigsetmask(SIG_SETMASK, &newmask, &oldmask);
592 	(void) mutex_lock(&yp_domain_lock);
593 
594 	if ((domain = yp_domain) == 0) {
595 #if	MT_UNSAFE_YP
596 		(void) mutex_lock(&one_lane);
597 #endif
598 		if (yp_get_default_domain(&yp_domain) == 0) {
599 			domain = yp_domain;
600 		}
601 #if	MT_UNSAFE_YP
602 		(void) mutex_unlock(&one_lane);
603 #endif
604 	}
605 
606 	(void) mutex_unlock(&yp_domain_lock);
607 	(void) thr_sigsetmask(SIG_SETMASK, &oldmask, NULL);
608 
609 	return (domain);
610 }
611 
612 nss_backend_t *
_nss_nis_constr(ops,n_ops,enum_map)613 _nss_nis_constr(ops, n_ops, enum_map)
614 	nis_backend_op_t	ops[];
615 	int			n_ops;
616 	const char		*enum_map;
617 {
618 	const char		*domain;
619 	nis_backend_ptr_t	be;
620 
621 	if ((domain = _nss_nis_domain()) == 0 ||
622 	    (be = (nis_backend_ptr_t)malloc(sizeof (*be))) == 0) {
623 		return (0);
624 	}
625 	be->ops		= ops;
626 	be->n_ops	= n_ops;
627 	be->domain	= domain;
628 	be->enum_map	= enum_map;   /* Don't strdup, assume valid forever */
629 	be->enum_key	= 0;
630 	be->enum_keylen	= 0;
631 
632 	return ((nss_backend_t *)be);
633 }
634 
635 /*
636  * This routine is used to parse lines of the form:
637  * 	name number aliases
638  * It returns 1 if the key in argp matches any one of the
639  * names in the line, otherwise 0
640  * Used by rpc
641  */
642 int
_nss_nis_check_name_aliases(nss_XbyY_args_t * argp,const char * line,int linelen)643 _nss_nis_check_name_aliases(nss_XbyY_args_t *argp, const char *line,
644 	int linelen)
645 {
646 	const char	*limit, *linep, *keyp;
647 
648 	linep = line;
649 	limit = line + linelen;
650 	keyp = argp->key.name;
651 
652 	/* compare name */
653 	while (*keyp && linep < limit && !isspace(*linep) && *keyp == *linep) {
654 		keyp++;
655 		linep++;
656 	}
657 	if (*keyp == '\0' && linep < limit && isspace(*linep))
658 		return (1);
659 	/* skip remainder of the name, if any */
660 	while (linep < limit && !isspace(*linep))
661 		linep++;
662 	/* skip the delimiting spaces */
663 	while (linep < limit && isspace(*linep))
664 		linep++;
665 	/* compare with the aliases */
666 	while (linep < limit) {
667 		/*
668 		 * 1st pass: skip number
669 		 * Other passes: skip remainder of the alias name, if any
670 		 */
671 		while (linep < limit && !isspace(*linep))
672 			linep++;
673 		/* skip the delimiting spaces */
674 		while (linep < limit && isspace(*linep))
675 			linep++;
676 		/* compare with the alias name */
677 		keyp = argp->key.name;
678 		while (*keyp && linep < limit && !isspace(*linep) &&
679 		    *keyp == *linep) {
680 			keyp++;
681 			linep++;
682 		}
683 		if (*keyp == '\0' && (linep == limit || isspace(*linep)))
684 			return (1);
685 	}
686 	return (0);
687 }
688