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
dhcp_auth_reset(struct authstate * state)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 *
dhcp_auth_validate(struct authstate * state,const struct auth * auth,const void * vm,size_t mlen,int mp,int mt,const void * vdata,size_t dlen)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
auth_get_rdm_monotonic(uint64_t * rdm)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
get_next_rdm_monotonic_clock(struct auth * auth)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
get_next_rdm_monotonic(struct dhcpcd_ctx * ctx,struct auth * auth)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
dhcp_auth_encode(struct dhcpcd_ctx * ctx,struct auth * auth,const struct token * t,void * vm,size_t mlen,int mp,int mt,void * vdata,size_t dlen)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