xref: /illumos-gate/usr/src/lib/libnsl/rpc/netnamer.c (revision 683007910b81ed9ff942db7c464d1b6df524048a)
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, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 
23 /*
24  * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
25  * Use is subject to license terms.
26  */
27 /* Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T */
28 /* All Rights Reserved */
29 /*
30  * Portions of this source code were derived from Berkeley
31  * 4.3 BSD under license from the Regents of the University of
32  * California.
33  */
34 /*
35  * ==== hack-attack:  possibly MT-safe but definitely not MT-hot.
36  * ==== turn this into a real switch frontend and backends
37  *
38  * Well, at least the API doesn't involve pointers-to-static.
39  */
40 
41 #pragma ident	"%Z%%M%	%I%	%E% SMI"
42 
43 /*
44  * netname utility routines convert from netnames to unix names (uid, gid)
45  *
46  * This module is operating system dependent!
47  * What we define here will work with any unix system that has adopted
48  * the Sun NIS domain architecture.
49  */
50 
51 #undef NIS
52 #include "mt.h"
53 #include "rpc_mt.h"
54 #include <stdio.h>
55 #include <stdlib.h>
56 #include <sys/types.h>
57 #include <ctype.h>
58 #include <grp.h>
59 #include <pwd.h>
60 #include <string.h>
61 #include <syslog.h>
62 #include <sys/param.h>
63 #include <nsswitch.h>
64 #include <rpc/rpc.h>
65 #include <rpcsvc/nis.h>
66 #include <rpcsvc/ypclnt.h>
67 #include "nsl_stdio_prv.h"
68 #include <nss_dbdefs.h>
69 
70 static const char    OPSYS[]	= "unix";
71 static const char    NETIDFILE[] = "/etc/netid";
72 static const char    NETID[]	= "netid.byname";
73 static const char    PKTABLE[]  = "cred.org_dir";
74 #define	PKTABLE_LEN 12
75 #define	OPSYS_LEN 4
76 
77 #ifndef NGROUPS
78 #define	NGROUPS 16
79 #endif
80 
81 extern int _getgroupsbymember(const char *, gid_t[], int, int);
82 
83 /*
84  * the value for NOBODY_UID is set by the SVID. The following define also
85  * appears in netname.c
86  */
87 
88 #define	NOBODY_UID 60001
89 
90 /*
91  *	default publickey policy:
92  *		publickey: nis [NOTFOUND = return] files
93  */
94 
95 
96 /*		NSW_NOTSUCCESS  NSW_NOTFOUND   NSW_UNAVAIL    NSW_TRYAGAIN */
97 #define	DEF_ACTION {__NSW_RETURN, __NSW_RETURN, __NSW_CONTINUE, __NSW_CONTINUE}
98 
99 static struct __nsw_lookup lookup_files = {"files", DEF_ACTION, NULL, NULL},
100 		lookup_nis = {"nis", DEF_ACTION, NULL, &lookup_files};
101 static struct __nsw_switchconfig publickey_default =
102 			{0, "publickey", 2, &lookup_nis};
103 
104 static mutex_t serialize_netname_r = DEFAULTMUTEX;
105 
106 struct netid_userdata {
107 	uid_t	*uidp;
108 	gid_t	*gidp;
109 	int	*gidlenp;
110 	gid_t	*gidlist;
111 };
112 
113 static int
114 parse_uid(char *s, struct netid_userdata *argp)
115 {
116 	uid_t	u;
117 
118 	if (!s || !isdigit(*s)) {
119 		syslog(LOG_ERR,
120 			"netname2user: expecting uid '%s'", s);
121 		return (__NSW_NOTFOUND); /* xxx need a better error */
122 	}
123 
124 	/* Fetch the uid */
125 	u = (uid_t)(atoi(s));
126 
127 	if (u == 0) {
128 		syslog(LOG_ERR, "netname2user: should not have uid 0");
129 		return (__NSW_NOTFOUND);
130 	}
131 	*(argp->uidp) = u;
132 	return (__NSW_SUCCESS);
133 }
134 
135 
136 /* parse a comma separated gid list */
137 static int
138 parse_gidlist(char *p, struct netid_userdata *argp)
139 {
140 	int len;
141 	gid_t	g;
142 
143 	if (!p || (!isdigit(*p))) {
144 		syslog(LOG_ERR,
145 			"netname2user: missing group id list in '%s'.",
146 			p);
147 		return (__NSW_NOTFOUND);
148 	}
149 
150 	g = (gid_t)(atoi(p));
151 	*(argp->gidp) = g;
152 
153 	len = 0;
154 	while (p = strchr(p, ','))
155 		argp->gidlist[len++] = (gid_t)atoi(++p);
156 	*(argp->gidlenp) = len;
157 	return (__NSW_SUCCESS);
158 }
159 
160 
161 /*
162  * parse_netid_str()
163  *
164  * Parse uid and group information from the passed string.
165  *
166  * The format of the string passed is
167  * 	uid:gid,grp,grp, ...
168  *
169  */
170 static int
171 parse_netid_str(char *s, struct netid_userdata *argp)
172 {
173 	char	*p;
174 	int	err;
175 
176 	/* get uid */
177 	err = parse_uid(s, argp);
178 	if (err != __NSW_SUCCESS)
179 		return (err);
180 
181 	/* Now get the group list */
182 	p = strchr(s, ':');
183 	if (!p) {
184 		syslog(LOG_ERR,
185 			"netname2user: missing group id list in '%s'", s);
186 		return (__NSW_NOTFOUND);
187 	}
188 	++p;			/* skip ':' */
189 	err = parse_gidlist(p, argp);
190 	return (err);
191 }
192 
193 static int
194 parse_uid_gidlist(char *ustr, char *gstr, struct netid_userdata *argp)
195 {
196 	int	err;
197 
198 	/* get uid */
199 	err = parse_uid(ustr, argp);
200 	if (err != __NSW_SUCCESS)
201 		return (err);
202 
203 	/* Now get the group list */
204 	return (parse_gidlist(gstr, argp));
205 }
206 
207 
208 /*
209  * netname2user_files()
210  *
211  * This routine fetches the netid information from the "files" nameservice.
212  * ie /etc/netid.
213  */
214 static int
215 netname2user_files(int *err, char *netname, struct netid_userdata *argp)
216 {
217 	char 	buf[512];	/* one line from the file */
218 	char	*name;
219 	char	*value;
220 	char 	*res;
221 	__NSL_FILE *fd;
222 
223 	fd = __nsl_fopen(NETIDFILE, "r");
224 	if (fd == (__NSL_FILE *)0) {
225 		*err = __NSW_UNAVAIL;
226 		return (0);
227 	}
228 	/*
229 	 * for each line in the file parse it appropriately
230 	 * file format is :
231 	 *	netid	uid:grp,grp,grp # for users
232 	 *	netid	0:hostname	# for hosts
233 	 */
234 	while (!__nsl_feof(fd)) {
235 		res = __nsl_fgets(buf, 512, fd);
236 		if (res == NULL)
237 			break;
238 
239 		/* Skip comments and blank lines */
240 		if ((*res == '#') || (*res == '\n'))
241 			continue;
242 
243 		name = &(buf[0]);
244 		while (isspace(*name))
245 			name++;
246 		if (*name == '\0')	/* blank line continue */
247 			continue;
248 		value = name;		/* will contain the value eventually */
249 		while (!isspace(*value))
250 			value++;
251 		if (*value == '\0') {
252 			syslog(LOG_WARNING,
253 				"netname2user: badly formatted line in %s.",
254 				NETIDFILE);
255 			continue;
256 		}
257 		*value++ = '\0'; /* nul terminate the name */
258 
259 		if (strcasecmp(name, netname) == 0) {
260 			(void) __nsl_fclose(fd);
261 			while (isspace(*value))
262 				value++;
263 			*err = parse_netid_str(value, argp);
264 			return (*err == __NSW_SUCCESS);
265 		}
266 	}
267 	(void) __nsl_fclose(fd);
268 	*err = __NSW_NOTFOUND;
269 	return (0);
270 }
271 
272 /*
273  * netname2user_nis()
274  *
275  * This function reads the netid from the NIS (YP) nameservice.
276  */
277 static int
278 netname2user_nis(int *err, char *netname, struct netid_userdata *argp)
279 {
280 	char *domain;
281 	int yperr;
282 	char *lookup;
283 	int len;
284 
285 	domain = strchr(netname, '@');
286 	if (!domain) {
287 		*err = __NSW_UNAVAIL;
288 		return (0);
289 	}
290 
291 	/* Point past the '@' character */
292 	domain++;
293 	lookup = NULL;
294 	yperr = yp_match(domain, (char *)NETID, netname, strlen(netname),
295 			&lookup, &len);
296 	switch (yperr) {
297 		case 0:
298 			break; /* the successful case */
299 
300 		default :
301 			/*
302 			 *  XXX not sure about yp_match semantics.
303 			 * should err be set to NOTFOUND here?
304 			 */
305 			*err = __NSW_UNAVAIL;
306 			return (0);
307 	}
308 	if (lookup) {
309 		lookup[len] = '\0';
310 		*err = parse_netid_str(lookup, argp);
311 		free(lookup);
312 		return (*err == __NSW_SUCCESS);
313 	}
314 	*err = __NSW_NOTFOUND;
315 	return (0);
316 }
317 
318 /*
319  * Obtain user information (uid, gidlist) from nisplus.
320  * What we're trying to do here is to map a netname into
321  * local unix information (uid, gids), relevant in
322  * the *local* domain.
323  *
324  *	 cname   auth_type auth_name public  private
325  * ----------------------------------------------------------
326  *	nisname   DES     netname   pubkey  prikey
327  *	nisname   LOCAL   uid       gidlist
328  *
329  * 1.  Find out which 'home' domain to look for user's DES entry.
330  *	This is gotten from the domain part of the netname.
331  * 2.  Get the nisplus principal name from the DES entry in the cred
332  *	table of user's home domain.
333  * 3.  Use the nisplus principal name and search in the cred table of
334  *	the *local* directory for the LOCAL entry.
335  *
336  * Note that we need this translation of netname to <uid,gidlist> to be
337  * secure, so we *must* use authenticated connections.
338  */
339 static int
340 netname2user_nisplus(int *err, char *netname, struct netid_userdata *argp)
341 {
342 	char *domain;
343 	nis_result *res;
344 	char	sname[NIS_MAXNAMELEN+1]; /*  search criteria + table name */
345 	char	principal[NIS_MAXNAMELEN+1];
346 	int len;
347 
348 	/* 1.  Get home domain of user. */
349 	domain = strchr(netname, '@');
350 	if (!domain) {
351 		*err = __NSW_UNAVAIL;
352 		return (0);
353 	}
354 	domain++;  /* skip '@' */
355 
356 
357 	/* 2.  Get user's nisplus principal name.  */
358 	if ((strlen(netname)+strlen(domain)+PKTABLE_LEN+32) >
359 		(size_t)NIS_MAXNAMELEN) {
360 		*err = __NSW_UNAVAIL;
361 		return (0);
362 	}
363 	(void) snprintf(sname, sizeof (sname),
364 		"[auth_name=\"%s\",auth_type=DES],%s.%s",
365 		netname, PKTABLE, domain);
366 	if (sname[strlen(sname) - 1] != '.')
367 		(void) strcat(sname, ".");
368 
369 	/* must use authenticated call here */
370 	/* XXX but we cant, for now. XXX */
371 	res = nis_list(sname, USE_DGRAM+NO_AUTHINFO+FOLLOW_LINKS+FOLLOW_PATH,
372 	    NULL, NULL);
373 	switch (res->status) {
374 	case NIS_SUCCESS:
375 	case NIS_S_SUCCESS:
376 		break;   /* go and do something useful */
377 	case NIS_NOTFOUND:
378 	case NIS_PARTIAL:
379 	case NIS_NOSUCHNAME:
380 	case NIS_NOSUCHTABLE:
381 		*err = __NSW_NOTFOUND;
382 		nis_freeresult(res);
383 		return (0);
384 	case NIS_S_NOTFOUND:
385 	case NIS_TRYAGAIN:
386 		*err = __NSW_TRYAGAIN;
387 		syslog(LOG_ERR,
388 			"netname2user: (nis+ lookup): %s\n",
389 			nis_sperrno(res->status));
390 		nis_freeresult(res);
391 		return (0);
392 	default:
393 		*err = __NSW_UNAVAIL;
394 		syslog(LOG_ERR, "netname2user: (nis+ lookup): %s\n",
395 			nis_sperrno(res->status));
396 		nis_freeresult(res);
397 		return (0);
398 	}
399 
400 	if (res->objects.objects_len > 1) {
401 		/*
402 		 * A netname belonging to more than one principal?
403 		 * Something wrong with cred table. should be unique.
404 		 * Warn user and continue.
405 		 */
406 		syslog(LOG_ALERT,
407 			"netname2user: DES entry for %s in \
408 			directory %s not unique",
409 			netname, domain);
410 	}
411 
412 	len = ENTRY_LEN(res->objects.objects_val, 0);
413 	(void) strncpy(principal, ENTRY_VAL(res->objects.objects_val, 0), len);
414 	principal[len] = '\0';
415 	nis_freeresult(res);
416 
417 	if (principal[0] == '\0') {
418 		*err = __NSW_UNAVAIL;
419 		return (0);
420 	}
421 
422 	/*
423 	 *	3.  Use principal name to look up uid/gid information in
424 	 *	LOCAL entry in **local** cred table.
425 	 */
426 	domain = nis_local_directory();
427 	if ((strlen(principal)+strlen(domain)+PKTABLE_LEN+30) >
428 		(size_t)NIS_MAXNAMELEN) {
429 		*err = __NSW_UNAVAIL;
430 		syslog(LOG_ERR, "netname2user: principal name '%s' too long",
431 			principal);
432 		return (0);
433 	}
434 	(void) snprintf(sname, sizeof (sname),
435 		"[cname=\"%s\",auth_type=LOCAL],%s.%s",
436 		principal, PKTABLE, domain);
437 	if (sname[strlen(sname) - 1] != '.')
438 		(void) strcat(sname, ".");
439 
440 	/* must use authenticated call here */
441 	/* XXX but we cant, for now. XXX */
442 	res = nis_list(sname, USE_DGRAM+NO_AUTHINFO+FOLLOW_LINKS+FOLLOW_PATH,
443 	    NULL, NULL);
444 	switch (res->status) {
445 	case NIS_NOTFOUND:
446 	case NIS_PARTIAL:
447 	case NIS_NOSUCHNAME:
448 	case NIS_NOSUCHTABLE:
449 		*err = __NSW_NOTFOUND;
450 		nis_freeresult(res);
451 		return (0);
452 	case NIS_S_NOTFOUND:
453 	case NIS_TRYAGAIN:
454 		*err = __NSW_TRYAGAIN;
455 		syslog(LOG_ERR,
456 			"netname2user: (nis+ lookup): %s\n",
457 			nis_sperrno(res->status));
458 		nis_freeresult(res);
459 		return (0);
460 	case NIS_SUCCESS:
461 	case NIS_S_SUCCESS:
462 		break;   /* go and do something useful */
463 	default:
464 		*err = __NSW_UNAVAIL;
465 		syslog(LOG_ERR, "netname2user: (nis+ lookup): %s\n",
466 			nis_sperrno(res->status));
467 		nis_freeresult(res);
468 		return (0);
469 	}
470 
471 	if (res->objects.objects_len > 1) {
472 		/*
473 		 * A principal can have more than one LOCAL entry?
474 		 * Something wrong with cred table.
475 		 * Warn user and continue.
476 		 */
477 		syslog(LOG_ALERT,
478 			"netname2user: LOCAL entry for %s in\
479 				directory %s not unique",
480 			netname, domain);
481 	}
482 	/* nisname	LOCAL	uid 	grp,grp,grp */
483 	*err = parse_uid_gidlist(ENTRY_VAL(res->objects.objects_val, 2),
484 					/* uid */
485 			ENTRY_VAL(res->objects.objects_val, 3), /* gids */
486 			argp);
487 	nis_freeresult(res);
488 	return (*err == __NSW_SUCCESS);
489 }
490 
491 /*
492  * Build the uid and gid from the netname for users in LDAP.
493  * There is no netid container in LDAP. For this we build
494  * the netname to user data dynamically from the passwd and
495  * group data. This works only for users in a single domain.
496  * This function is an interim solution until we support a
497  * netid container in LDAP which enables us to do netname2user
498  * resolution for multiple domains.
499  */
500 static int
501 netname2user_ldap(int *err, char *netname, struct netid_userdata *argp)
502 {
503 	char buf[NSS_LINELEN_PASSWD];
504 	char *p2, *lasts;
505 	struct passwd pw;
506 	uid_t uidnu;
507 	int ngroups = 0;
508 	int count;
509 	char pwbuf[NSS_LINELEN_PASSWD];
510 	gid_t groups[NGROUPS_MAX];
511 
512 	if (strlcpy(buf, netname, NSS_LINELEN_PASSWD) >= NSS_LINELEN_PASSWD) {
513 		*err = __NSW_UNAVAIL;
514 		return (0);
515 	}
516 
517 	/* get the uid from the netname */
518 	if (strtok_r(buf, ".", &lasts) == NULL) {
519 		*err = __NSW_UNAVAIL;
520 		return (0);
521 	}
522 	if ((p2 = strtok_r(NULL, "@", &lasts)) == NULL) {
523 		*err = __NSW_UNAVAIL;
524 		return (0);
525 	}
526 	uidnu = atoi(p2);
527 
528 	/*
529 	 * check out the primary group and crosscheck the uid
530 	 * with the passwd data
531 	 */
532 	if ((getpwuid_r(uidnu, &pw, pwbuf, sizeof (pwbuf))) == NULL) {
533 		*err = __NSW_UNAVAIL;
534 		return (0);
535 	}
536 
537 	*(argp->uidp) = pw.pw_uid;
538 	*(argp->gidp) = pw.pw_gid;
539 
540 	/* search through all groups for membership */
541 
542 	groups[0] = pw.pw_gid;
543 
544 	ngroups = _getgroupsbymember(pw.pw_name, groups, NGROUPS_MAX,
545 				(pw.pw_gid >= 0) ? 1 : 0);
546 
547 	if (ngroups < 0) {
548 		*err = __NSW_UNAVAIL;
549 		return (0);
550 	}
551 
552 	*(argp->gidlenp) = ngroups;
553 
554 	for (count = 0; count < ngroups; count++) {
555 		(argp->gidlist[count]) = groups[count];
556 	}
557 
558 	*err = __NSW_SUCCESS;
559 	return (1);
560 
561 }
562 
563 /*
564  * Convert network-name into unix credential
565  */
566 int
567 netname2user(const char netname[MAXNETNAMELEN + 1], uid_t *uidp, gid_t *gidp,
568 						int *gidlenp, gid_t *gidlist)
569 {
570 	struct __nsw_switchconfig *conf;
571 	struct __nsw_lookup *look;
572 	enum __nsw_parse_err perr;
573 	int needfree = 1, res;
574 	struct netid_userdata argp;
575 	int err;
576 
577 	/*
578 	 * Take care of the special case of nobody. Compare the netname
579 	 * to the string "nobody". If they are equal, return the SVID
580 	 * standard value for nobody.
581 	 */
582 
583 	if (strcmp(netname, "nobody") == 0) {
584 		*uidp = NOBODY_UID;
585 		*gidp = NOBODY_UID;
586 		*gidlenp = 0;
587 		return (1);
588 	}
589 
590 	/*
591 	 * First we do some generic sanity checks on the name we were
592 	 * passed. This lets us assume they are correct in the backends.
593 	 *
594 	 * NOTE: this code only recognizes names of the form :
595 	 *		unix.UID@domainname
596 	 */
597 	if (strncmp(netname, OPSYS, OPSYS_LEN) != 0)
598 		return (0);
599 	if (!isdigit(netname[OPSYS_LEN+1]))	/* check for uid string */
600 		return (0);
601 
602 	argp.uidp = uidp;
603 	argp.gidp = gidp;
604 	argp.gidlenp = gidlenp;
605 	argp.gidlist = gidlist;
606 	(void) mutex_lock(&serialize_netname_r);
607 
608 	conf = __nsw_getconfig("publickey", &perr);
609 	if (!conf) {
610 		conf = &publickey_default;
611 		needfree = 0;
612 	} else
613 		needfree = 1; /* free the config structure */
614 
615 	for (look = conf->lookups; look; look = look->next) {
616 		if (strcmp(look->service_name, "nisplus") == 0)
617 			res = netname2user_nisplus(&err,
618 						(char *)netname, &argp);
619 		else if (strcmp(look->service_name, "nis") == 0)
620 			res = netname2user_nis(&err, (char *)netname, &argp);
621 		else if (strcmp(look->service_name, "files") == 0)
622 			res = netname2user_files(&err, (char *)netname, &argp);
623 		else if (strcmp(look->service_name, "ldap") == 0)
624 			res = netname2user_ldap(&err, (char *)netname, &argp);
625 		else {
626 			syslog(LOG_INFO,
627 		"netname2user: unknown nameservice for publickey info '%s'\n",
628 						look->service_name);
629 			err = __NSW_UNAVAIL;
630 		}
631 		switch (look->actions[err]) {
632 			case __NSW_CONTINUE :
633 				break;
634 			case __NSW_RETURN :
635 				if (needfree)
636 					__nsw_freeconfig(conf);
637 				(void) mutex_unlock(&serialize_netname_r);
638 				return (res);
639 			default :
640 				syslog(LOG_ERR,
641 			"netname2user: Unknown action for nameservice '%s'",
642 							look->service_name);
643 		}
644 	}
645 	if (needfree)
646 		__nsw_freeconfig(conf);
647 	(void) mutex_unlock(&serialize_netname_r);
648 	return (0);
649 }
650 
651 /*
652  * Convert network-name to hostname (fully qualified)
653  * NOTE: this code only recognizes names of the form :
654  *		unix.HOST@domainname
655  *
656  * This is very simple.  Since the netname is of the form:
657  *	unix.host@domainname
658  * We just construct the hostname using information from the domainname.
659  */
660 int
661 netname2host(const char netname[MAXNETNAMELEN + 1], char *hostname,
662 							const int hostlen)
663 {
664 	char *p, *domainname;
665 	int len, dlen;
666 
667 	if (!netname) {
668 		syslog(LOG_ERR, "netname2host: null netname");
669 		goto bad_exit;
670 	}
671 
672 	if (strncmp(netname, OPSYS, OPSYS_LEN) != 0)
673 		goto bad_netname;
674 	p = (char *)netname + OPSYS_LEN;	/* skip OPSYS part */
675 	if (*p != '.')
676 		goto bad_netname;
677 	++p;				/* skip '.' */
678 
679 	domainname = strchr(p, '@');	/* get domain name */
680 	if (domainname == 0)
681 		goto bad_netname;
682 
683 	len = domainname - p;		/* host sits between '.' and '@' */
684 	domainname++;			/* skip '@' sign */
685 
686 	if (len <= 0)
687 		goto bad_netname;
688 
689 	if (hostlen < len) {
690 		syslog(LOG_ERR,
691 			"netname2host: insufficient space for hostname");
692 		goto bad_exit;
693 	}
694 
695 	if (isdigit(*p))		/* don't want uid here */
696 		goto bad_netname;
697 
698 	if (*p == '\0')			/* check for null hostname */
699 		goto bad_netname;
700 
701 	(void) strncpy(hostname, p, len);
702 
703 	/* make into fully qualified hostname by concatenating domain part */
704 	dlen = strlen(domainname);
705 	if (hostlen < (len + dlen + 2)) {
706 		syslog(LOG_ERR,
707 			"netname2host: insufficient space for hostname");
708 		goto bad_exit;
709 	}
710 
711 	hostname[len] = '.';
712 	(void) strncpy(hostname+len+1, domainname, dlen);
713 	hostname[len+dlen+1] = '\0';
714 
715 	return (1);
716 
717 bad_netname:
718 	syslog(LOG_ERR, "netname2host: invalid host netname %s", netname);
719 
720 bad_exit:
721 	hostname[0] = '\0';
722 	return (0);
723 }
724