xref: /dragonfly/contrib/dhcpcd/src/auth.c (revision 16dd80e4)
1 /*
2  * dhcpcd - DHCP client daemon
3  * Copyright (c) 2006-2018 Roy Marples <roy@marples.name>
4  * All rights reserved
5 
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25  * SUCH DAMAGE.
26  */
27 
28 #include <sys/file.h>
29 #include <errno.h>
30 #include <fcntl.h>
31 #include <inttypes.h>
32 #include <stddef.h>
33 #include <stdlib.h>
34 #include <string.h>
35 #include <time.h>
36 #include <unistd.h>
37 
38 #include "config.h"
39 #include "auth.h"
40 #include "dhcp.h"
41 #include "dhcp6.h"
42 #include "dhcpcd.h"
43 
44 #ifdef HAVE_HMAC_H
45 #include <hmac.h>
46 #endif
47 
48 #ifdef __sun
49 #define htonll
50 #define ntohll
51 #endif
52 
53 #ifndef htonll
54 #if (BYTE_ORDER == LITTLE_ENDIAN)
55 #define	htonll(x)	((uint64_t)htonl((uint32_t)((x) >> 32)) | \
56 			 (uint64_t)htonl((uint32_t)((x) & 0x00000000ffffffffULL)) << 32)
57 #else	/* (BYTE_ORDER == LITTLE_ENDIAN) */
58 #define	htonll(x)	(x)
59 #endif
60 #endif  /* htonll */
61 
62 #ifndef ntohll
63 #if (BYTE_ORDER == LITTLE_ENDIAN)
64 #define	ntohll(x)	((uint64_t)ntohl((uint32_t)((x) >> 32)) | \
65 			 (uint64_t)ntohl((uint32_t)((x) & 0x00000000ffffffffULL)) << 32)
66 #else	/* (BYTE_ORDER == LITTLE_ENDIAN) */
67 #define	ntohll(x)	(x)
68 #endif
69 #endif  /* ntohll */
70 
71 #define HMAC_LENGTH	16
72 
73 void
74 dhcp_auth_reset(struct authstate *state)
75 {
76 
77 	state->replay = 0;
78 	if (state->token) {
79 		free(state->token->key);
80 		free(state->token->realm);
81 		free(state->token);
82 		state->token = NULL;
83 	}
84 	if (state->reconf) {
85 		free(state->reconf->key);
86 		free(state->reconf->realm);
87 		free(state->reconf);
88 		state->reconf = NULL;
89 	}
90 }
91 
92 /*
93  * Authenticate a DHCP message.
94  * m and mlen refer to the whole message.
95  * t is the DHCP type, pass it 4 or 6.
96  * data and dlen refer to the authentication option within the message.
97  */
98 const struct token *
99 dhcp_auth_validate(struct authstate *state, const struct auth *auth,
100     const void *vm, size_t mlen, int mp,  int mt,
101     const void *vdata, size_t dlen)
102 {
103 	const uint8_t *m, *data;
104 	uint8_t protocol, algorithm, rdm, *mm, type;
105 	uint64_t replay;
106 	uint32_t secretid;
107 	const uint8_t *d, *realm;
108 	size_t realm_len;
109 	const struct token *t;
110 	time_t now;
111 	uint8_t hmac_code[HMAC_LENGTH];
112 
113 	if (dlen < 3 + sizeof(replay)) {
114 		errno = EINVAL;
115 		return NULL;
116 	}
117 
118 	m = vm;
119 	data = vdata;
120 	/* Ensure that d is inside m which *may* not be the case for DHPCPv4 */
121 	if (data < m || data > m + mlen || data + dlen > m + mlen) {
122 		errno = ERANGE;
123 		return NULL;
124 	}
125 
126 	d = data;
127 	protocol = *d++;
128 	algorithm = *d++;
129 	rdm = *d++;
130 	if (!(auth->options & DHCPCD_AUTH_SEND)) {
131 		/* If we didn't send any authorisation, it can only be a
132 		 * reconfigure key */
133 		if (protocol != AUTH_PROTO_RECONFKEY) {
134 			errno = EINVAL;
135 			return NULL;
136 		}
137 	} else if (protocol != auth->protocol ||
138 		    algorithm != auth->algorithm ||
139 		    rdm != auth->rdm)
140 	{
141 		/* As we don't require authentication, we should still
142 		 * accept a reconfigure key */
143 		if (protocol != AUTH_PROTO_RECONFKEY ||
144 		    auth->options & DHCPCD_AUTH_REQUIRE)
145 		{
146 			errno = EPERM;
147 			return NULL;
148 		}
149 	}
150 	dlen -= 3;
151 
152 	memcpy(&replay, d, sizeof(replay));
153 	replay = ntohll(replay);
154 	/*
155 	 * Test for a replay attack.
156 	 *
157 	 * NOTE: Some servers always send a replay data value of zero.
158 	 * This is strictly compliant with RFC 3315 and 3318 which say:
159 	 * "If the RDM field contains 0x00, the replay detection field MUST be
160 	 *    set to the value of a monotonically increasing counter."
161 	 * An example of a monotonically increasing sequence is:
162 	 * 1, 2, 2, 2, 2, 2, 2
163 	 * Errata 3474 updates RFC 3318 to say:
164 	 * "If the RDM field contains 0x00, the replay detection field MUST be
165 	 *    set to the value of a strictly increasing counter."
166 	 *
167 	 * Taking the above into account, dhcpcd will only test for
168 	 * strictly speaking replay attacks if it receives any non zero
169 	 * replay data to validate against.
170 	 */
171 	if (state->token && state->replay != 0) {
172 		if (state->replay == (replay ^ 0x8000000000000000ULL)) {
173 			/* We don't know if the singular point is increasing
174 			 * or decreasing. */
175 			errno = EPERM;
176 			return NULL;
177 		}
178 		if ((uint64_t)(replay - state->replay) <= 0) {
179 			/* Replay attack detected */
180 			errno = EPERM;
181 			return NULL;
182 		}
183 	}
184 	d+= sizeof(replay);
185 	dlen -= sizeof(replay);
186 
187 	realm = NULL;
188 	realm_len = 0;
189 
190 	/* Extract realm and secret.
191 	 * Rest of data is MAC. */
192 	switch (protocol) {
193 	case AUTH_PROTO_TOKEN:
194 		secretid = auth->token_rcv_secretid;
195 		break;
196 	case AUTH_PROTO_DELAYED:
197 		if (dlen < sizeof(secretid) + sizeof(hmac_code)) {
198 			errno = EINVAL;
199 			return NULL;
200 		}
201 		memcpy(&secretid, d, sizeof(secretid));
202 		secretid = ntohl(secretid);
203 		d += sizeof(secretid);
204 		dlen -= sizeof(secretid);
205 		break;
206 	case AUTH_PROTO_DELAYEDREALM:
207 		if (dlen < sizeof(secretid) + sizeof(hmac_code)) {
208 			errno = EINVAL;
209 			return NULL;
210 		}
211 		realm_len = dlen - (sizeof(secretid) + sizeof(hmac_code));
212 		if (realm_len) {
213 			realm = d;
214 			d += realm_len;
215 			dlen -= realm_len;
216 		}
217 		memcpy(&secretid, d, sizeof(secretid));
218 		secretid = ntohl(secretid);
219 		d += sizeof(secretid);
220 		dlen -= sizeof(secretid);
221 		break;
222 	case AUTH_PROTO_RECONFKEY:
223 		if (dlen != 1 + 16) {
224 			errno = EINVAL;
225 			return NULL;
226 		}
227 		type = *d++;
228 		dlen--;
229 		switch (type) {
230 		case 1:
231 			if ((mp == 4 && mt == DHCP_ACK) ||
232 			    (mp == 6 && mt == DHCP6_REPLY))
233 			{
234 				if (state->reconf == NULL) {
235 					state->reconf =
236 					    malloc(sizeof(*state->reconf));
237 					if (state->reconf == NULL)
238 						return NULL;
239 					state->reconf->key = malloc(16);
240 					if (state->reconf->key == NULL) {
241 						free(state->reconf);
242 						state->reconf = NULL;
243 						return NULL;
244 					}
245 					state->reconf->secretid = 0;
246 					state->reconf->expire = 0;
247 					state->reconf->realm = NULL;
248 					state->reconf->realm_len = 0;
249 					state->reconf->key_len = 16;
250 				}
251 				memcpy(state->reconf->key, d, 16);
252 			} else {
253 				errno = EINVAL;
254 				return NULL;
255 			}
256 			if (state->reconf == NULL)
257 				errno = ENOENT;
258 			/* Free the old token so we log acceptance */
259 			if (state->token) {
260 				free(state->token);
261 				state->token = NULL;
262 			}
263 			/* Nothing to validate, just accepting the key */
264 			return state->reconf;
265 		case 2:
266 			if (!((mp == 4 && mt == DHCP_FORCERENEW) ||
267 			    (mp == 6 && mt == DHCP6_RECONFIGURE)))
268 			{
269 				errno = EINVAL;
270 				return NULL;
271 			}
272 			if (state->reconf == NULL) {
273 				errno = ENOENT;
274 				return NULL;
275 			}
276 			t = state->reconf;
277 			goto gottoken;
278 		default:
279 			errno = EINVAL;
280 			return NULL;
281 		}
282 	default:
283 		errno = ENOTSUP;
284 		return NULL;
285 	}
286 
287 	/* Find a token for the realm and secret */
288 	TAILQ_FOREACH(t, &auth->tokens, next) {
289 		if (t->secretid == secretid &&
290 		    t->realm_len == realm_len &&
291 		    (t->realm_len == 0 ||
292 		    memcmp(t->realm, realm, t->realm_len) == 0))
293 			break;
294 	}
295 	if (t == NULL) {
296 		errno = ESRCH;
297 		return NULL;
298 	}
299 	if (t->expire) {
300 		if (time(&now) == -1)
301 			return NULL;
302 		if (t->expire < now) {
303 			errno = EFAULT;
304 			return NULL;
305 		}
306 	}
307 
308 gottoken:
309 	/* First message from the server */
310 	if (state->token &&
311 	    (state->token->secretid != t->secretid ||
312 	    state->token->realm_len != t->realm_len ||
313 	    memcmp(state->token->realm, t->realm, t->realm_len)))
314 	{
315 		errno = EPERM;
316 		return NULL;
317 	}
318 
319 	/* Special case as no hashing needs to be done. */
320 	if (protocol == AUTH_PROTO_TOKEN) {
321 		if (dlen != t->key_len || memcmp(d, t->key, dlen)) {
322 			errno = EPERM;
323 			return NULL;
324 		}
325 		goto finish;
326 	}
327 
328 	/* Make a duplicate of the message, but zero out the MAC part */
329 	mm = malloc(mlen);
330 	if (mm == NULL)
331 		return NULL;
332 	memcpy(mm, m, mlen);
333 	memset(mm + (d - m), 0, dlen);
334 
335 	/* RFC3318, section 5.2 - zero giaddr and hops */
336 	if (mp == 4) {
337 		/* Assert the bootp structure is correct size. */
338 		__CTASSERT(sizeof(struct bootp) == 300);
339 
340 		*(mm + offsetof(struct bootp, hops)) = '\0';
341 		memset(mm + offsetof(struct bootp, giaddr), 0, 4);
342 	}
343 
344 	memset(hmac_code, 0, sizeof(hmac_code));
345 	switch (algorithm) {
346 	case AUTH_ALG_HMAC_MD5:
347 		hmac("md5", t->key, t->key_len, mm, mlen,
348 		     hmac_code, sizeof(hmac_code));
349 		break;
350 	default:
351 		errno = ENOSYS;
352 		free(mm);
353 		return NULL;
354 	}
355 
356 	free(mm);
357 	if (memcmp(d, &hmac_code, dlen)) {
358 		errno = EPERM;
359 		return NULL;
360 	}
361 
362 finish:
363 	/* If we got here then authentication passed */
364 	state->replay = replay;
365 	if (state->token == NULL) {
366 		/* We cannot just save a pointer because a reconfigure will
367 		 * recreate the token list. So we duplicate it. */
368 		state->token = malloc(sizeof(*state->token));
369 		if (state->token) {
370 			state->token->secretid = t->secretid;
371 			state->token->key = malloc(t->key_len);
372 			if (state->token->key) {
373 				state->token->key_len = t->key_len;
374 				memcpy(state->token->key, t->key, t->key_len);
375 			} else {
376 				free(state->token);
377 				state->token = NULL;
378 				return NULL;
379 			}
380 			if (t->realm_len) {
381 				state->token->realm = malloc(t->realm_len);
382 				if (state->token->realm) {
383 					state->token->realm_len = t->realm_len;
384 					memcpy(state->token->realm, t->realm,
385 					    t->realm_len);
386 				} else {
387 					free(state->token->key);
388 					free(state->token);
389 					state->token = NULL;
390 					return NULL;
391 				}
392 			} else {
393 				state->token->realm = NULL;
394 				state->token->realm_len = 0;
395 			}
396 		}
397 		/* If we cannot save the token, we must invalidate */
398 		if (state->token == NULL)
399 			return NULL;
400 	}
401 
402 	return t;
403 }
404 
405 static uint64_t
406 get_next_rdm_monotonic_counter(struct auth *auth)
407 {
408 	FILE *fp;
409 	uint64_t rdm;
410 #ifdef LOCK_EX
411 	int flocked;
412 #endif
413 
414 	fp = fopen(RDM_MONOFILE, "r+");
415 	if (fp == NULL) {
416 		if (errno != ENOENT)
417 			return ++auth->last_replay; /* report error? */
418 		fp = fopen(RDM_MONOFILE, "w");
419 		if (fp == NULL)
420 			return ++auth->last_replay; /* report error? */
421 #ifdef LOCK_EX
422 		flocked = flock(fileno(fp), LOCK_EX);
423 #endif
424 		rdm = 0;
425 	} else {
426 #ifdef LOCK_EX
427 		flocked = flock(fileno(fp), LOCK_EX);
428 #endif
429 		if (fscanf(fp, "0x%016" PRIu64, &rdm) != 1)
430 			rdm = 0; /* truncated? report error? */
431 	}
432 
433 	rdm++;
434 	if (fseek(fp, 0, SEEK_SET) == -1 ||
435 	    ftruncate(fileno(fp), 0) == -1 ||
436 	    fprintf(fp, "0x%016" PRIu64 "\n", rdm) != 19 ||
437 	    fflush(fp) == EOF)
438 	{
439 		if (!auth->last_replay_set) {
440 			auth->last_replay = rdm;
441 			auth->last_replay_set = 1;
442 		} else
443 			rdm = ++auth->last_replay;
444 		/* report error? */
445 	}
446 #ifdef LOCK_EX
447 	if (flocked == 0)
448 		flock(fileno(fp), LOCK_UN);
449 #endif
450 	fclose(fp);
451 	return rdm;
452 }
453 
454 #define	NTP_EPOCH	2208988800U	/* 1970 - 1900 in seconds */
455 #define	NTP_SCALE_FRAC	4294967295.0	/* max value of the fractional part */
456 static uint64_t
457 get_next_rdm_monotonic_clock(struct auth *auth)
458 {
459 	struct timespec ts;
460 	uint64_t secs, rdm;
461 	double frac;
462 
463 	if (clock_gettime(CLOCK_REALTIME, &ts) != 0)
464 		return ++auth->last_replay; /* report error? */
465 
466 	secs = (uint64_t)ts.tv_sec + NTP_EPOCH;
467 	frac = ((double)ts.tv_nsec / 1e9 * NTP_SCALE_FRAC);
468 	rdm = (secs << 32) | (uint64_t)frac;
469 	return rdm;
470 }
471 
472 static uint64_t
473 get_next_rdm_monotonic(struct auth *auth)
474 {
475 
476 	if (auth->options & DHCPCD_AUTH_RDM_COUNTER)
477 		return get_next_rdm_monotonic_counter(auth);
478 	return get_next_rdm_monotonic_clock(auth);
479 }
480 
481 /*
482  * Encode a DHCP message.
483  * Either we know which token to use from the server response
484  * or we are using a basic configuration token.
485  * token is the token to encrypt with.
486  * m and mlen refer to the whole message.
487  * mp is the DHCP type, pass it 4 or 6.
488  * mt is the DHCP message type.
489  * data and dlen refer to the authentication option within the message.
490  */
491 ssize_t
492 dhcp_auth_encode(struct auth *auth, const struct token *t,
493     void *vm, size_t mlen, int mp, int mt,
494     void *vdata, size_t dlen)
495 {
496 	uint64_t rdm;
497 	uint8_t hmac_code[HMAC_LENGTH];
498 	time_t now;
499 	uint8_t hops, *p, *m, *data;
500 	uint32_t giaddr, secretid;
501 	bool auth_info;
502 
503 	/* Ignore the token argument given to us - always send using the
504 	 * configured token. */
505 	if (auth->protocol == AUTH_PROTO_TOKEN) {
506 		TAILQ_FOREACH(t, &auth->tokens, next) {
507 			if (t->secretid == auth->token_snd_secretid)
508 				break;
509 		}
510 		if (t == NULL) {
511 			errno = EINVAL;
512 			return -1;
513 		}
514 		if (t->expire) {
515 			if (time(&now) == -1)
516 				return -1;
517 			if (t->expire < now) {
518 				errno = EPERM;
519 				return -1;
520 			}
521 		}
522 	}
523 
524 	switch(auth->protocol) {
525 	case AUTH_PROTO_TOKEN:
526 	case AUTH_PROTO_DELAYED:
527 	case AUTH_PROTO_DELAYEDREALM:
528 		/* We don't ever send a reconf key */
529 		break;
530 	default:
531 		errno = ENOTSUP;
532 		return -1;
533 	}
534 
535 	switch(auth->algorithm) {
536 	case AUTH_ALG_NONE:
537 	case AUTH_ALG_HMAC_MD5:
538 		break;
539 	default:
540 		errno = ENOTSUP;
541 		return -1;
542 	}
543 
544 	switch(auth->rdm) {
545 	case AUTH_RDM_MONOTONIC:
546 		break;
547 	default:
548 		errno = ENOTSUP;
549 		return -1;
550 	}
551 
552 	/* DISCOVER or INFORM messages don't write auth info */
553 	if ((mp == 4 && (mt == DHCP_DISCOVER || mt == DHCP_INFORM)) ||
554 	    (mp == 6 && (mt == DHCP6_SOLICIT || mt == DHCP6_INFORMATION_REQ)))
555 		auth_info = false;
556 	else
557 		auth_info = true;
558 
559 	/* Work out the auth area size.
560 	 * We only need to do this for DISCOVER messages */
561 	if (vdata == NULL) {
562 		dlen = 1 + 1 + 1 + 8;
563 		switch(auth->protocol) {
564 		case AUTH_PROTO_TOKEN:
565 			dlen += t->key_len;
566 			break;
567 		case AUTH_PROTO_DELAYEDREALM:
568 			if (auth_info && t)
569 				dlen += t->realm_len;
570 			/* FALLTHROUGH */
571 		case AUTH_PROTO_DELAYED:
572 			if (auth_info && t)
573 				dlen += sizeof(t->secretid) + sizeof(hmac_code);
574 			break;
575 		}
576 		return (ssize_t)dlen;
577 	}
578 
579 	if (dlen < 1 + 1 + 1 + 8) {
580 		errno = ENOBUFS;
581 		return -1;
582 	}
583 
584 	/* Ensure that d is inside m which *may* not be the case for DHPCPv4 */
585 	m = vm;
586 	data = vdata;
587 	if (data < m || data > m + mlen || data + dlen > m + mlen) {
588 		errno = ERANGE;
589 		return -1;
590 	}
591 
592 	/* Write out our option */
593 	*data++ = auth->protocol;
594 	*data++ = auth->algorithm;
595 	/*
596 	 * RFC 3315 21.4.4.1 says that SOLICIT in DELAYED authentication
597 	 * should not set RDM or it's data.
598 	 * An expired draft draft-ietf-dhc-dhcpv6-clarify-auth-01 suggets
599 	 * this should not be set for INFORMATION REQ messages as well,
600 	 * which is probably a good idea because both states start from zero.
601 	 */
602 	if (auth_info ||
603 	    !(auth->protocol & (AUTH_PROTO_DELAYED | AUTH_PROTO_DELAYEDREALM)))
604 	{
605 		*data++ = auth->rdm;
606 		switch (auth->rdm) {
607 		case AUTH_RDM_MONOTONIC:
608 			rdm = get_next_rdm_monotonic(auth);
609 			break;
610 		default:
611 			/* This block appeases gcc, clang doesn't need it */
612 			rdm = get_next_rdm_monotonic(auth);
613 			break;
614 		}
615 		rdm = htonll(rdm);
616 		memcpy(data, &rdm, 8);
617 	} else {
618 		*data++ = 0;		/* rdm */
619 		memset(data, 0, 8);	/* replay detection data */
620 	}
621 	data += 8;
622 	dlen -= 1 + 1 + 1 + 8;
623 
624 	/* Special case as no hashing needs to be done. */
625 	if (auth->protocol == AUTH_PROTO_TOKEN) {
626 		/* Should be impossible, but still */
627 		if (t == NULL) {
628 			errno = EINVAL;
629 			return -1;
630 		}
631 		if (dlen < t->key_len) {
632 			errno =	ENOBUFS;
633 			return -1;
634 		}
635 		memcpy(data, t->key, t->key_len);
636 		return (ssize_t)(dlen - t->key_len);
637 	}
638 
639 	/* DISCOVER or INFORM messages don't write auth info */
640 	if (!auth_info)
641 		return (ssize_t)dlen;
642 
643 	/* Loading a saved lease without an authentication option */
644 	if (t == NULL)
645 		return 0;
646 
647 	/* Write out the Realm */
648 	if (auth->protocol == AUTH_PROTO_DELAYEDREALM) {
649 		if (dlen < t->realm_len) {
650 			errno = ENOBUFS;
651 			return -1;
652 		}
653 		memcpy(data, t->realm, t->realm_len);
654 		data += t->realm_len;
655 		dlen -= t->realm_len;
656 	}
657 
658 	/* Write out the SecretID */
659 	if (auth->protocol == AUTH_PROTO_DELAYED ||
660 	    auth->protocol == AUTH_PROTO_DELAYEDREALM)
661 	{
662 		if (dlen < sizeof(t->secretid)) {
663 			errno = ENOBUFS;
664 			return -1;
665 		}
666 		secretid = htonl(t->secretid);
667 		memcpy(data, &secretid, sizeof(secretid));
668 		data += sizeof(secretid);
669 		dlen -= sizeof(secretid);
670 	}
671 
672 	/* Zero what's left, the MAC */
673 	memset(data, 0, dlen);
674 
675 	/* RFC3318, section 5.2 - zero giaddr and hops */
676 	if (mp == 4) {
677 		p = m + offsetof(struct bootp, hops);
678 		hops = *p;
679 		*p = '\0';
680 		p = m + offsetof(struct bootp, giaddr);
681 		memcpy(&giaddr, p, sizeof(giaddr));
682 		memset(p, 0, sizeof(giaddr));
683 	} else {
684 		/* appease GCC again */
685 		hops = 0;
686 		giaddr = 0;
687 	}
688 
689 	/* Create our hash and write it out */
690 	switch(auth->algorithm) {
691 	case AUTH_ALG_HMAC_MD5:
692 		hmac("md5", t->key, t->key_len, m, mlen,
693 		     hmac_code, sizeof(hmac_code));
694 		memcpy(data, hmac_code, sizeof(hmac_code));
695 		break;
696 	}
697 
698 	/* RFC3318, section 5.2 - restore giaddr and hops */
699 	if (mp == 4) {
700 		p = m + offsetof(struct bootp, hops);
701 		*p = hops;
702 		p = m + offsetof(struct bootp, giaddr);
703 		memcpy(p, &giaddr, sizeof(giaddr));
704 	}
705 
706 	/* Done! */
707 	return (int)(dlen - sizeof(hmac_code)); /* should be zero */
708 }
709