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