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  * Copyright 2014 Nexenta Systems, Inc.  All rights reserved.
26  */
27 
28 #include <strings.h>
29 #include <stdlib.h>
30 #include <syslog.h>
31 #include <sys/md5.h>
32 #include <smbsrv/string.h>
33 #include <smbsrv/libsmb.h>
34 #include <netsmb/spnego.h>	/* libsmbfs */
35 #include <assert.h>
36 
37 #define	NTLM_CHAL_SZ	SMBAUTH_CHAL_SZ	/* challenge size */
38 
39 /*
40  * Compute the combined (server+client) challenge per. [MS-NLMP 3.3.1]
41  * MD5(concat(ServerChallenge,ClientChallenge))
42  */
43 void
smb_auth_ntlm2_mkchallenge(char * result,const char * srv_chal,const char * clnt_chal)44 smb_auth_ntlm2_mkchallenge(char *result,
45 	const char *srv_chal, const char *clnt_chal)
46 {
47 	MD5_CTX context;
48 	uchar_t challenges[2 * NTLM_CHAL_SZ];
49 	uchar_t digest[SMBAUTH_HASH_SZ];
50 
51 	/*
52 	 * challenges = ConcatenationOf(ServerChallenge, ClientChallenge)
53 	 */
54 	(void) memcpy(challenges, srv_chal, NTLM_CHAL_SZ);
55 	(void) memcpy(challenges + NTLM_CHAL_SZ, clnt_chal, NTLM_CHAL_SZ);
56 
57 	/*
58 	 * digest = MD5(challenges)
59 	 */
60 	MD5Init(&context);
61 	MD5Update(&context, challenges, sizeof (challenges));
62 	MD5Final(digest, &context);
63 
64 	/*
65 	 * result = digest[0..7]
66 	 */
67 	(void) memcpy(result, digest, NTLM_CHAL_SZ);
68 }
69 
70 void
smb_auth_ntlm2_kxkey(unsigned char * result,const char * srv_chal,const char * clnt_chal,unsigned char * ssn_base_key)71 smb_auth_ntlm2_kxkey(unsigned char *result, const char *srv_chal,
72 	const char *clnt_chal, unsigned char *ssn_base_key)
73 {
74 	uchar_t challenges[2 * NTLM_CHAL_SZ];
75 
76 	/*
77 	 * challenges = ConcatenationOf(ServerChallenge, ClientChallenge)
78 	 */
79 	(void) memcpy(challenges, srv_chal, NTLM_CHAL_SZ);
80 	(void) memcpy(challenges + NTLM_CHAL_SZ, clnt_chal, NTLM_CHAL_SZ);
81 
82 	/* HMAC_MD5(SessionBaseKey, concat(...)) */
83 	/* SMBAUTH_HMACT64 args: D, Dsz, K, Ksz, digest */
84 	(void) SMBAUTH_HMACT64(challenges, sizeof (challenges),
85 	    ssn_base_key, SMBAUTH_HASH_SZ, result);
86 }
87 
88 /*
89  * smb_auth_qnd_unicode
90  *
91  * Quick and dirty unicode conversion!
92  * Returns the length of dst in bytes.
93  */
94 int
smb_auth_qnd_unicode(smb_wchar_t * dst,const char * src,int length)95 smb_auth_qnd_unicode(smb_wchar_t *dst, const char *src, int length)
96 {
97 	int i;
98 	unsigned int count;
99 	smb_wchar_t new_char;
100 
101 	if ((count = oemtoucs(dst, src, length, OEM_CPG_1252)) == 0) {
102 		for (i = 0; i < length; ++i) {
103 			new_char = (smb_wchar_t)src[i] & 0xff;
104 			dst[i] = LE_IN16(&new_char);
105 		}
106 		dst[i] = 0;
107 		count = length;
108 	}
109 
110 	return (count * sizeof (smb_wchar_t));
111 }
112 
113 /*
114  * smb_auth_lmupr
115  *
116  * Converts the given LM password to all uppercase.
117  * The standard strupr cannot
118  * be used here because lm_pwd doesn't have to be
119  * nul terminated.
120  */
121 static void
smb_auth_lmupr(unsigned char * lm_pwd)122 smb_auth_lmupr(unsigned char *lm_pwd)
123 {
124 	unsigned char *p = lm_pwd;
125 	int i;
126 
127 	for (i = 0; (*p) && (i < SMBAUTH_LM_PWD_SZ); i++) {
128 		if (smb_isascii(*p)) {
129 			*p = smb_toupper(*p);
130 			p++;
131 		}
132 	}
133 }
134 
135 /*
136  * smb_auth_lm_hash
137  *
138  * Source: Implementing CIFS (Chris Hertel)
139  *
140  * 1. The password, as entered by user, is either padded with nulls
141  *	  or trimmed to 14 bytes.
142  *    . Note that the 14-byte result string is not handled as a
143  *	    nul-terminated string.
144  *	  . The given password is OEM not Unicode
145  *
146  * 2. The 14-byte password is converted to all uppercase
147  *
148  * 3. The result is used as key to encrypt the KGS magic string to
149  *    make a 16-byte hash.
150  */
151 int
smb_auth_lm_hash(const char * password,unsigned char * lm_hash)152 smb_auth_lm_hash(const char *password, unsigned char *lm_hash)
153 {
154 	unsigned char lm_pwd[SMBAUTH_LM_PWD_SZ];
155 
156 	bzero((void *)lm_pwd, SMBAUTH_LM_PWD_SZ);
157 	(void) strncpy((char *)lm_pwd, password, SMBAUTH_LM_PWD_SZ);
158 	smb_auth_lmupr(lm_pwd);
159 
160 	return (smb_auth_DES(lm_hash, SMBAUTH_HASH_SZ, lm_pwd,
161 	    SMBAUTH_LM_PWD_SZ, (unsigned char *)SMBAUTH_LM_MAGIC_STR,
162 	    sizeof (SMBAUTH_LM_MAGIC_STR)));
163 }
164 
165 /*
166  * smb_auth_lm_response
167  *
168  * Create a LM response from the given LM hash and challenge.
169  *
170  * Returns SMBAUTH_FAILURE if any problems occur, SMBAUTH_SUCCESS if
171  * all goes well.
172  */
173 static int
smb_auth_lm_response(unsigned char * hash,unsigned char * challenge,unsigned char * lm_rsp)174 smb_auth_lm_response(unsigned char *hash,
175     unsigned char *challenge, /* NTLM_CHAL_SZ */
176     unsigned char *lm_rsp)
177 {
178 	unsigned char S21[21];
179 
180 	/*
181 	 * 14-byte LM Hash should be padded with 5 nul bytes to create
182 	 * a 21-byte string to be used in producing LM response
183 	 */
184 	bzero(&S21[SMBAUTH_HASH_SZ], 5);
185 	bcopy(hash, S21, SMBAUTH_HASH_SZ);
186 
187 	/* padded LM Hash -> LM Response */
188 	return (smb_auth_DES(lm_rsp, SMBAUTH_LM_RESP_SZ, S21, 21,
189 	    challenge, NTLM_CHAL_SZ));
190 }
191 
192 /*
193  * smb_auth_ntlm_hash
194  *
195  * Make NTLM Hash (using MD4) from the given password.
196  * The result will contain a 16-byte NTLM hash.
197  */
198 int
smb_auth_ntlm_hash(const char * password,unsigned char * hash)199 smb_auth_ntlm_hash(const char *password, unsigned char *hash)
200 {
201 	smb_wchar_t *unicode_password;
202 	int length, unicode_len;
203 	int rc;
204 
205 	if (password == NULL || hash == NULL)
206 		return (SMBAUTH_FAILURE);
207 
208 	length = strlen(password);
209 	unicode_len = (length + 1) * sizeof (smb_wchar_t);
210 	unicode_password = malloc(unicode_len);
211 
212 	if (unicode_password == NULL)
213 		return (SMBAUTH_FAILURE);
214 
215 	length = smb_auth_qnd_unicode(unicode_password, password, length);
216 	rc = smb_auth_md4(hash, (unsigned char *)unicode_password, length);
217 
218 	(void) memset(unicode_password, 0, unicode_len);
219 	free(unicode_password);
220 
221 	return (rc);
222 }
223 
224 /*
225  * smb_auth_ntlm_response
226  *
227  * Make LM/NTLM response from the given LM/NTLM Hash and given
228  * challenge.
229  */
230 static int
smb_auth_ntlm_response(unsigned char * hash,unsigned char * challenge,unsigned char * ntlm_rsp)231 smb_auth_ntlm_response(unsigned char *hash,
232     unsigned char *challenge, /* NTLM_CHAL_SZ */
233     unsigned char *ntlm_rsp)
234 {
235 	unsigned char S21[21];
236 
237 	bcopy(hash, S21, SMBAUTH_HASH_SZ);
238 	bzero(&S21[SMBAUTH_HASH_SZ], 5);
239 	if (smb_auth_DES((unsigned char *)ntlm_rsp, SMBAUTH_LM_RESP_SZ,
240 	    S21, 21, challenge, NTLM_CHAL_SZ) == SMBAUTH_FAILURE)
241 		return (0);
242 	return (SMBAUTH_LM_RESP_SZ);
243 }
244 
245 /*
246  * smb_auth_ntlmv2_hash
247  *
248  * The NTLM v2 hash will be created from the given NTLM hash, username,
249  * and the NETBIOS name of the domain.
250  *
251  * The NTLMv2 hash will be returned via the ntlmv2_hash parameter which
252  * will be used in the calculation of the NTLMv2 and LMv2 responses.
253  */
254 int
smb_auth_ntlmv2_hash(unsigned char * ntlm_hash,char * username,char * ntdomain,unsigned char * ntlmv2_hash)255 smb_auth_ntlmv2_hash(unsigned char *ntlm_hash,
256     char *username,
257     char *ntdomain,
258     unsigned char *ntlmv2_hash)
259 {
260 	smb_wchar_t *data;
261 	int data_len;
262 	unsigned char *buf;
263 	int rc;
264 
265 	if (username == NULL || ntdomain == NULL)
266 		return (SMBAUTH_FAILURE);
267 
268 	(void) smb_strupr(username);
269 
270 	data_len = strlen(username) + strlen(ntdomain);
271 	buf = (unsigned char *)malloc((data_len + 1) * sizeof (char));
272 	if (buf == NULL)
273 		return (SMBAUTH_FAILURE);
274 
275 	(void) snprintf((char *)buf, data_len + 1, "%s%s", username, ntdomain);
276 	data = (smb_wchar_t *)malloc((data_len + 1) * sizeof (smb_wchar_t));
277 	if (data == NULL) {
278 		free(buf);
279 		return (SMBAUTH_FAILURE);
280 	}
281 
282 	data_len = smb_auth_qnd_unicode(data, (char *)buf, data_len);
283 	rc = SMBAUTH_HMACT64((unsigned char *)data, data_len, ntlm_hash,
284 	    SMBAUTH_HASH_SZ, ntlmv2_hash);
285 
286 	free(buf);
287 	free(data);
288 	return (rc);
289 }
290 
291 /*
292  * smb_auth_v2_response
293  *
294  * Caculates either the LMv2 or NTLMv2 response.
295  *
296  * Same algorithm is used for calculating both LMv2 or NTLMv2 responses.
297  * This routine will return NTLMv2 response if the data blob information
298  * is passed in as the clnt_data. Otherwise, it will return LMv2 response
299  * with the 8-byte client challenge(a.k.a blip) as the clnt_data.
300  *
301  * (LM/NTLM)v2 response is the hmac-md5 hash of the specified data
302  * (server challenge + NTLMv2 data blob or LMv2 client challenge)
303  * using the NTLMv2 hash as the key.
304  *
305  * Returns the size of the corresponding v2 response upon success.
306  * Otherwise, returns -1 on error.
307  */
308 static int
smb_auth_v2_response(unsigned char * hash,unsigned char * srv_challenge,unsigned char * clnt_data,int clen,unsigned char * v2_rsp)309 smb_auth_v2_response(
310 	unsigned char *hash,
311 	unsigned char *srv_challenge, /* NTLM_CHAL_SZ */
312 	unsigned char *clnt_data, int clen,
313 	unsigned char *v2_rsp)
314 {
315 	unsigned char *hmac_data;
316 	int slen = NTLM_CHAL_SZ;
317 
318 	hmac_data = malloc(NTLM_CHAL_SZ + clen);
319 	if (!hmac_data) {
320 		return (-1);
321 	}
322 
323 	(void) memcpy(hmac_data, srv_challenge, slen);
324 	(void) memcpy(&hmac_data[slen], clnt_data, clen);
325 	if (SMBAUTH_HMACT64(hmac_data, slen + clen, (unsigned char *)hash,
326 	    SMBAUTH_HASH_SZ, (unsigned char *)v2_rsp) != SMBAUTH_SUCCESS)
327 		return (-1);
328 	(void) memcpy(&v2_rsp[SMBAUTH_HASH_SZ], clnt_data, clen);
329 
330 	free(hmac_data);
331 	return (SMBAUTH_HASH_SZ + clen);
332 }
333 
334 
335 static boolean_t
smb_lm_password_ok(unsigned char * challenge,unsigned char * lm_hash,unsigned char * lm_resp)336 smb_lm_password_ok(
337     unsigned char *challenge,
338     unsigned char *lm_hash,
339     unsigned char *lm_resp)
340 {
341 	unsigned char ok_resp[SMBAUTH_LM_RESP_SZ];
342 	int rc;
343 
344 	rc = smb_auth_lm_response(lm_hash, challenge, ok_resp);
345 	if (rc != SMBAUTH_SUCCESS)
346 		return (B_FALSE);
347 
348 	return (bcmp(ok_resp, lm_resp, SMBAUTH_LM_RESP_SZ) == 0);
349 }
350 
351 static boolean_t
smb_ntlm_password_ok(unsigned char * challenge,unsigned char * ntlm_hash,unsigned char * nt_resp,unsigned char * session_key)352 smb_ntlm_password_ok(
353     unsigned char *challenge,
354     unsigned char *ntlm_hash,
355     unsigned char *nt_resp,
356     unsigned char *session_key)
357 {
358 	unsigned char ok_resp[SMBAUTH_LM_RESP_SZ];
359 	int rc;
360 	boolean_t ok;
361 
362 	rc = smb_auth_ntlm_response(ntlm_hash, challenge, ok_resp);
363 	if (rc != SMBAUTH_LM_RESP_SZ)
364 		return (B_FALSE);
365 
366 	ok = (bcmp(ok_resp, nt_resp, SMBAUTH_LM_RESP_SZ) == 0);
367 	if (ok && (session_key)) {
368 		rc = smb_auth_md4(session_key, ntlm_hash, SMBAUTH_HASH_SZ);
369 		if (rc != SMBAUTH_SUCCESS)
370 			ok = B_FALSE;
371 	}
372 	return (ok);
373 }
374 
375 static boolean_t
smb_ntlmv2_password_ok(unsigned char * challenge,unsigned char * ntlm_hash,unsigned char * passwd,int pwdlen,char * domain,char * username,uchar_t * session_key)376 smb_ntlmv2_password_ok(
377     unsigned char *challenge,
378     unsigned char *ntlm_hash,
379     unsigned char *passwd,
380     int pwdlen,
381     char *domain,
382     char *username,
383     uchar_t *session_key)
384 {
385 	unsigned char *clnt_blob;
386 	int clnt_blob_len;
387 	unsigned char ntlmv2_hash[SMBAUTH_HASH_SZ];
388 	unsigned char *ntlmv2_resp;
389 	boolean_t ok = B_FALSE;
390 	char *dest[3];
391 	int i;
392 	int rc;
393 
394 	clnt_blob_len = pwdlen - SMBAUTH_HASH_SZ;
395 	clnt_blob = &passwd[SMBAUTH_HASH_SZ];
396 	dest[0] = domain;
397 	if ((dest[1] = strdup(domain)) == NULL)
398 		return (B_FALSE);
399 	(void) smb_strupr(dest[1]);
400 	dest[2] = "";
401 
402 	/*
403 	 * 15.5.2 The NTLMv2 Password Hash, pg. 279, of the "Implementing CIFS"
404 	 *
405 	 * The NTLMv2 Hash is created from:
406 	 * - NTLM hash
407 	 * - user's username, and
408 	 * - the name of the logon destination(i.e. the NetBIOS name of either
409 	 *   the SMB server or NT Domain against which the user is trying to
410 	 *   authenticate.
411 	 *
412 	 * Experiments show this is not exactly the case.
413 	 * For Windows Server 2003, the domain name needs to be included and
414 	 * converted to uppercase. For Vista, the domain name needs to be
415 	 * included also, but leave the case alone.  And in some cases it needs
416 	 * to be empty. All three variants are tried here.
417 	 */
418 
419 	ntlmv2_resp = (unsigned char *)malloc(SMBAUTH_HASH_SZ + clnt_blob_len);
420 	if (ntlmv2_resp == NULL) {
421 		free(dest[1]);
422 		return (B_FALSE);
423 	}
424 
425 	for (i = 0; i < (sizeof (dest) / sizeof (char *)); i++) {
426 		if (smb_auth_ntlmv2_hash(ntlm_hash, username, dest[i],
427 		    ntlmv2_hash) != SMBAUTH_SUCCESS)
428 			break;
429 
430 		if (smb_auth_v2_response(ntlmv2_hash, challenge,
431 		    clnt_blob, clnt_blob_len, ntlmv2_resp) < 0)
432 			break;
433 
434 		ok = (bcmp(passwd, ntlmv2_resp, pwdlen) == 0);
435 		if (ok && session_key) {
436 			rc = SMBAUTH_HMACT64(ntlmv2_resp,
437 			    SMBAUTH_HASH_SZ, ntlmv2_hash,
438 			    SMBAUTH_SESSION_KEY_SZ, session_key);
439 			if (rc != SMBAUTH_SUCCESS) {
440 				ok = B_FALSE;
441 			}
442 			break;
443 		}
444 	}
445 
446 	free(dest[1]);
447 	free(ntlmv2_resp);
448 	return (ok);
449 }
450 
451 static boolean_t
smb_lmv2_password_ok(unsigned char * srv_challenge,unsigned char * ntlm_hash,unsigned char * passwd,char * domain,char * username)452 smb_lmv2_password_ok(
453     unsigned char *srv_challenge,
454     unsigned char *ntlm_hash,
455     unsigned char *passwd,
456     char *domain,
457     char *username)
458 {
459 	unsigned char *clnt_challenge;
460 	unsigned char ntlmv2_hash[SMBAUTH_HASH_SZ];
461 	unsigned char lmv2_resp[SMBAUTH_LM_RESP_SZ];
462 	boolean_t ok = B_FALSE;
463 	char *dest[3];
464 	int i;
465 
466 	clnt_challenge = &passwd[SMBAUTH_HASH_SZ];
467 	dest[0] = domain;
468 	if ((dest[1] = strdup(domain)) == NULL)
469 		return (B_FALSE);
470 	(void) smb_strupr(dest[1]);
471 	dest[2] = "";
472 
473 	/*
474 	 * 15.5.2 The NTLMv2 Password Hash, pg. 279, of the "Implementing CIFS"
475 	 *
476 	 * The NTLMv2 Hash is created from:
477 	 * - NTLM hash
478 	 * - user's username, and
479 	 * - the name of the logon destination(i.e. the NetBIOS name of either
480 	 *   the SMB server or NT Domain against which the suer is trying to
481 	 *   authenticate.
482 	 *
483 	 * Experiments show this is not exactly the case.
484 	 * For Windows Server 2003, the domain name needs to be included and
485 	 * converted to uppercase. For Vista, the domain name needs to be
486 	 * included also, but leave the case alone.  And in some cases it needs
487 	 * to be empty. All three variants are tried here.
488 	 */
489 
490 	for (i = 0; i < (sizeof (dest) / sizeof (char *)); i++) {
491 		if (smb_auth_ntlmv2_hash(ntlm_hash, username, dest[i],
492 		    ntlmv2_hash) != SMBAUTH_SUCCESS)
493 			break;
494 
495 		if (smb_auth_v2_response(ntlmv2_hash, srv_challenge,
496 		    clnt_challenge, SMBAUTH_CHAL_SZ,
497 		    lmv2_resp) < 0)
498 			break;
499 
500 		ok = (bcmp(passwd, lmv2_resp, SMBAUTH_LM_RESP_SZ) == 0);
501 		if (ok)
502 			break;
503 	}
504 
505 	free(dest[1]);
506 	return (ok);
507 }
508 
509 /*
510  * smb_auth_validate
511  *
512  * Validates given NTLMv2 (or NTLM, LMv2, LM) client responses against
513  * the stored user's password, passed in smbpw.  Try those in the order
514  * strongest to weakest, stopping at a point determined by the configured
515  * lmauth_level (LM Compatibility Level).
516  */
517 boolean_t
smb_auth_validate(smb_passwd_t * smbpw,char * domain,char * username,unsigned char * challenge,uint_t clen,unsigned char * nt_resp,uint_t nt_len,unsigned char * lm_resp,uint_t lm_len,uchar_t * session_key)518 smb_auth_validate(
519     smb_passwd_t *smbpw,
520     char *domain,
521     char *username,
522     unsigned char *challenge,
523     uint_t clen,
524     unsigned char *nt_resp,
525     uint_t nt_len,
526     unsigned char *lm_resp,
527     uint_t lm_len,
528     uchar_t *session_key)
529 {
530 	int64_t lmlevel;
531 	boolean_t ok = B_FALSE;
532 
533 	if (smb_config_getnum(SMB_CI_LM_LEVEL, &lmlevel) != SMBD_SMF_OK)
534 		return (B_FALSE);
535 
536 	if (lmlevel > 5)
537 		return (B_FALSE);
538 
539 	if (clen != NTLM_CHAL_SZ)
540 		return (B_FALSE);
541 
542 	/*
543 	 * Accept NTLMv2 at any LM level (0-5).
544 	 */
545 	if (nt_len > SMBAUTH_LM_RESP_SZ) {
546 		ok = smb_ntlmv2_password_ok(challenge,
547 		    smbpw->pw_nthash, nt_resp, nt_len,
548 		    domain, username, session_key);
549 		if (ok)
550 			return (ok);
551 	}
552 
553 	if (lmlevel == 5)
554 		return (B_FALSE);
555 
556 	/*
557 	 * Accept NTLM at levels 0-4
558 	 */
559 	if (nt_len == SMBAUTH_LM_RESP_SZ) {
560 		ok = smb_ntlm_password_ok(challenge, smbpw->pw_nthash,
561 		    nt_resp, session_key);
562 		if (ok)
563 			return (ok);
564 	}
565 
566 	if (lmlevel == 4)
567 		return (B_FALSE);
568 
569 
570 	/*
571 	 * Accept LM/LMv2 auth at levels 0-3
572 	 */
573 	if (lm_len != SMBAUTH_LM_RESP_SZ)
574 		return (B_FALSE);
575 	if (session_key)
576 		(void) smb_auth_md4(session_key, smbpw->pw_nthash,
577 		    SMBAUTH_HASH_SZ);
578 	ok = smb_lmv2_password_ok(challenge, smbpw->pw_nthash,
579 	    lm_resp, domain, username);
580 	if (ok)
581 		return (ok);
582 	ok = smb_lm_password_ok(challenge, smbpw->pw_lmhash, lm_resp);
583 	if (ok)
584 		return (ok);
585 
586 	return (B_FALSE);
587 }
588 
589 /*
590  * smb_gen_random_passwd(buf, len)
591  * Generate a random password of length len-1, and store it in buf,
592  * null terminated.  This is used as a machine account password,
593  * which we set when we join a domain.
594  *
595  * [MS-DISO] A machine password is an ASCII string of randomly chosen
596  * characters. Each character's ASCII code is between 32 and 122 inclusive.
597  * That's space through 'z'.
598  */
599 
600 int
smb_gen_random_passwd(char * buf,size_t len)601 smb_gen_random_passwd(char *buf, size_t len)
602 {
603 	const uchar_t start = ' ';
604 	const uchar_t modulus = 'z' - ' ' + 1;
605 	uchar_t t;
606 	int i;
607 
608 	/* Last byte is the null. */
609 	len--;
610 
611 	/* Temporarily put random data in the caller's buffer. */
612 	randomize(buf, len);
613 
614 	/* Convert the random data to printable characters. */
615 	for (i = 0; i < len; i++) {
616 		/* need unsigned math */
617 		t = (uchar_t)buf[i];
618 		t = (t % modulus) + start;
619 		assert(' ' <= t && t <= 'z');
620 		buf[i] = (char)t;
621 	}
622 
623 	buf[len] = '\0';
624 
625 	return (0);
626 }
627