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