log.c revision 1.53
1/* $OpenBSD: log.c,v 1.53 2005/04/08 16:14:04 deraadt Exp $ */ 2/* $EOM: log.c,v 1.30 2000/09/29 08:19:23 niklas Exp $ */ 3 4/* 5 * Copyright (c) 1998, 1999, 2001 Niklas Hallqvist. All rights reserved. 6 * Copyright (c) 1999, 2000, 2001, 2003 H�kan Olsson. All rights reserved. 7 * 8 * Redistribution and use in source and binary forms, with or without 9 * modification, are permitted provided that the following conditions 10 * are met: 11 * 1. Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * 2. Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in the 15 * documentation and/or other materials provided with the distribution. 16 * 17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 */ 28 29/* 30 * This code was written under funding by Ericsson Radio Systems. 31 */ 32 33#include <sys/types.h> 34#include <sys/time.h> 35 36#ifdef USE_DEBUG 37#include <sys/socket.h> 38#include <sys/stat.h> 39#include <sys/uio.h> 40#include <netinet/in.h> 41#include <netinet/in_systm.h> 42#include <netinet/ip.h> 43#include <netinet/ip6.h> 44#include <netinet/udp.h> 45#include <arpa/inet.h> 46 47#include <pcap.h> 48 49#endif /* USE_DEBUG */ 50 51#include <errno.h> 52#include <stdio.h> 53#include <stdlib.h> 54#include <string.h> 55#include <syslog.h> 56#include <stdarg.h> 57#include <unistd.h> 58 59#include "conf.h" 60#include "isakmp_num.h" 61#include "log.h" 62#include "monitor.h" 63#include "util.h" 64 65static void _log_print(int, int, const char *, va_list, int, int); 66 67static FILE *log_output; 68 69int verbose_logging = 0; 70#if defined (USE_DEBUG) 71static int log_level[LOG_ENDCLASS]; 72 73#define TCPDUMP_MAGIC 0xa1b2c3d4 74#define SNAPLEN (64 * 1024) 75 76struct packhdr { 77 struct pcap_pkthdr pcap;/* pcap file packet header */ 78 u_int32_t sa_family; /* address family */ 79 union { 80 struct ip ip4; /* IPv4 header (w/o options) */ 81 struct ip6_hdr ip6; /* IPv6 header */ 82 } ip; 83}; 84 85struct isakmp_hdr { 86 u_int8_t icookie[8], rcookie[8]; 87 u_int8_t next, ver, type, flags; 88 u_int32_t msgid, len; 89}; 90 91static char *pcaplog_file = NULL; 92static FILE *packet_log; 93static u_int8_t *packet_buf = NULL; 94 95static int udp_cksum(struct packhdr *, const struct udphdr *, 96 u_int16_t *); 97static u_int16_t in_cksum(const u_int16_t *, int); 98#endif /* USE_DEBUG */ 99 100void 101log_init(int debug) 102{ 103 if (debug) 104 log_output = stderr; 105 else 106 log_to(0); /* syslog */ 107} 108 109void 110log_reinit(void) 111{ 112 struct conf_list *logging; 113#ifdef USE_DEBUG 114 struct conf_list_node *logclass; 115 int class, level; 116#endif /* USE_DEBUG */ 117 118 logging = conf_get_list("General", "Logverbose"); 119 if (logging) { 120 verbose_logging = 1; 121 conf_free_list(logging); 122 } 123#ifdef USE_DEBUG 124 logging = conf_get_list("General", "Loglevel"); 125 if (!logging) 126 return; 127 128 for (logclass = TAILQ_FIRST(&logging->fields); logclass; 129 logclass = TAILQ_NEXT(logclass, link)) { 130 if (sscanf(logclass->field, "%d=%d", &class, &level) != 2) { 131 if (sscanf(logclass->field, "A=%d", &level) == 1) 132 for (class = 0; class < LOG_ENDCLASS; class++) 133 log_debug_cmd(class, level); 134 else { 135 log_print("init: invalid logging class or " 136 "level: %s", logclass->field); 137 continue; 138 } 139 } else 140 log_debug_cmd(class, level); 141 } 142 conf_free_list(logging); 143#endif /* USE_DEBUG */ 144} 145 146void 147log_to(FILE *f) 148{ 149 if (!log_output && f) 150 closelog(); 151 log_output = f; 152 if (!f) 153 openlog("isakmpd", LOG_PID | LOG_CONS, LOG_DAEMON); 154} 155 156FILE * 157log_current(void) 158{ 159 return log_output; 160} 161 162static char * 163_log_get_class(int error_class) 164{ 165 /* XXX For test purposes. To be removed later on? */ 166 static char *class_text[] = LOG_CLASSES_TEXT; 167 168 if (error_class < 0) 169 return "Dflt"; 170 else if (error_class >= LOG_ENDCLASS) 171 return "Unkn"; 172 else 173 return class_text[error_class]; 174} 175 176static void 177_log_print(int error, int syslog_level, const char *fmt, va_list ap, 178 int class, int level) 179{ 180 char buffer[LOG_SIZE], nbuf[LOG_SIZE + 32]; 181 static const char fallback_msg[] = 182 "write to log file failed (errno %d), redirecting to syslog"; 183 int len; 184 struct tm *tm; 185 struct timeval now; 186 time_t t; 187 188 len = vsnprintf(buffer, sizeof buffer, fmt, ap); 189 if (len > 0 && len < (int) sizeof buffer - 1 && error) 190 snprintf(buffer + len, sizeof buffer - len, ": %s", 191 strerror(errno)); 192 if (log_output) { 193 gettimeofday(&now, 0); 194 t = now.tv_sec; 195 tm = localtime(&t); 196 if (class >= 0) 197 snprintf(nbuf, sizeof nbuf, 198 "%02d%02d%02d.%06ld %s %02d ", 199 tm->tm_hour, tm->tm_min, tm->tm_sec, now.tv_usec, 200 _log_get_class(class), level); 201 else /* LOG_PRINT (-1) or LOG_REPORT (-2) */ 202 snprintf(nbuf, sizeof nbuf, "%02d%02d%02d.%06ld %s ", 203 tm->tm_hour, tm->tm_min, tm->tm_sec, now.tv_usec, 204 class == LOG_PRINT ? "Default" : "Report>"); 205 strlcat(nbuf, buffer, sizeof nbuf); 206#if defined (USE_PRIVSEP) 207 strlcat(nbuf, getuid() ? "" : " [priv]", LOG_SIZE + 32); 208#endif 209 strlcat(nbuf, "\n", sizeof nbuf); 210 211 if (fwrite(nbuf, strlen(nbuf), 1, log_output) == 0) { 212 /* Report fallback. */ 213 syslog(LOG_ALERT, fallback_msg, errno); 214 fprintf(log_output, fallback_msg, errno); 215 216 /* 217 * Close log_output to prevent isakmpd from locking 218 * the file. We may need to explicitly close stdout 219 * to do this properly. 220 * XXX - Figure out how to match two FILE *'s and 221 * rewrite. 222 */ 223 if (fileno(log_output) != -1 && 224 fileno(stdout) == fileno(log_output)) 225 fclose(stdout); 226 fclose(log_output); 227 228 /* Fallback to syslog. */ 229 log_to(0); 230 231 /* (Re)send current message to syslog(). */ 232 syslog(class == LOG_REPORT ? LOG_ALERT : 233 syslog_level, "%s", buffer); 234 } 235 } else 236 syslog(class == LOG_REPORT ? LOG_ALERT : syslog_level, "%s", 237 buffer); 238} 239 240#ifdef USE_DEBUG 241void 242log_debug(int cls, int level, const char *fmt, ...) 243{ 244 va_list ap; 245 246 /* 247 * If we are not debugging this class, or the level is too low, just 248 * return. 249 */ 250 if (cls >= 0 && (log_level[cls] == 0 || level > log_level[cls])) 251 return; 252 va_start(ap, fmt); 253 _log_print(0, LOG_INFO, fmt, ap, cls, level); 254 va_end(ap); 255} 256 257void 258log_debug_buf(int cls, int level, const char *header, const u_int8_t *buf, 259 size_t sz) 260{ 261 size_t i, j; 262 char s[73]; 263 264 /* 265 * If we are not debugging this class, or the level is too low, just 266 * return. 267 */ 268 if (cls >= 0 && (log_level[cls] == 0 || level > log_level[cls])) 269 return; 270 271 log_debug(cls, level, "%s:", header); 272 for (i = j = 0; i < sz;) { 273 snprintf(s + j, sizeof s - j, "%02x", buf[i++]); 274 j += 2; 275 if (i % 4 == 0) { 276 if (i % 32 == 0) { 277 s[j] = '\0'; 278 log_debug(cls, level, "%s", s); 279 j = 0; 280 } else 281 s[j++] = ' '; 282 } 283 } 284 if (j) { 285 s[j] = '\0'; 286 log_debug(cls, level, "%s", s); 287 } 288} 289 290void 291log_debug_cmd(int cls, int level) 292{ 293 if (cls < 0 || cls >= LOG_ENDCLASS) { 294 log_print("log_debug_cmd: invalid debugging class %d", cls); 295 return; 296 } 297 if (level < 0) { 298 log_print("log_debug_cmd: invalid debugging level %d for " 299 "class %d", level, cls); 300 return; 301 } 302 if (level == log_level[cls]) 303 log_print("log_debug_cmd: log level unchanged for class %d", 304 cls); 305 else { 306 log_print("log_debug_cmd: log level changed from %d to %d " 307 "for class %d", log_level[cls], level, cls); 308 log_level[cls] = level; 309 } 310} 311 312void 313log_debug_toggle(void) 314{ 315 static int log_level_copy[LOG_ENDCLASS], toggle = 0; 316 317 if (!toggle) { 318 LOG_DBG((LOG_MISC, 50, "log_debug_toggle: " 319 "debug levels cleared")); 320 memcpy(&log_level_copy, &log_level, sizeof log_level); 321 bzero(&log_level, sizeof log_level); 322 } else { 323 memcpy(&log_level, &log_level_copy, sizeof log_level); 324 LOG_DBG((LOG_MISC, 50, "log_debug_toggle: " 325 "debug levels restored")); 326 } 327 toggle = !toggle; 328} 329#endif /* USE_DEBUG */ 330 331void 332log_print(const char *fmt, ...) 333{ 334 va_list ap; 335 336 va_start(ap, fmt); 337 _log_print(0, LOG_NOTICE, fmt, ap, LOG_PRINT, 0); 338 va_end(ap); 339} 340 341void 342log_verbose(const char *fmt, ...) 343{ 344 va_list ap; 345#ifdef USE_DEBUG 346 int i; 347#endif /* USE_DEBUG */ 348 349 if (verbose_logging == 0) 350 return; 351 352#ifdef USE_DEBUG 353 for (i = 0; i < LOG_ENDCLASS; i++) 354 if (log_level[i] > 0) 355 return; 356#endif 357 358 va_start(ap, fmt); 359 _log_print(0, LOG_NOTICE, fmt, ap, LOG_PRINT, 0); 360 va_end(ap); 361} 362 363void 364log_error(const char *fmt, ...) 365{ 366 va_list ap; 367 368 va_start(ap, fmt); 369 _log_print(1, LOG_ERR, fmt, ap, LOG_PRINT, 0); 370 va_end(ap); 371} 372 373void 374log_fatal(const char *fmt, ...) 375{ 376 va_list ap; 377 378 va_start(ap, fmt); 379 _log_print(1, LOG_CRIT, fmt, ap, LOG_PRINT, 0); 380 va_end(ap); 381#ifdef USE_PRIVSEP 382 monitor_exit(1); 383#else 384 exit(1); 385#endif 386} 387 388#ifdef USE_DEBUG 389void 390log_packet_init(char *newname) 391{ 392 struct pcap_file_header sf_hdr; 393 struct stat st; 394 mode_t old_umask; 395 char *mode; 396 397 /* Allocate packet buffer first time through. */ 398 if (!packet_buf) 399 packet_buf = malloc(SNAPLEN); 400 401 if (!packet_buf) { 402 log_error("log_packet_init: malloc (%d) failed", SNAPLEN); 403 return; 404 } 405 if (pcaplog_file && strcmp(pcaplog_file, PCAP_FILE_DEFAULT) != 0) 406 free(pcaplog_file); 407 408 pcaplog_file = strdup(newname); 409 if (!pcaplog_file) { 410 log_error("log_packet_init: strdup (\"%s\") failed", newname); 411 return; 412 } 413 /* Does the file already exist? XXX lstat() or stat()? */ 414#if defined (USE_PRIVSEP) 415 /* XXX This is a fstat! */ 416 if (monitor_stat(pcaplog_file, &st) == 0) { 417#else 418 if (lstat(pcaplog_file, &st) == 0) { 419#endif 420 /* Sanity checks. */ 421 if ((st.st_mode & S_IFMT) != S_IFREG) { 422 log_print("log_packet_init: existing capture file is " 423 "not a regular file"); 424 return; 425 } 426 if ((st.st_mode & (S_IRWXG | S_IRWXO)) != 0) { 427 log_print("log_packet_init: existing capture " 428 "file has bad modes"); 429 return; 430 } 431 /* 432 * XXX It would be nice to check if it actually is a pcap 433 * file... 434 */ 435 436 mode = "a"; 437 } else 438 mode = "w"; 439 440 old_umask = umask(S_IRWXG | S_IRWXO); 441 packet_log = monitor_fopen(pcaplog_file, mode); 442 umask(old_umask); 443 444 if (!packet_log) { 445 log_error("log_packet_init: fopen (\"%s\", \"%s\") failed", 446 pcaplog_file, mode); 447 return; 448 } 449 log_print("log_packet_init: " 450 "starting IKE packet capture to file \"%s\"", pcaplog_file); 451 452 /* If this is a new file, we need to write a PCAP header to it. */ 453 if (*mode == 'w') { 454 sf_hdr.magic = TCPDUMP_MAGIC; 455 sf_hdr.version_major = PCAP_VERSION_MAJOR; 456 sf_hdr.version_minor = PCAP_VERSION_MINOR; 457 sf_hdr.thiszone = 0; 458 sf_hdr.snaplen = SNAPLEN; 459 sf_hdr.sigfigs = 0; 460 sf_hdr.linktype = DLT_LOOP; 461 462 fwrite((char *) &sf_hdr, sizeof sf_hdr, 1, packet_log); 463 fflush(packet_log); 464 } 465} 466 467void 468log_packet_restart(char *newname) 469{ 470 if (packet_log) { 471 log_print("log_packet_restart: capture already active on " 472 "file \"%s\"", pcaplog_file); 473 return; 474 } 475 if (newname) 476 log_packet_init(newname); 477 else if (!pcaplog_file) 478 log_packet_init(PCAP_FILE_DEFAULT); 479 else 480 log_packet_init(pcaplog_file); 481} 482 483void 484log_packet_stop(void) 485{ 486 /* Stop capture. */ 487 if (packet_log) { 488 fclose(packet_log); 489 log_print("log_packet_stop: stopped capture"); 490 } 491 packet_log = 0; 492} 493 494void 495log_packet_iov(struct sockaddr *src, struct sockaddr *dst, struct iovec *iov, 496 int iovcnt) 497{ 498 struct isakmp_hdr *isakmphdr; 499 struct packhdr hdr; 500 struct udphdr udp; 501 struct timeval tv; 502 int off, datalen, hdrlen, i, add_espmarker = 0; 503 const u_int32_t espmarker = 0; 504 505 for (i = 0, datalen = 0; i < iovcnt; i++) 506 datalen += iov[i].iov_len; 507 508 if (!packet_log || datalen > SNAPLEN) 509 return; 510 511 /* copy packet into buffer */ 512 for (i = 0, off = 0; i < iovcnt; i++) { 513 memcpy(packet_buf + off, iov[i].iov_base, iov[i].iov_len); 514 off += iov[i].iov_len; 515 } 516 517 bzero(&hdr, sizeof hdr); 518 bzero(&udp, sizeof udp); 519 520 /* isakmp - turn off the encryption bit in the isakmp hdr */ 521 isakmphdr = (struct isakmp_hdr *) packet_buf; 522 isakmphdr->flags &= ~(ISAKMP_FLAGS_ENC); 523 524 /* udp */ 525 udp.uh_sport = sockaddr_port(src); 526 udp.uh_dport = sockaddr_port(dst); 527 datalen += sizeof udp; 528#if defined (USE_NAT_TRAVERSAL) 529 if (ntohs(udp.uh_sport) == 4500 || 530 ntohs(udp.uh_dport) == 4500) { /* XXX Quick and dirty */ 531 add_espmarker = 1; 532 datalen += sizeof espmarker; 533 } 534#endif 535 udp.uh_ulen = htons(datalen); 536 537 /* ip */ 538 hdr.sa_family = htonl(src->sa_family); 539 switch (src->sa_family) { 540 default: 541 /* Assume IPv4. XXX Can 'default' ever happen here? */ 542 hdr.sa_family = htonl(AF_INET); 543 hdr.ip.ip4.ip_src.s_addr = 0x02020202; 544 hdr.ip.ip4.ip_dst.s_addr = 0x01010101; 545 /* The rest of the setup is common to AF_INET. */ 546 goto setup_ip4; 547 548 case AF_INET: 549 hdr.ip.ip4.ip_src.s_addr = 550 ((struct sockaddr_in *)src)->sin_addr.s_addr; 551 hdr.ip.ip4.ip_dst.s_addr = 552 ((struct sockaddr_in *)dst)->sin_addr.s_addr; 553 554setup_ip4: 555 hdrlen = sizeof hdr.ip.ip4; 556 hdr.ip.ip4.ip_v = 0x4; 557 hdr.ip.ip4.ip_hl = 0x5; 558 hdr.ip.ip4.ip_p = IPPROTO_UDP; 559 hdr.ip.ip4.ip_len = htons(datalen + hdrlen); 560 /* Let's use the IP ID as a "packet counter". */ 561 i = ntohs(hdr.ip.ip4.ip_id) + 1; 562 hdr.ip.ip4.ip_id = htons(i); 563 /* Calculate IP header checksum. */ 564 hdr.ip.ip4.ip_sum = in_cksum((u_int16_t *) & hdr.ip.ip4, 565 hdr.ip.ip4.ip_hl << 2); 566 break; 567 568 case AF_INET6: 569 hdrlen = sizeof(hdr.ip.ip6); 570 hdr.ip.ip6.ip6_vfc = IPV6_VERSION; 571 hdr.ip.ip6.ip6_nxt = IPPROTO_UDP; 572 hdr.ip.ip6.ip6_plen = udp.uh_ulen; 573 memcpy(&hdr.ip.ip6.ip6_src, 574 &((struct sockaddr_in6 *)src)->sin6_addr, 575 sizeof hdr.ip.ip6.ip6_src); 576 memcpy(&hdr.ip.ip6.ip6_dst, 577 &((struct sockaddr_in6 *)dst)->sin6_addr, 578 sizeof hdr.ip.ip6.ip6_dst); 579 break; 580 } 581 582 /* Calculate UDP checksum. */ 583 udp.uh_sum = udp_cksum(&hdr, &udp, (u_int16_t *) packet_buf); 584 hdrlen += sizeof hdr.sa_family; 585 586 /* pcap file packet header */ 587 gettimeofday(&tv, 0); 588 hdr.pcap.ts.tv_sec = tv.tv_sec; 589 hdr.pcap.ts.tv_usec = tv.tv_usec; 590 hdr.pcap.caplen = datalen + hdrlen; 591 hdr.pcap.len = datalen + hdrlen; 592 593 hdrlen += sizeof(struct pcap_pkthdr); 594 datalen -= sizeof(struct udphdr); 595 596 /* Write to pcap file. */ 597 fwrite(&hdr, hdrlen, 1, packet_log); /* pcap + IP */ 598 fwrite(&udp, sizeof(struct udphdr), 1, packet_log); /* UDP */ 599 if (add_espmarker) { 600 fwrite(&espmarker, sizeof espmarker, 1, packet_log); 601 datalen -= sizeof espmarker; 602 } 603 fwrite(packet_buf, datalen, 1, packet_log); /* IKE-data */ 604 fflush(packet_log); 605} 606 607/* Copied from tcpdump/print-udp.c, mostly rewritten. */ 608static int 609udp_cksum(struct packhdr *hdr, const struct udphdr *u, u_int16_t *d) 610{ 611 struct ip *ip4; 612 struct ip6_hdr *ip6; 613 int i, hdrlen, tlen = ntohs(u->uh_ulen) - sizeof(struct udphdr); 614 615 union phu { 616 struct ip4pseudo { 617 struct in_addr src; 618 struct in_addr dst; 619 u_int8_t z; 620 u_int8_t proto; 621 u_int16_t len; 622 } ip4p; 623 struct ip6pseudo { 624 struct in6_addr src; 625 struct in6_addr dst; 626 u_int32_t plen; 627 u_int16_t z0; 628 u_int8_t z1; 629 u_int8_t nxt; 630 } ip6p; 631 u_int16_t pa[20]; 632 } phu; 633 const u_int16_t *sp; 634 u_int32_t sum; 635 636 /* Setup pseudoheader. */ 637 bzero(phu.pa, sizeof phu); 638 switch (ntohl(hdr->sa_family)) { 639 case AF_INET: 640 ip4 = &hdr->ip.ip4; 641 memcpy(&phu.ip4p.src, &ip4->ip_src, sizeof(struct in_addr)); 642 memcpy(&phu.ip4p.dst, &ip4->ip_dst, sizeof(struct in_addr)); 643 phu.ip4p.proto = ip4->ip_p; 644 phu.ip4p.len = u->uh_ulen; 645 hdrlen = sizeof phu.ip4p; 646 break; 647 648 case AF_INET6: 649 ip6 = &hdr->ip.ip6; 650 memcpy(&phu.ip6p.src, &ip6->ip6_src, sizeof(phu.ip6p.src)); 651 memcpy(&phu.ip6p.dst, &ip6->ip6_dst, sizeof(phu.ip6p.dst)); 652 phu.ip6p.plen = u->uh_ulen; 653 phu.ip6p.nxt = ip6->ip6_nxt; 654 hdrlen = sizeof phu.ip6p; 655 break; 656 657 default: 658 return 0; 659 } 660 661 /* IPv6 wants a 0xFFFF checksum "on error", not 0x0. */ 662 if (tlen < 0) 663 return (ntohl(hdr->sa_family) == AF_INET ? 0 : 0xFFFF); 664 665 sum = 0; 666 for (i = 0; i < hdrlen; i += 2) 667 sum += phu.pa[i / 2]; 668 669 sp = (const u_int16_t *)u; 670 for (i = 0; i < (int)sizeof(struct udphdr); i += 2) 671 sum += *sp++; 672 673 sp = d; 674 for (i = 0; i < (tlen & ~1); i += 2) 675 sum += *sp++; 676 677 if (tlen & 1) 678 sum += htons((*(const char *)sp) << 8); 679 680 while (sum > 0xffff) 681 sum = (sum & 0xffff) + (sum >> 16); 682 sum = ~sum & 0xffff; 683 684 return sum; 685} 686 687/* Copied from tcpdump/print-ip.c, modified. */ 688static u_int16_t 689in_cksum(const u_int16_t *w, int len) 690{ 691 int nleft = len, sum = 0; 692 u_int16_t answer; 693 694 while (nleft > 1) { 695 sum += *w++; 696 nleft -= 2; 697 } 698 if (nleft == 1) 699 sum += htons(*(const u_char *)w << 8); 700 701 sum = (sum >> 16) + (sum & 0xffff); /* add hi 16 to low 16 */ 702 sum += (sum >> 16); /* add carry */ 703 answer = ~sum; /* truncate to 16 bits */ 704 return answer; 705} 706 707#endif /* USE_DEBUG */ 708