xref: /openbsd-src/libexec/login_token/token.c (revision 1b7fb171885ef76699602ea94c898eea4ad21334)
1*1b7fb171Smillert /*	$OpenBSD: token.c,v 1.19 2015/10/05 17:31:17 millert Exp $	*/
227b8cbb6Smillert 
327b8cbb6Smillert /*-
4fafad76dSmillert  * Copyright (c) 1995 Migration Associates Corp. All Rights Reserved
527b8cbb6Smillert  *
627b8cbb6Smillert  * Redistribution and use in source and binary forms, with or without
727b8cbb6Smillert  * modification, are permitted provided that the following conditions
827b8cbb6Smillert  * are met:
927b8cbb6Smillert  * 1. Redistributions of source code must retain the above copyright
1027b8cbb6Smillert  *    notice, this list of conditions and the following disclaimer.
1127b8cbb6Smillert  * 2. Redistributions in binary form must reproduce the above copyright
1227b8cbb6Smillert  *    notice, this list of conditions and the following disclaimer in the
1327b8cbb6Smillert  *    documentation and/or other materials provided with the distribution.
1427b8cbb6Smillert  * 3. All advertising materials mentioning features or use of this software
1527b8cbb6Smillert  *    must display the following acknowledgement:
1627b8cbb6Smillert  *      This product includes software developed by Berkeley Software Design,
1727b8cbb6Smillert  *      Inc.
1827b8cbb6Smillert  * 4. The name of Berkeley Software Design, Inc.  may not be used to endorse
1927b8cbb6Smillert  *    or promote products derived from this software without specific prior
2027b8cbb6Smillert  *    written permission.
2127b8cbb6Smillert  *
2227b8cbb6Smillert  * THIS SOFTWARE IS PROVIDED BY BERKELEY SOFTWARE DESIGN, INC. ``AS IS'' AND
2327b8cbb6Smillert  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
2427b8cbb6Smillert  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
2527b8cbb6Smillert  * ARE DISCLAIMED.  IN NO EVENT SHALL BERKELEY SOFTWARE DESIGN, INC. BE LIABLE
2627b8cbb6Smillert  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
2727b8cbb6Smillert  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
2827b8cbb6Smillert  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
2927b8cbb6Smillert  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
3027b8cbb6Smillert  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
3127b8cbb6Smillert  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
3227b8cbb6Smillert  * SUCH DAMAGE.
3327b8cbb6Smillert  *
3427b8cbb6Smillert  *	BSDI $From: token.c,v 1.2 1996/08/28 22:07:55 prb Exp $
3527b8cbb6Smillert  */
3627b8cbb6Smillert 
3727b8cbb6Smillert /*
3827b8cbb6Smillert  * DES functions for one-way encrypting Authentication Tokens.
3927b8cbb6Smillert  * All knowledge of DES is confined to this file.
4027b8cbb6Smillert  */
4127b8cbb6Smillert 
4227b8cbb6Smillert #include <sys/types.h>
4327b8cbb6Smillert #include <sys/time.h>
4427b8cbb6Smillert #include <sys/resource.h>
4527b8cbb6Smillert 
4627b8cbb6Smillert #include <ctype.h>
4727b8cbb6Smillert #include <stdio.h>
4827b8cbb6Smillert #include <syslog.h>
49d05a8106Sderaadt #include <limits.h>
5027b8cbb6Smillert #include <stdlib.h>
5127b8cbb6Smillert #include <string.h>
5227b8cbb6Smillert #include <unistd.h>
530456bce5Sjsg #include <openssl/des.h>
5427b8cbb6Smillert 
5527b8cbb6Smillert #include "token.h"
5627b8cbb6Smillert #include "tokendb.h"
5727b8cbb6Smillert 
5827b8cbb6Smillert /*
5927b8cbb6Smillert  * Define a union of various types of arguments to DES functions.
6027b8cbb6Smillert  * All native DES types are modulo 8 bytes in length. Cipher text
6127b8cbb6Smillert  * needs a trailing null byte.
6227b8cbb6Smillert  */
6327b8cbb6Smillert 
6427b8cbb6Smillert typedef	union {
650456bce5Sjsg 	DES_cblock	cb;
6627b8cbb6Smillert 	char		ct[9];
6764ff528aStedu 	uint32_t	ul[2];
6827b8cbb6Smillert } TOKEN_CBlock;
6927b8cbb6Smillert 
7027b8cbb6Smillert /*
7127b8cbb6Smillert  * Static definition of random number challenge for token.
7227b8cbb6Smillert  * Challenge length is 8 bytes, left-justified with trailing null byte.
7327b8cbb6Smillert  */
7427b8cbb6Smillert 
7527b8cbb6Smillert static	TOKEN_CBlock tokennumber;
7627b8cbb6Smillert 
7727b8cbb6Smillert /*
7827b8cbb6Smillert  * Static function prototypes
7927b8cbb6Smillert  */
8027b8cbb6Smillert 
81b4975d76Smarkus static	void	tokenseed(TOKEN_CBlock *);
8227b8cbb6Smillert static	void	lcase(char *);
8327b8cbb6Smillert static	void	h2d(char *);
8427b8cbb6Smillert static	void	h2cb(char *, TOKEN_CBlock *);
8527b8cbb6Smillert static	void	cb2h(TOKEN_CBlock, char *);
8627b8cbb6Smillert 
8727b8cbb6Smillert /*
8827b8cbb6Smillert  * Generate random DES cipherblock seed. Feedback key into
8927b8cbb6Smillert  * new_random_key to strengthen.
9027b8cbb6Smillert  */
9127b8cbb6Smillert 
9227b8cbb6Smillert static void
tokenseed(TOKEN_CBlock * cb)93b4975d76Smarkus tokenseed(TOKEN_CBlock *cb)
9427b8cbb6Smillert {
95b4975d76Smarkus 	cb->ul[0] = arc4random();
96b4975d76Smarkus 	cb->ul[1] = arc4random();
9727b8cbb6Smillert }
9827b8cbb6Smillert 
9927b8cbb6Smillert /*
10027b8cbb6Smillert  * Send a random challenge string to the token. The challenge
10127b8cbb6Smillert  * is always base 10 as there are no alpha keys on the keyboard.
10227b8cbb6Smillert  */
10327b8cbb6Smillert 
10427b8cbb6Smillert void
tokenchallenge(char * user,char * challenge,int size,char * card_type)10527b8cbb6Smillert tokenchallenge(char *user, char *challenge, int size, char *card_type)
10627b8cbb6Smillert {
10727b8cbb6Smillert 	TOKENDB_Rec tr;
10827b8cbb6Smillert 	TOKEN_CBlock cb;
1090456bce5Sjsg 	DES_key_schedule ks;
11027b8cbb6Smillert 	int r, c;
11127b8cbb6Smillert 
11227b8cbb6Smillert 	r = 1;	/* no reduced input mode by default! */
11327b8cbb6Smillert 
11427b8cbb6Smillert 	if ((tt->modes & TOKEN_RIM) &&
11527b8cbb6Smillert 	    tokendb_getrec(user, &tr) == 0 &&
11627b8cbb6Smillert 	    (tr.mode & TOKEN_RIM)) {
11727b8cbb6Smillert 		c = 0;
11827b8cbb6Smillert 		while ((r = tokendb_lockrec(user, &tr, TOKEN_LOCKED)) == 1) {
11927b8cbb6Smillert 			if (c++ >= 60)
12027b8cbb6Smillert 				break;
12127b8cbb6Smillert 			sleep(1);
12227b8cbb6Smillert 		}
12327b8cbb6Smillert 		tr.flags &= ~TOKEN_LOCKED;
12427b8cbb6Smillert 		if (r == 0 && tr.rim[0]) {
12527b8cbb6Smillert 			h2cb(tr.secret, &cb);
1260456bce5Sjsg 			DES_fixup_key_parity(&cb.cb);
1270456bce5Sjsg 			DES_key_sched(&cb.cb, &ks);
1280456bce5Sjsg 			DES_ecb_encrypt(&tr.rim, &cb.cb, &ks, DES_ENCRYPT);
12927b8cbb6Smillert 			memcpy(tr.rim, cb.cb, 8);
13027b8cbb6Smillert 			for (r = 0; r < 8; ++r) {
13127b8cbb6Smillert 				if ((tr.rim[r] &= 0xf) > 9)
13227b8cbb6Smillert 					tr.rim[r] -= 10;
13327b8cbb6Smillert 				tr.rim[r] |= 0x30;
13427b8cbb6Smillert 			}
13527b8cbb6Smillert 			r = 0;		/* reset it back */
13627b8cbb6Smillert 			memcpy(tokennumber.ct, tr.rim, 8);
13727b8cbb6Smillert 			tokennumber.ct[8] = 0;
13827b8cbb6Smillert 			tokendb_putrec(user, &tr);
13927b8cbb6Smillert 		}
14027b8cbb6Smillert 	}
14127b8cbb6Smillert 	if (r != 0 || tr.rim[0] == '\0') {
142b4975d76Smarkus 		memset(tokennumber.ct, 0, sizeof(tokennumber.ct));
14394f29c57Smillert 		snprintf(tokennumber.ct, sizeof(tokennumber.ct), "%8.8u",
14494f29c57Smillert 		    arc4random());
14527b8cbb6Smillert 		if (r == 0) {
14627b8cbb6Smillert 			memcpy(tr.rim, tokennumber.ct, 8);
14727b8cbb6Smillert 			tokendb_putrec(user, &tr);
14827b8cbb6Smillert 		}
14927b8cbb6Smillert 	}
15027b8cbb6Smillert 
15127b8cbb6Smillert 	snprintf(challenge, size, "%s Challenge \"%s\"\r\n%s Response: ",
15227b8cbb6Smillert 	    card_type, tokennumber.ct, card_type);
15327b8cbb6Smillert }
15427b8cbb6Smillert 
15527b8cbb6Smillert /*
15627b8cbb6Smillert  * Verify response from user against token's predicted cipher
15727b8cbb6Smillert  * of the random number challenge.
15827b8cbb6Smillert  */
15927b8cbb6Smillert 
16027b8cbb6Smillert int
tokenverify(char * username,char * challenge,char * response)16127b8cbb6Smillert tokenverify(char *username, char *challenge, char *response)
16227b8cbb6Smillert {
16327b8cbb6Smillert 	char	*state;
16427b8cbb6Smillert 	TOKENDB_Rec tokenrec;
16527b8cbb6Smillert 	TOKEN_CBlock tmp;
16627b8cbb6Smillert 	TOKEN_CBlock cmp_text;
16727b8cbb6Smillert 	TOKEN_CBlock user_seed;
16827b8cbb6Smillert 	TOKEN_CBlock cipher_text;
1690456bce5Sjsg 	DES_key_schedule key_schedule;
17027b8cbb6Smillert 
17127b8cbb6Smillert 
172b4975d76Smarkus 	memset(cmp_text.ct, 0, sizeof(cmp_text.ct));
173b4975d76Smarkus 	memset(user_seed.ct, 0, sizeof(user_seed.ct));
174b4975d76Smarkus 	memset(cipher_text.ct, 0, sizeof(cipher_text.ct));
175b4975d76Smarkus 	memset(tokennumber.ct, 0, sizeof(tokennumber.ct));
17627b8cbb6Smillert 
177a1ac9dbdSfgsch 	(void)strtok(challenge, "\"");
17827b8cbb6Smillert 	state = strtok(NULL, "\"");
17927b8cbb6Smillert 	tmp.ul[0] = strtoul(state, NULL, 10);
18082ff3630Sguenther 	snprintf(tokennumber.ct, sizeof(tokennumber.ct), "%8.8u",tmp.ul[0]);
18127b8cbb6Smillert 
18227b8cbb6Smillert 	/*
18327b8cbb6Smillert 	 * Retrieve the db record for the user. Nuke it as soon as
18427b8cbb6Smillert 	 * we have translated out the user's shared secret just in
18527b8cbb6Smillert 	 * case we (somehow) get core dumped...
18627b8cbb6Smillert 	 */
18727b8cbb6Smillert 
18827b8cbb6Smillert 	if (tokendb_getrec(username, &tokenrec))
18927b8cbb6Smillert 		return (-1);
19027b8cbb6Smillert 
19127b8cbb6Smillert 	h2cb(tokenrec.secret, &user_seed);
192*1b7fb171Smillert 	explicit_bzero(&tokenrec.secret, sizeof(tokenrec.secret));
19327b8cbb6Smillert 
19427b8cbb6Smillert 	if (!(tokenrec.flags & TOKEN_ENABLED))
19527b8cbb6Smillert 		return (-1);
19627b8cbb6Smillert 
19727b8cbb6Smillert 	/*
19827b8cbb6Smillert 	 * Compute the anticipated response in hex. Nuke the user's
19927b8cbb6Smillert 	 * shared secret asap.
20027b8cbb6Smillert 	 */
20127b8cbb6Smillert 
2020456bce5Sjsg 	DES_fixup_key_parity(&user_seed.cb);
2030456bce5Sjsg 	DES_key_sched(&user_seed.cb, &key_schedule);
204*1b7fb171Smillert 	explicit_bzero(user_seed.ct, sizeof(user_seed.ct));
2050456bce5Sjsg 	DES_ecb_encrypt(&tokennumber.cb, &cipher_text.cb, &key_schedule,
20627b8cbb6Smillert 	    DES_ENCRYPT);
207*1b7fb171Smillert 	explicit_bzero(&key_schedule, sizeof(key_schedule));
20827b8cbb6Smillert 
20927b8cbb6Smillert 	/*
21027b8cbb6Smillert 	 * The token thinks it's descended from VAXen.  Deal with i386
21127b8cbb6Smillert 	 * endian-ness of binary cipher prior to generating ascii from first
21227b8cbb6Smillert 	 * 32 bits.
21327b8cbb6Smillert 	 */
21427b8cbb6Smillert 
21527b8cbb6Smillert 	HTONL(cipher_text.ul[0]);
21682ff3630Sguenther 	snprintf(cmp_text.ct, sizeof(cmp_text.ct), "%8.8x", cipher_text.ul[0]);
21727b8cbb6Smillert 
21827b8cbb6Smillert 	if (tokenrec.mode & TOKEN_PHONEMODE) {
21927b8cbb6Smillert 		/*
22027b8cbb6Smillert 		 * If we are a CRYPTOCard, we need to see if we are in
22127b8cbb6Smillert 		 * "telephone number mode".  If so, transmogrify the fourth
22227b8cbb6Smillert 		 * digit of the cipher.  Lower case response just in case
22327b8cbb6Smillert 		 * it's * hex.  Compare hex cipher with anticipated response
22427b8cbb6Smillert 		 * from token.
22527b8cbb6Smillert 		 */
22627b8cbb6Smillert 
22727b8cbb6Smillert 		lcase(response);
22827b8cbb6Smillert 
22927b8cbb6Smillert 		if (response[3] == '-')
23027b8cbb6Smillert 			cmp_text.ct[3] = '-';
23127b8cbb6Smillert 	}
23227b8cbb6Smillert 
23327b8cbb6Smillert 	if ((tokenrec.mode & TOKEN_HEXMODE) && !strcmp(response, cmp_text.ct))
23427b8cbb6Smillert 		return (0);
23527b8cbb6Smillert 
23627b8cbb6Smillert 	/*
23727b8cbb6Smillert 	 * No match against the computed hex cipher.  The token could be
23827b8cbb6Smillert 	 * in decimal mode.  Pervert the string to magic decimal equivalent.
23927b8cbb6Smillert 	 */
24027b8cbb6Smillert 
24127b8cbb6Smillert 	h2d(cmp_text.ct);
24227b8cbb6Smillert 
24327b8cbb6Smillert 	if ((tokenrec.mode & TOKEN_DECMODE) && !strcmp(response, cmp_text.ct))
24427b8cbb6Smillert 		return (0);
24527b8cbb6Smillert 
24627b8cbb6Smillert 	return (-1);
24727b8cbb6Smillert }
24827b8cbb6Smillert 
24927b8cbb6Smillert /*
25027b8cbb6Smillert  * Initialize a new user record in the token database.
25127b8cbb6Smillert  */
25227b8cbb6Smillert 
25327b8cbb6Smillert int
tokenuserinit(int flags,char * username,unsigned char * usecret,unsigned mode)25427b8cbb6Smillert tokenuserinit(int flags, char *username, unsigned char *usecret, unsigned mode)
25527b8cbb6Smillert {
25627b8cbb6Smillert 	TOKENDB_Rec tokenrec;
25727b8cbb6Smillert 	TOKEN_CBlock secret;
25827b8cbb6Smillert 	TOKEN_CBlock nulls;
25927b8cbb6Smillert 	TOKEN_CBlock checksum;
26027b8cbb6Smillert 	TOKEN_CBlock checktxt;
2610456bce5Sjsg 	DES_key_schedule key_schedule;
26227b8cbb6Smillert 
263a1ac9dbdSfgsch 	memset(&secret, 0, sizeof(secret));
264b4975d76Smarkus 
26527b8cbb6Smillert 	/*
26627b8cbb6Smillert 	 * If no user secret passed in, create one
26727b8cbb6Smillert 	 */
26827b8cbb6Smillert 
26927b8cbb6Smillert 	if ( (flags & TOKEN_GENSECRET) )
270b4975d76Smarkus 		tokenseed(&secret);
271b4975d76Smarkus 	else
2720456bce5Sjsg 		memcpy(&secret, usecret, sizeof(DES_cblock));
273b4975d76Smarkus 
2740456bce5Sjsg 	DES_fixup_key_parity(&secret.cb);
27527b8cbb6Smillert 
27627b8cbb6Smillert 	/*
277284c262eSjufi 	 * Check if the db record already exists.  If there's no
27827b8cbb6Smillert 	 * force-init flag and it exists, go away. Else,
27927b8cbb6Smillert 	 * create the user's db record and put to the db.
28027b8cbb6Smillert 	 */
28127b8cbb6Smillert 
28227b8cbb6Smillert 
28327b8cbb6Smillert 	if (!(flags & TOKEN_FORCEINIT) &&
28427b8cbb6Smillert 	    tokendb_getrec(username, &tokenrec) == 0)
28527b8cbb6Smillert 		return (1);
28627b8cbb6Smillert 
28727b8cbb6Smillert 	memset(&tokenrec, 0, sizeof(tokenrec));
288a79f1fdfSderaadt 	strlcpy(tokenrec.uname, username, sizeof(tokenrec.uname));
28927b8cbb6Smillert 	cb2h(secret, tokenrec.secret);
29027b8cbb6Smillert 	tokenrec.mode = 0;
29127b8cbb6Smillert 	tokenrec.flags = TOKEN_ENABLED | TOKEN_USEMODES;
29227b8cbb6Smillert 	tokenrec.mode = mode;
293686fccfdSavsm 	memset(tokenrec.reserved_char1, 0, sizeof(tokenrec.reserved_char1));
294686fccfdSavsm 	memset(tokenrec.reserved_char2, 0, sizeof(tokenrec.reserved_char2));
29527b8cbb6Smillert 
29627b8cbb6Smillert 	if (tokendb_putrec(username, &tokenrec))
29727b8cbb6Smillert 		return (-1);
29827b8cbb6Smillert 
29927b8cbb6Smillert 	/*
30027b8cbb6Smillert 	 * Check if the shared secret was generated here. If so, we
30127b8cbb6Smillert 	 * need to inform the user about it in order that it can be
30227b8cbb6Smillert 	 * programmed into the token. See tokenverify() (above) for
30327b8cbb6Smillert 	 * discussion of cipher generation.
30427b8cbb6Smillert 	 */
30527b8cbb6Smillert 
306b4975d76Smarkus 	if (!(flags & TOKEN_GENSECRET)) {
307*1b7fb171Smillert 		explicit_bzero(&secret, sizeof(secret));
30827b8cbb6Smillert 		return (0);
309b4975d76Smarkus 	}
31027b8cbb6Smillert 
31127b8cbb6Smillert 	printf("Shared secret for %s\'s token: "
31227b8cbb6Smillert 	    "%03o %03o %03o %03o %03o %03o %03o %03o\n",
31327b8cbb6Smillert 	    username, secret.cb[0], secret.cb[1], secret.cb[2], secret.cb[3],
31427b8cbb6Smillert 	    secret.cb[4], secret.cb[5], secret.cb[6], secret.cb[7]);
31527b8cbb6Smillert 
3160456bce5Sjsg 	DES_key_sched(&secret.cb, &key_schedule);
317*1b7fb171Smillert 	explicit_bzero(&secret, sizeof(secret));
31827b8cbb6Smillert 	memset(&nulls, 0, sizeof(nulls));
3190456bce5Sjsg 	DES_ecb_encrypt(&nulls.cb, &checksum.cb, &key_schedule, DES_ENCRYPT);
320*1b7fb171Smillert 	explicit_bzero(&key_schedule, sizeof(key_schedule));
32127b8cbb6Smillert 	HTONL(checksum.ul[0]);
32282ff3630Sguenther 	snprintf(checktxt.ct, sizeof(checktxt.ct), "%8.8x", checksum.ul[0]);
32327b8cbb6Smillert 	printf("Hex Checksum: \"%s\"", checktxt.ct);
32427b8cbb6Smillert 
32527b8cbb6Smillert 	h2d(checktxt.ct);
32627b8cbb6Smillert 	printf("\tDecimal Checksum: \"%s\"\n", checktxt.ct);
32727b8cbb6Smillert 
32827b8cbb6Smillert 	return (0);
32927b8cbb6Smillert }
33027b8cbb6Smillert 
33127b8cbb6Smillert /*
33227b8cbb6Smillert  * Magically transform a hex character string into a decimal character
33327b8cbb6Smillert  * string as defined by the token card vendor. The string should have
33427b8cbb6Smillert  * been lowercased by now.
33527b8cbb6Smillert  */
33627b8cbb6Smillert 
33727b8cbb6Smillert static	void
h2d(char * cp)33827b8cbb6Smillert h2d(char *cp)
33927b8cbb6Smillert {
34027b8cbb6Smillert 	int	i;
34127b8cbb6Smillert 
3420456bce5Sjsg 	for (i=0; i<sizeof(DES_cblock); i++, cp++) {
34327b8cbb6Smillert 		if (*cp >= 'a' && *cp <= 'f')
34427b8cbb6Smillert 			*cp = tt->map[*cp - 'a'];
34527b8cbb6Smillert 	}
34627b8cbb6Smillert }
34727b8cbb6Smillert 
34827b8cbb6Smillert /*
34927b8cbb6Smillert  * Translate an hex 16 byte ascii representation of an unsigned
3500456bce5Sjsg  * integer to a DES_cblock.
35127b8cbb6Smillert  */
35227b8cbb6Smillert 
35327b8cbb6Smillert static	void
h2cb(char * hp,TOKEN_CBlock * cb)35427b8cbb6Smillert h2cb(char *hp, TOKEN_CBlock *cb)
35527b8cbb6Smillert {
35627b8cbb6Smillert 	char	scratch[9];
35727b8cbb6Smillert 
35827b8cbb6Smillert 	strlcpy(scratch, hp, sizeof(scratch));
35927b8cbb6Smillert 	cb->ul[0] = strtoul(scratch, NULL, 16);
36027b8cbb6Smillert 
36127b8cbb6Smillert 	strlcpy(scratch, hp + 8, sizeof(scratch));
36227b8cbb6Smillert 	cb->ul[1] = strtoul(scratch, NULL, 16);
36327b8cbb6Smillert }
36427b8cbb6Smillert 
36527b8cbb6Smillert /*
3660456bce5Sjsg  * Translate a DES_cblock to an 16 byte ascii hex representation.
36727b8cbb6Smillert  */
36827b8cbb6Smillert 
36927b8cbb6Smillert static	void
cb2h(TOKEN_CBlock cb,char * hp)37027b8cbb6Smillert cb2h(TOKEN_CBlock cb, char* hp)
37127b8cbb6Smillert {
37227b8cbb6Smillert 	char	scratch[17];
37327b8cbb6Smillert 
37482ff3630Sguenther 	snprintf(scratch,   9, "%8.8x", cb.ul[0]);
37582ff3630Sguenther 	snprintf(scratch+8, 9, "%8.8x", cb.ul[1]);
37627b8cbb6Smillert 	memcpy(hp, scratch, 16);
37727b8cbb6Smillert }
37827b8cbb6Smillert 
37927b8cbb6Smillert /*
38027b8cbb6Smillert  * Lowercase possible hex response
38127b8cbb6Smillert  */
38227b8cbb6Smillert 
38327b8cbb6Smillert static	void
lcase(char * cp)38427b8cbb6Smillert lcase(char *cp)
38527b8cbb6Smillert {
38627b8cbb6Smillert 	while (*cp) {
3874207a9b6Sderaadt 		if (isupper((unsigned char)*cp))
3884207a9b6Sderaadt 			*cp = tolower((unsigned char)*cp);
38927b8cbb6Smillert 		cp++;
39027b8cbb6Smillert 	}
39127b8cbb6Smillert }
392