Home | History | Annotate | Download | only in passwdutil
      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  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
     23  * Use is subject to license terms.
     24  */
     25 
     26 /*
     27  *	npd_clnt.c
     28  *	Contains all the client-side routines to communicate
     29  *	with the NIS+ passwd update deamon.
     30  *
     31  */
     32 
     33 #include <stdlib.h>
     34 #include <syslog.h>
     35 #include <string.h>
     36 #include <shadow.h>
     37 #include <rpc/rpc.h>
     38 #include <rpc/xdr.h>
     39 #include <rpc/des_crypt.h>
     40 #include <mp.h>
     41 #include <rpc/key_prot.h>
     42 #include <rpcsvc/nis.h>
     43 #include <rpcsvc/nispasswd.h>
     44 #include <rpcsvc/nis_dhext.h>
     45 #include <memory.h>
     46 #include <sys/time.h>
     47 #include <unistd.h>
     48 #include <sys/types.h>
     49 
     50 #define	_NPD_PASSMAXLEN	16
     51 
     52 extern bool_t	__npd_ecb_crypt(uint32_t *, uint32_t *,
     53 				des_block *, unsigned int, unsigned int,
     54 				des_block *);
     55 extern bool_t	__npd_cbc_crypt(uint32_t *, char *, unsigned int,
     56 				npd_newpass *, unsigned int, unsigned int,
     57 				des_block *);
     58 extern bool_t	__npd2_cbc_crypt(uint32_t *, char *, unsigned int,
     59 				npd_newpass2 *, unsigned int, unsigned int,
     60 				des_block *);
     61 rpcvers_t	clnt_vers = NISPASSWD_VERS2;
     62 
     63 /*
     64  * Loop thru the NIS+ security cf entries until one DH(EXT) mech key is
     65  * successfully extracted from the server DHEXT netobj.  Copy the hex key
     66  * string to newly allocated memory and return it's address in 'keybuf'.
     67  * The caller must free this memory.  Also, dup the key length and algtype
     68  * "alias" string and return it's address in keystr  (which the caller
     69  * must also free on successful return).
     70  *
     71  * Policy: If no valid cf entries exist or if the entry is the "des" compat
     72  * one, then try it and then end search.
     73  *
     74  * Returns TRUE on success and FALSE on failure.
     75  */
     76 static bool_t
     77 get_dhext_key(
     78 	netobj		*pkey,		/* in */
     79 	char		**keybuf,	/* out */
     80 	keylen_t	*keylen,	/* (bits) out */
     81 	algtype_t	*keyalgtype,	/* out */
     82 	char		**keystr)	/* out */
     83 {
     84 	mechanism_t	**mechs;  /* list of mechanisms	*/
     85 	char		*hexkey;  /* hex public key */
     86 
     87 	if (mechs = __nis_get_mechanisms(FALSE)) {
     88 		mechanism_t **mpp;
     89 
     90 		for (mpp = mechs; *mpp; mpp++) {
     91 			mechanism_t *mp = *mpp;
     92 
     93 			if (AUTH_DES_COMPAT_CHK(mp)) {
     94 				__nis_release_mechanisms(mechs);
     95 				goto try_auth_des;
     96 			}
     97 			if (! VALID_MECH_ENTRY(mp))
     98 				continue;
     99 
    100 			if (hexkey = __nis_dhext_extract_pkey(pkey,
    101 			    mp->keylen, mp->algtype)) {
    102 				if ((*keybuf = malloc(strlen(hexkey) + 1))
    103 				    == 0) {
    104 					syslog(LOG_ERR, "malloc failed");
    105 					continue;  /* try next mech */
    106 				}
    107 				(void) strcpy(*keybuf, hexkey);
    108 				*keylen = mp->keylen;
    109 				*keyalgtype = mp->algtype;
    110 				*keystr = strdup(mp->alias);
    111 				__nis_release_mechanisms(mechs);
    112 				return (TRUE);
    113 			} else
    114 				continue;
    115 		}
    116 		__nis_release_mechanisms(mechs);
    117 		return (FALSE);
    118 	} else {
    119 
    120 	/* no valid cf mech entries or AUTH_DES compat entry found */
    121 	try_auth_des:
    122 		if (hexkey = __nis_dhext_extract_pkey(pkey,
    123 		    AUTH_DES_KEYLEN, AUTH_DES_ALGTYPE)) {
    124 			if ((*keybuf = malloc(strlen(hexkey) + 1)) == NULL) {
    125 					syslog(LOG_ERR, "malloc failed");
    126 					return (FALSE);
    127 			}
    128 			(void) strcpy(*keybuf, hexkey);
    129 			*keylen = AUTH_DES_KEYLEN;
    130 			*keyalgtype = AUTH_DES_ALGTYPE;
    131 			*keystr = strdup(NIS_SEC_CF_DES_ALIAS);
    132 			return (TRUE);
    133 		}
    134 	}
    135 	return (FALSE);
    136 }
    137 
    138 
    139 
    140 /*
    141  * given the domain return the client handle to the rpc.nispasswdd
    142  * that I need to contact and the master_servers' publickey and the
    143  * the key length and algtype "alias" string.
    144  *
    145  * returns TRUE on success and FALSE on failure.
    146  *
    147  * on successful return, caller must free the srv_pubkey buf and
    148  * the keystr buf.
    149  */
    150 bool_t
    151 npd_makeclnthandle(domain, clnt, srv_pubkey,
    152 			srv_keylen, srv_keyalgtype, key_type)
    153 char		*domain;
    154 CLIENT		**clnt;			/* out */
    155 char		**srv_pubkey;		/* buf to hold the pubkey; out */
    156 keylen_t	*srv_keylen;		/* server key lenth (bits); out */
    157 algtype_t	*srv_keyalgtype;	/* server key algorithm type; out */
    158 char		**key_type;		/* key length/algtype str buf; out */
    159 {
    160 	nis_server	**srvs;		/* servers that serve 'domain' */
    161 	nis_server	*master_srv;
    162 	char		buf[NIS_MAXNAMELEN];
    163 	CLIENT		*tmpclnt = NULL;
    164 	rpcvers_t	vers;
    165 
    166 	if (domain == NULL || *domain == '\0')
    167 		domain = nis_local_directory();
    168 
    169 	/* strlen("org_dir.") + null + "." = 10 */
    170 	if ((strlen(domain) + 10) > (size_t)NIS_MAXNAMELEN)
    171 		return (FALSE);
    172 	(void) snprintf(buf, sizeof (buf), "org_dir.%s", domain);
    173 	if (buf[strlen(buf) - 1] != '.')
    174 		(void) strcat(buf, ".");
    175 
    176 	srvs = nis_getservlist(buf);
    177 	if (srvs == NULL) {
    178 		/* can't find any of the servers that serve this domain */
    179 		/* something is very wrong ! */
    180 		syslog(LOG_ERR,
    181 			"can't get a list of servers for %s domain",
    182 			domain);
    183 		return (FALSE);
    184 	}
    185 	master_srv = srvs[0];	/* the first one is always the master */
    186 
    187 	/*
    188 	 * copy a publickey
    189 	 */
    190 	switch (master_srv->key_type) {
    191 	case NIS_PK_DHEXT:
    192 		if (!get_dhext_key(&(master_srv->pkey), srv_pubkey,
    193 					srv_keylen, srv_keyalgtype,
    194 					key_type)) {
    195 			syslog(LOG_WARNING,
    196 		"could not get a DHEXT public key for master server '%s'",
    197 				master_srv->name);
    198 			(void) nis_freeservlist(srvs);
    199 			return (FALSE);
    200 		}
    201 		break;
    202 
    203 	case NIS_PK_DH:
    204 		if ((*srv_pubkey = malloc(master_srv->pkey.n_len)) == NULL) {
    205 			syslog(LOG_ERR, "malloc failed");
    206 			(void) nis_freeservlist(srvs);
    207 			return (FALSE);
    208 		}
    209 		(void) strcpy(*srv_pubkey, master_srv->pkey.n_bytes);
    210 		*srv_keylen = AUTH_DES_KEYLEN;
    211 		*srv_keyalgtype = AUTH_DES_ALGTYPE;
    212 		*key_type = strdup(AUTH_DES_AUTH_TYPE);
    213 		break;
    214 
    215 	case NIS_PK_NONE:
    216 	default:
    217 		/* server does not have a D-H key-pair */
    218 		syslog(LOG_ERR, "no publickey for %s", master_srv->name);
    219 		(void) nis_freeservlist(srvs);
    220 		return (FALSE);
    221 	}
    222 
    223 	/*
    224 	 * now that we have the universal addr for the master server,
    225 	 * lets create the client handle to rpc.nispasswdd.
    226 	 * always use VC and attempt to create an authenticated handle.
    227 	 * nis_make_rpchandle() will attempt to use auth_des first,
    228 	 * if user does not have D-H keys, then it will try auth_sys.
    229 	 * sendsz and recvsz are 0 ==> choose defaults.
    230 	 *
    231 	 * First try NISPASSWD_VERS2. If it fails, fallback to NISPASSWD_VERS
    232 	 */
    233 	for (vers = NISPASSWD_VERS2;
    234 		(vers >= NISPASSWD_VERS) && (tmpclnt == NULL); vers--) {
    235 		tmpclnt = nis_make_rpchandle_gss_svc_ruid(master_srv, 0,
    236 				NISPASSWD_PROG, vers, ZMH_VC+ZMH_AUTH, 0,
    237 				0, NULL, NIS_SVCNAME_NISPASSWD);
    238 		clnt_vers = vers;
    239 	}
    240 
    241 	/* done with server list */
    242 	(void) nis_freeservlist(srvs);
    243 	if (tmpclnt == NULL) {
    244 		/*
    245 		 * error syslog'd by nis_make_rpchandle()
    246 		 */
    247 		return (FALSE);
    248 	}
    249 	*clnt = tmpclnt;
    250 	return (TRUE);
    251 }
    252 
    253 /* Default timeout can be changed using clnt_control() */
    254 static	struct	timeval	TIMEOUT = { 55, 0 };
    255 
    256 /*
    257  * initiate the passwd update request session by sending
    258  * username, domainname, the generated public key and
    259  * the callers' old passwd encrypted with the common DES key.
    260  * if it succeeds, decrypt the identifier and randval sent in
    261  * the response; otherwise return an appropriate error code.
    262  */
    263 nispasswd_status
    264 nispasswd_auth(user, domain, oldpass, u_pubkey, key_type, keylen,
    265 		algtype, deskeys, clnt, ident, randval, err)
    266 char		*user;		/* user name */
    267 char		*domain;	/* domain */
    268 char		*oldpass;	/* clear old password */
    269 uchar_t		*u_pubkey;	/* users' public key */
    270 char		*key_type;	/* key len and alg type string */
    271 keylen_t	keylen;		/* user's public key length */
    272 algtype_t	algtype;	/* user's public key algorithm type */
    273 des_block	*deskeys;	/* the common DES key */
    274 CLIENT		*clnt;		/* client handle to rpc.nispasswdd */
    275 uint32_t	*ident;		/* ID, returned on first attempt */
    276 uint32_t	*randval;	/* R, returned on first attempt */
    277 int		*err;		/* error code, returned */
    278 {
    279 	npd_request	req_arg;
    280 	nispasswd_authresult	res;
    281 	des_block	ivec;
    282 	unsigned char	xpass[_NPD_PASSMAXLEN+1];
    283 	unsigned char	xpass2[__NPD2_MAXPASSBYTES+1];
    284 	des_block	cryptbuf;
    285 	int		cryptstat;
    286 	int		i;
    287 
    288 	if ((user == NULL || *user == '\0') ||
    289 		(domain == NULL || *domain == '\0') ||
    290 		(oldpass == NULL || *oldpass == '\0') ||
    291 		(u_pubkey == NULL || *u_pubkey == '\0') ||
    292 		(deskeys == (des_block *) NULL) ||
    293 		(clnt == (CLIENT *) NULL)) {
    294 		*err = NPD_INVALIDARGS;
    295 		return (NPD_FAILED);
    296 	}
    297 	(void) memset((char *)&req_arg, 0, sizeof (req_arg));
    298 	(void) memset((char *)&res, 0, sizeof (res));
    299 
    300 	if (clnt_vers == NISPASSWD_VERS) {
    301 		/* encrypt the passwd with the common des key */
    302 		if (strlen(oldpass) > (size_t)_NPD_PASSMAXLEN) {
    303 			*err = NPD_BUFTOOSMALL;
    304 			return (NPD_FAILED);
    305 		}
    306 		(void) strlcpy((char *)xpass, oldpass, sizeof (xpass));
    307 		for (i = strlen(oldpass); i < _NPD_PASSMAXLEN; i++)
    308 			xpass[i] = '\0';
    309 
    310 		ivec.key.high = ivec.key.low = 0;
    311 		if (AUTH_DES_KEY(keylen, algtype))
    312 			cryptstat = cbc_crypt((char *)deskeys[0].c,
    313 					(char *)xpass, _NPD_PASSMAXLEN,
    314 					DES_ENCRYPT | DES_HW, (char *)&ivec);
    315 		else
    316 			cryptstat = __cbc_triple_crypt(deskeys, (char *)xpass,
    317 					_NPD_PASSMAXLEN, DES_ENCRYPT | DES_HW,
    318 					(char *)&ivec);
    319 
    320 		if (DES_FAILED(cryptstat)) {
    321 			*err = NPD_ENCRYPTFAIL;
    322 			return (NPD_FAILED);
    323 		}
    324 	} else {
    325 		/* encrypt the passwd with the common des key */
    326 		if (strlen(oldpass) > (size_t)__NPD2_MAXPASSBYTES) {
    327 			*err = NPD_BUFTOOSMALL;
    328 			return (NPD_FAILED);
    329 		}
    330 		(void) strlcpy((char *)xpass2, oldpass, sizeof (xpass2));
    331 		for (i = strlen(oldpass); i < __NPD2_MAXPASSBYTES; i++)
    332 			xpass2[i] = '\0';
    333 
    334 		ivec.key.high = ivec.key.low = 0;
    335 		if (AUTH_DES_KEY(keylen, algtype))
    336 			cryptstat = cbc_crypt((char *)deskeys[0].c,
    337 					(char *)xpass2, __NPD2_MAXPASSBYTES,
    338 					DES_ENCRYPT | DES_HW, (char *)&ivec);
    339 		else
    340 			cryptstat = __cbc_triple_crypt(deskeys, (char *)xpass2,
    341 					__NPD2_MAXPASSBYTES,
    342 					DES_ENCRYPT | DES_HW, (char *)&ivec);
    343 
    344 		if (DES_FAILED(cryptstat)) {
    345 			*err = NPD_ENCRYPTFAIL;
    346 			return (NPD_FAILED);
    347 		}
    348 	}
    349 
    350 	req_arg.username = user;
    351 	req_arg.domain = domain;
    352 	req_arg.key_type = key_type;
    353 	req_arg.user_pub_key.user_pub_key_len =
    354 			strlen((char *)u_pubkey) + 1;
    355 	req_arg.user_pub_key.user_pub_key_val = u_pubkey;
    356 	if (clnt_vers == NISPASSWD_VERS) {
    357 		req_arg.npd_authpass.npd_authpass_len = _NPD_PASSMAXLEN;
    358 		req_arg.npd_authpass.npd_authpass_val = xpass;
    359 	} else {
    360 		req_arg.npd_authpass.npd_authpass_len = __NPD2_MAXPASSBYTES;
    361 		req_arg.npd_authpass.npd_authpass_val = xpass2;
    362 	}
    363 	req_arg.ident = *ident;		/* on re-tries ident is non-zero */
    364 
    365 	if (clnt_call(clnt, NISPASSWD_AUTHENTICATE,
    366 		(xdrproc_t)xdr_npd_request, (caddr_t)&req_arg,
    367 		(xdrproc_t)xdr_nispasswd_authresult, (caddr_t)&res,
    368 		TIMEOUT) != RPC_SUCCESS) {
    369 
    370 		/* following msg is printed on stderr */
    371 		(void) clnt_perror(clnt,
    372 		    "authenticate call to rpc.nispasswdd failed");
    373 		*err = NPD_SRVNOTRESP;
    374 		return (NPD_FAILED);
    375 	}
    376 
    377 	switch (res.status) {
    378 	case NPD_SUCCESS:
    379 	case NPD_TRYAGAIN:
    380 		/*
    381 		 * decrypt the ident & randval
    382 		 */
    383 		cryptbuf.key.high =
    384 			ntohl(res.nispasswd_authresult_u.npd_verf.npd_xid);
    385 		cryptbuf.key.low =
    386 			ntohl(res.nispasswd_authresult_u.npd_verf.npd_xrandval);
    387 
    388 		if (! __npd_ecb_crypt(ident, randval, &cryptbuf,
    389 			sizeof (des_block), DES_DECRYPT, &(deskeys[0]))) {
    390 			*err = NPD_DECRYPTFAIL;
    391 			return (NPD_FAILED);
    392 		}
    393 		return (res.status);
    394 
    395 	case NPD_FAILED:
    396 		*err = res.nispasswd_authresult_u.npd_err;
    397 		return (NPD_FAILED);
    398 	default:
    399 		/*
    400 		 * should never reach this case !
    401 		 */
    402 		*err = NPD_SYSTEMERR;
    403 		return (NPD_FAILED);
    404 	}
    405 	/* NOTREACHED */
    406 }
    407 
    408 /*
    409  * authenticated the caller, now send the identifier; and the
    410  * new password and the random value encrypted with the common
    411  * DES key. Send any other changed password information in the
    412  * clear.
    413  */
    414 int
    415 nispasswd_pass(clnt, ident, randval, deskey, newpass, gecos, shell, err, errlst)
    416 CLIENT		*clnt;		/* client handle to rpc.nispasswdd */
    417 uint32_t	ident;		/* ID */
    418 uint32_t	randval;	/* R */
    419 des_block	*deskey;	/* common DES key */
    420 char		*newpass;	/* clear new password */
    421 char		*gecos;		/* gecos */
    422 char		*shell;		/* shell */
    423 int		*err;		/* error code, returned */
    424 nispasswd_error	**errlst;	/* error list on partial success, returned */
    425 {
    426 	npd_update	send_arg;
    427 	npd_update2	send_arg2;
    428 	nispasswd_updresult	result;
    429 	npd_newpass	cryptbuf1;
    430 	npd_newpass2	cryptbuf2;
    431 	unsigned int	tmp_xrval;
    432 	unsigned int	tmp_npd_pad;
    433 	nispasswd_error	*errl = NULL, *p;
    434 	char   xnewpass[__NPD_MAXPASSBYTES+1];
    435 	char   xnewpass2[__NPD2_MAXPASSBYTES+1];
    436 
    437 	if ((clnt == (CLIENT *) NULL) ||
    438 		(deskey == (des_block *) NULL) ||
    439 		(newpass == NULL || *newpass == '\0')) {
    440 		*err = NPD_INVALIDARGS;
    441 		return (NPD_FAILED);
    442 	}
    443 
    444 	if (clnt_vers == NISPASSWD_VERS) {
    445 		(void) memset((char *)&send_arg, 0, sizeof (send_arg));
    446 		(void) memset((char *)&result, 0, sizeof (result));
    447 		send_arg.ident = ident;
    448 
    449 		(void) strlcpy(xnewpass, newpass, sizeof (xnewpass));
    450 
    451 		if (! __npd_cbc_crypt(&randval, xnewpass, strlen(xnewpass),
    452 			&cryptbuf1, _NPD_PASSMAXLEN, DES_ENCRYPT, deskey)) {
    453 			*err = NPD_ENCRYPTFAIL;
    454 			return (NPD_FAILED);
    455 		}
    456 		tmp_xrval = cryptbuf1.npd_xrandval;
    457 		cryptbuf1.npd_xrandval = htonl(tmp_xrval);
    458 		send_arg.xnewpass = cryptbuf1;
    459 
    460 		/* gecos */
    461 		send_arg.pass_info.pw_gecos = gecos;
    462 
    463 		/* shell */
    464 		send_arg.pass_info.pw_shell = shell;
    465 
    466 		if (clnt_call(clnt, NISPASSWD_UPDATE,
    467 			(xdrproc_t)xdr_npd_update, (caddr_t)&send_arg,
    468 			(xdrproc_t)xdr_nispasswd_updresult, (caddr_t)&result,
    469 			TIMEOUT) != RPC_SUCCESS) {
    470 
    471 			/* printed to stderr */
    472 			(void) clnt_perror(clnt,
    473 			    "update call to rpc.nispasswdd failed");
    474 			*err = NPD_SRVNOTRESP;
    475 			return (NPD_FAILED);
    476 		}
    477 
    478 	} else {
    479 		(void) memset((char *)&send_arg2, 0, sizeof (send_arg2));
    480 		(void) memset((char *)&result, 0, sizeof (result));
    481 		send_arg2.ident = ident;
    482 
    483 		(void) strlcpy(xnewpass2, newpass, sizeof (xnewpass2));
    484 
    485 		if (! __npd2_cbc_crypt(&randval, xnewpass2, strlen(xnewpass2),
    486 			&cryptbuf2, sizeof (cryptbuf2), DES_ENCRYPT,
    487 			deskey)) {
    488 			*err = NPD_ENCRYPTFAIL;
    489 			return (NPD_FAILED);
    490 		}
    491 		tmp_xrval = cryptbuf2.npd_xrandval;
    492 		tmp_npd_pad = cryptbuf2.npd_pad;
    493 		cryptbuf2.npd_xrandval = htonl(tmp_xrval);
    494 		cryptbuf2.npd_pad = htonl(tmp_npd_pad);
    495 
    496 		send_arg2.xnewpass = cryptbuf2;
    497 		/* gecos */
    498 		send_arg2.pass_info.pw_gecos = gecos;
    499 		/* shell */
    500 		send_arg2.pass_info.pw_shell = shell;
    501 
    502 		if (clnt_call(clnt, NISPASSWD_UPDATE,
    503 			(xdrproc_t)xdr_npd_update2, (caddr_t)&send_arg2,
    504 			(xdrproc_t)xdr_nispasswd_updresult, (caddr_t)&result,
    505 			TIMEOUT) != RPC_SUCCESS) {
    506 
    507 			/* printed to stderr */
    508 			(void) clnt_perror(clnt,
    509 			    "update call to rpc.nispasswdd failed");
    510 			*err = NPD_SRVNOTRESP;
    511 			return (NPD_FAILED);
    512 		}
    513 	}
    514 	switch (result.status) {
    515 	case NPD_SUCCESS:
    516 		return (NPD_SUCCESS);
    517 	case NPD_PARTIALSUCCESS:
    518 		/* need to assign field/err code */
    519 		errl = &result.nispasswd_updresult_u.reason;
    520 		if (errl == (struct nispasswd_error *)NULL) {
    521 			*err = NPD_SYSTEMERR;
    522 			return (NPD_FAILED);
    523 		}
    524 		*errlst = (nispasswd_error *)
    525 				calloc(1, sizeof (nispasswd_error));
    526 		if (*errlst == (struct nispasswd_error *)NULL) {
    527 			*err = NPD_SYSTEMERR;
    528 			return (NPD_FAILED);
    529 		}
    530 
    531 		for (p = *errlst; errl != NULL; errl = errl->next) {
    532 			p->npd_field = errl->npd_field;
    533 			p->npd_code = errl->npd_code;
    534 			if (errl->next != NULL) {
    535 				p->next = (nispasswd_error *)
    536 					calloc(1, sizeof (nispasswd_error));
    537 				p = p->next;
    538 			} else
    539 				p->next = (nispasswd_error *) NULL;
    540 		}
    541 		return (NPD_PARTIALSUCCESS);
    542 	case NPD_FAILED:
    543 		*err = result.nispasswd_updresult_u.npd_err;
    544 		return (NPD_FAILED);
    545 	default:
    546 		/*
    547 		 * should never reach this case !
    548 		 */
    549 		*err = NPD_SYSTEMERR;
    550 		return (NPD_FAILED);
    551 	}
    552 }
    553 
    554 void
    555 __npd_free_errlist(list)
    556 nispasswd_error *list;
    557 {
    558 	nispasswd_error *p;
    559 
    560 	if (list == NULL)
    561 		return;
    562 	for (; list != NULL; list = p) {
    563 		p = list->next;
    564 		free(list);
    565 	}
    566 	list = NULL;
    567 }
    568