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