tor

The Tor anonymity network
git clone https://git.dasho.dev/tor.git
Log | Files | Refs | README | LICENSE

tor-resolve.c (18732B)


      1 /* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson
      2 * Copyright (c) 2007-2021, The Tor Project, Inc.
      3 */
      4 /* See LICENSE for licensing information */
      5 
      6 #include "orconfig.h"
      7 
      8 #include "lib/arch/bytes.h"
      9 #include "lib/log/log.h"
     10 #include "lib/malloc/malloc.h"
     11 #include "lib/net/address.h"
     12 #include "lib/net/resolve.h"
     13 #include "lib/net/socket.h"
     14 #include "lib/sandbox/sandbox.h"
     15 #include "lib/string/util_string.h"
     16 
     17 #include "lib/net/socks5_status.h"
     18 #include "trunnel/socks5.h"
     19 
     20 #include <stdio.h>
     21 #include <stdlib.h>
     22 #include <stdarg.h>
     23 #include <string.h>
     24 
     25 #ifdef HAVE_NETINET_IN_H
     26 #include <netinet/in.h>
     27 #endif
     28 #ifdef HAVE_ARPA_INET_H
     29 #include <arpa/inet.h>
     30 #endif
     31 #ifdef HAVE_SYS_SOCKET_H
     32 #include <sys/socket.h>
     33 #endif
     34 #ifdef HAVE_SYS_TYPES_H
     35 #include <sys/types.h> /* Must be included before sys/stat.h for Ultrix */
     36 #endif
     37 #ifdef HAVE_ERRNO_H
     38 #include <errno.h>
     39 #endif
     40 
     41 #ifdef _WIN32
     42 #include <winsock2.h>
     43 #include <ws2tcpip.h>
     44 #endif
     45 
     46 #define RESPONSE_LEN_4 8
     47 #define log_sock_error(act, _s)                                         \
     48  STMT_BEGIN                                                            \
     49    log_fn(LOG_ERR, LD_NET, "Error while %s: %s", act,                  \
     50    tor_socket_strerror(tor_socket_errno(_s)));                         \
     51  STMT_END
     52 
     53 static void usage(void) ATTR_NORETURN;
     54 
     55 /**
     56 * Set <b>out</b> to a pointer to newly allocated buffer containing
     57 * SOCKS4a RESOLVE request with <b>username</b> and <b>hostname</b>.
     58 * Return number of bytes in the buffer if succeeded or -1 if failed.
     59 */
     60 static ssize_t
     61 build_socks4a_resolve_request(uint8_t **out,
     62                              const char *username,
     63                              const char *hostname)
     64 {
     65  tor_assert(out);
     66  tor_assert(username);
     67  tor_assert(hostname);
     68 
     69  const char *errmsg = NULL;
     70  uint8_t *output = NULL;
     71  socks4_client_request_t *rq = socks4_client_request_new();
     72 
     73  socks4_client_request_set_version(rq, 4);
     74  socks4_client_request_set_command(rq, CMD_RESOLVE);
     75  socks4_client_request_set_port(rq, 0);
     76  socks4_client_request_set_addr(rq, 0x00000001u);
     77  socks4_client_request_set_username(rq, username);
     78  socks4_client_request_set_socks4a_addr_hostname(rq, hostname);
     79 
     80  errmsg = socks4_client_request_check(rq);
     81  if (errmsg) {
     82    goto cleanup;
     83  }
     84 
     85  ssize_t encoded_len = socks4_client_request_encoded_len(rq);
     86  if (encoded_len <= 0) {
     87    errmsg = "socks4_client_request_encoded_len failed";
     88    goto cleanup;
     89  }
     90 
     91  output = tor_malloc(encoded_len);
     92  memset(output, 0, encoded_len);
     93 
     94  encoded_len = socks4_client_request_encode(output, encoded_len, rq);
     95  if (encoded_len <= 0) {
     96    errmsg = "socks4_client_request_encode failed";
     97    goto cleanup;
     98  }
     99 
    100  *out = output;
    101 
    102 cleanup:
    103  socks4_client_request_free(rq);
    104  if (errmsg) {
    105    log_err(LD_NET, "build_socks4a_resolve_request failed: %s", errmsg);
    106    *out = NULL;
    107    tor_free(output);
    108  }
    109  return errmsg ? -1 : encoded_len;
    110 }
    111 
    112 #define SOCKS5_ATYPE_HOSTNAME 0x03
    113 #define SOCKS5_ATYPE_IPV4     0x01
    114 #define SOCKS5_ATYPE_IPV6     0x04
    115 
    116 /**
    117 * Set <b>out</b> to pointer to newly allocated buffer containing
    118 * SOCKS5 RESOLVE/RESOLVE_PTR request with given <b>hostname<b>.
    119 * Generate a reverse request if <b>reverse</b> is true.
    120 * Return the number of bytes in the buffer if succeeded or -1 if failed.
    121 */
    122 static ssize_t
    123 build_socks5_resolve_request(uint8_t **out,
    124                             const char *hostname,
    125                             int reverse)
    126 {
    127  const char *errmsg = NULL;
    128  uint8_t *outbuf = NULL;
    129  int is_ip_address;
    130  tor_addr_t addr;
    131  size_t addrlen;
    132  int ipv6;
    133  is_ip_address = tor_addr_parse(&addr, hostname) != -1;
    134  if (!is_ip_address && reverse) {
    135    log_err(LD_GENERAL, "Tried to do a reverse lookup on a non-IP!");
    136    return -1;
    137  }
    138  ipv6 = reverse && tor_addr_family(&addr) == AF_INET6;
    139  addrlen = reverse ? (ipv6 ? 16 : 4) : 1 + strlen(hostname);
    140  if (addrlen > UINT8_MAX) {
    141    log_err(LD_GENERAL, "Hostname is too long!");
    142    return -1;
    143  }
    144 
    145  socks5_client_request_t *rq = socks5_client_request_new();
    146 
    147  socks5_client_request_set_version(rq, 5);
    148  /* RESOLVE_PTR or RESOLVE */
    149  socks5_client_request_set_command(rq, reverse ? CMD_RESOLVE_PTR :
    150                                                  CMD_RESOLVE);
    151  socks5_client_request_set_reserved(rq, 0);
    152 
    153  uint8_t atype = SOCKS5_ATYPE_HOSTNAME;
    154  if (reverse)
    155    atype = ipv6 ? SOCKS5_ATYPE_IPV6 : SOCKS5_ATYPE_IPV4;
    156 
    157  socks5_client_request_set_atype(rq, atype);
    158 
    159  switch (atype) {
    160    case SOCKS5_ATYPE_IPV4: {
    161      socks5_client_request_set_dest_addr_ipv4(rq,
    162                                        tor_addr_to_ipv4h(&addr));
    163    } break;
    164    case SOCKS5_ATYPE_IPV6: {
    165      uint8_t *ipv6_array =
    166        socks5_client_request_getarray_dest_addr_ipv6(rq);
    167 
    168      tor_assert(ipv6_array);
    169 
    170      memcpy(ipv6_array, tor_addr_to_in6_addr8(&addr), 16);
    171    } break;
    172 
    173    case SOCKS5_ATYPE_HOSTNAME: {
    174      domainname_t *dn = domainname_new();
    175      domainname_set_len(dn, addrlen - 1);
    176      domainname_setlen_name(dn, addrlen - 1);
    177      char *dn_buf = domainname_getarray_name(dn);
    178      memcpy(dn_buf, hostname, addrlen - 1);
    179 
    180      errmsg = domainname_check(dn);
    181 
    182      if (errmsg) {
    183        domainname_free(dn);
    184        goto cleanup;
    185      } else {
    186        socks5_client_request_set_dest_addr_domainname(rq, dn);
    187      }
    188    } break;
    189    default:
    190      tor_assert_unreached();
    191      break;
    192  }
    193 
    194  socks5_client_request_set_dest_port(rq, 0);
    195 
    196  errmsg = socks5_client_request_check(rq);
    197  if (errmsg) {
    198    goto cleanup;
    199  }
    200 
    201  ssize_t encoded_len = socks5_client_request_encoded_len(rq);
    202  if (encoded_len < 0) {
    203    errmsg = "Cannot predict encoded length";
    204    goto cleanup;
    205  }
    206 
    207  outbuf = tor_malloc(encoded_len);
    208  memset(outbuf, 0, encoded_len);
    209 
    210  encoded_len = socks5_client_request_encode(outbuf, encoded_len, rq);
    211  if (encoded_len < 0) {
    212    errmsg = "encoding failed";
    213    goto cleanup;
    214  }
    215 
    216  *out = outbuf;
    217 
    218 cleanup:
    219  socks5_client_request_free(rq);
    220  if (errmsg) {
    221    tor_free(outbuf);
    222    log_err(LD_NET, "build_socks5_resolve_request failed with error: %s",
    223            errmsg);
    224  }
    225 
    226  return errmsg ? -1 : encoded_len;
    227 }
    228 
    229 /** Set *<b>out</b> to a newly allocated SOCKS4a resolve request with
    230 * <b>username</b> and <b>hostname</b> as provided.  Return the number
    231 * of bytes in the request. */
    232 static ssize_t
    233 build_socks_resolve_request(uint8_t **out,
    234                            const char *username,
    235                            const char *hostname,
    236                            int reverse,
    237                            int version)
    238 {
    239  tor_assert(out);
    240  tor_assert(username);
    241  tor_assert(hostname);
    242 
    243  tor_assert(version == 4 || version == 5);
    244 
    245  if (version == 4) {
    246    return build_socks4a_resolve_request(out, username, hostname);
    247  } else if (version == 5) {
    248    return build_socks5_resolve_request(out, hostname, reverse);
    249  }
    250 
    251  tor_assert_unreached();
    252  return -1;
    253 }
    254 
    255 static void
    256 onion_hs_warning(const char *hostname)
    257 {
    258  log_warn(LD_NET,
    259        "%s is a hidden service; those don't have IP addresses. "
    260        "You can use the AutomapHostsOnResolve option to have Tor return a "
    261        "fake address for hidden services.  Or you can have your "
    262        "application send the address to Tor directly; we recommend an "
    263        "application that uses SOCKS 5 with hostnames.",
    264           hostname);
    265 }
    266 
    267 static void
    268 onion_exit_warning(const char *hostname)
    269 {
    270  log_warn(LD_NET,
    271        "%s is a link pointing to an exit node; however, .exit domains"
    272        "have been long defunct and are not valid anymore.",
    273           hostname);
    274 }
    275 
    276 /** Given a <b>len</b>-byte SOCKS4a response in <b>response</b>, set
    277 * *<b>addr_out</b> to the address it contains (in host order).
    278 * Return 0 on success, -1 on error.
    279 */
    280 static int
    281 parse_socks4a_resolve_response(const char *hostname,
    282                               const char *response, size_t len,
    283                               tor_addr_t *addr_out)
    284 {
    285  int result = 0;
    286  uint8_t status;
    287  tor_assert(response);
    288  tor_assert(addr_out);
    289 
    290  socks4_server_reply_t *reply;
    291 
    292  ssize_t parsed = socks4_server_reply_parse(&reply,
    293                                             (const uint8_t *)response,
    294                                             len);
    295 
    296  if (parsed == -1) {
    297    log_warn(LD_PROTOCOL, "Failed parsing SOCKS4a response");
    298    result = -1; goto cleanup;
    299  }
    300 
    301  if (parsed == -2) {
    302    log_warn(LD_PROTOCOL,"Truncated socks response.");
    303    result = -1; goto cleanup;
    304  }
    305 
    306  if (socks4_server_reply_get_version(reply) != 0) { /* version: 0 */
    307    log_warn(LD_PROTOCOL,"Nonzero version in socks response: bad format.");
    308    result = -1; goto cleanup;
    309  }
    310  if (socks4_server_reply_get_port(reply) != 0) { /* port: 0 */
    311    log_warn(LD_PROTOCOL,"Nonzero port in socks response: bad format.");
    312    result = -1; goto cleanup;
    313  }
    314  status = socks4_server_reply_get_status(reply);
    315  if (status != 90) {
    316    log_warn(LD_NET,"Got status response '%d': socks request failed.", status);
    317    if (!strcasecmpend(hostname, ".onion")) {
    318      onion_hs_warning(hostname);
    319      result = -1; goto cleanup;
    320    }
    321 
    322    if (!strcasecmpend(hostname, ".exit")) {
    323      onion_exit_warning(hostname);
    324      result = -1; goto cleanup;
    325    }
    326 
    327    result = -1; goto cleanup;
    328  }
    329 
    330  tor_addr_from_ipv4h(addr_out, socks4_server_reply_get_addr(reply));
    331 
    332 cleanup:
    333  socks4_server_reply_free(reply);
    334  return result;
    335 }
    336 
    337 /* It would be nice to let someone know what SOCKS5 issue a user may have */
    338 static const char *
    339 socks5_reason_to_string(char reason)
    340 {
    341  switch (reason) {
    342    case SOCKS5_SUCCEEDED:
    343      return "succeeded";
    344    case SOCKS5_GENERAL_ERROR:
    345      return "general error";
    346    case SOCKS5_NOT_ALLOWED:
    347      return "not allowed";
    348    case SOCKS5_NET_UNREACHABLE:
    349      return "network is unreachable";
    350    case SOCKS5_HOST_UNREACHABLE:
    351      return "host is unreachable";
    352    case SOCKS5_CONNECTION_REFUSED:
    353      return "connection refused";
    354    case SOCKS5_TTL_EXPIRED:
    355      return "ttl expired";
    356    case SOCKS5_COMMAND_NOT_SUPPORTED:
    357      return "command not supported";
    358    case SOCKS5_ADDRESS_TYPE_NOT_SUPPORTED:
    359      return "address type not supported";
    360    default:
    361      return "unknown SOCKS5 code";
    362  }
    363 }
    364 
    365 /** Send a resolve request for <b>hostname</b> to the Tor listening on
    366 * <b>sockshost</b>:<b>socksport</b>.  Store the resulting IPv4
    367 * address (in host order) into *<b>result_addr</b>.
    368 */
    369 static int
    370 do_resolve(const char *hostname,
    371           const tor_addr_t *sockshost, uint16_t socksport,
    372           int reverse, int version,
    373           tor_addr_t *result_addr, char **result_hostname)
    374 {
    375  int s = -1;
    376  struct sockaddr_storage ss;
    377  socklen_t socklen;
    378  uint8_t *req = NULL;
    379  ssize_t len = 0;
    380 
    381  tor_assert(hostname);
    382  tor_assert(result_addr);
    383  tor_assert(version == 4 || version == 5);
    384 
    385  tor_addr_make_unspec(result_addr);
    386  *result_hostname = NULL;
    387 
    388  s = tor_open_socket(sockshost->family,SOCK_STREAM,IPPROTO_TCP);
    389  if (s<0) {
    390    log_sock_error("creating_socket", -1);
    391    return -1;
    392  }
    393 
    394  socklen = tor_addr_to_sockaddr(sockshost, socksport,
    395                                 (struct sockaddr *)&ss, sizeof(ss));
    396 
    397  if (connect(s, (struct sockaddr*)&ss, socklen)) {
    398    log_sock_error("connecting to SOCKS host", s);
    399    goto err;
    400  }
    401 
    402  if (version == 5) {
    403    socks5_client_version_t *v = socks5_client_version_new();
    404 
    405    socks5_client_version_set_version(v, 5);
    406    socks5_client_version_set_n_methods(v, 1);
    407    socks5_client_version_setlen_methods(v, 1);
    408    socks5_client_version_set_methods(v, 0, 0x00);
    409 
    410    tor_assert(!socks5_client_version_check(v));
    411    ssize_t encoded_len = socks5_client_version_encoded_len(v);
    412    tor_assert(encoded_len > 0);
    413 
    414    uint8_t *buf = tor_malloc(encoded_len);
    415    encoded_len = socks5_client_version_encode(buf, encoded_len, v);
    416    tor_assert(encoded_len > 0);
    417 
    418    socks5_client_version_free(v);
    419 
    420    if (write_all_to_socket(s, (const char *)buf,
    421                            encoded_len) != encoded_len) {
    422      log_err(LD_NET, "Error sending SOCKS5 method list.");
    423      tor_free(buf);
    424 
    425      goto err;
    426    }
    427 
    428    tor_free(buf);
    429 
    430    uint8_t method_buf[2];
    431 
    432    if (read_all_from_socket(s, (char *)method_buf, 2) != 2) {
    433      log_err(LD_NET, "Error reading SOCKS5 methods.");
    434      goto err;
    435    }
    436 
    437    socks5_server_method_t *m;
    438    ssize_t parsed = socks5_server_method_parse(&m, method_buf,
    439                                                sizeof(method_buf));
    440 
    441    if (parsed < 2) {
    442      log_err(LD_NET, "Failed to parse SOCKS5 method selection "
    443                      "message");
    444      socks5_server_method_free(m);
    445      goto err;
    446    }
    447 
    448    uint8_t method = socks5_server_method_get_method(m);
    449 
    450    socks5_server_method_free(m);
    451 
    452    if (method != 0x00) {
    453      log_err(LD_NET, "Unrecognized socks authentication method: %u",
    454              (unsigned)method);
    455      goto err;
    456    }
    457  }
    458 
    459  if ((len = build_socks_resolve_request(&req, "", hostname, reverse,
    460                                         version))<0) {
    461    log_err(LD_BUG,"Error generating SOCKS request");
    462    tor_assert(!req);
    463    goto err;
    464  }
    465  if (write_all_to_socket(s, (const char *)req, len) != len) {
    466    log_sock_error("sending SOCKS request", s);
    467    tor_free(req);
    468    goto err;
    469  }
    470  tor_free(req);
    471 
    472  if (version == 4) {
    473    char reply_buf[RESPONSE_LEN_4];
    474    if (read_all_from_socket(s, reply_buf, RESPONSE_LEN_4) != RESPONSE_LEN_4) {
    475      log_err(LD_NET, "Error reading SOCKS4 response.");
    476      goto err;
    477    }
    478    if (parse_socks4a_resolve_response(hostname,
    479                                       reply_buf, RESPONSE_LEN_4,
    480                                       result_addr)<0) {
    481      goto err;
    482    }
    483  } else {
    484    uint8_t reply_buf[512];
    485 
    486    len = read_all_from_socket(s, (char *)reply_buf,
    487                               sizeof(reply_buf));
    488 
    489    socks5_server_reply_t *reply;
    490 
    491    ssize_t parsed = socks5_server_reply_parse(&reply,
    492                                               reply_buf,
    493                                               len);
    494    if (parsed == -1) {
    495      log_err(LD_NET, "Failed parsing SOCKS5 response");
    496      goto err;
    497    }
    498 
    499    if (parsed == -2) {
    500      log_err(LD_NET, "Truncated SOCKS5 response");
    501      goto err;
    502    }
    503 
    504    /* Give a user some useful feedback about SOCKS5 errors */
    505    uint8_t reply_field = socks5_server_reply_get_reply(reply);
    506    if (reply_field != 0) {
    507      log_warn(LD_NET,"Got SOCKS5 status response '%u': %s",
    508               (unsigned)reply_field,
    509               socks5_reason_to_string(reply_field));
    510      if (reply_field == 4 && !strcasecmpend(hostname, ".onion")) {
    511        onion_hs_warning(hostname);
    512      }
    513 
    514      if (reply_field == 4 && !strcasecmpend(hostname, ".exit")) {
    515        onion_exit_warning(hostname);
    516      }
    517 
    518      socks5_server_reply_free(reply);
    519      goto err;
    520    }
    521 
    522    uint8_t atype = socks5_server_reply_get_atype(reply);
    523 
    524    if (atype == SOCKS5_ATYPE_IPV4) {
    525      /* IPv4 address */
    526      tor_addr_from_ipv4h(result_addr,
    527        socks5_server_reply_get_bind_addr_ipv4(reply));
    528    } else if (atype == SOCKS5_ATYPE_IPV6) {
    529      /* IPv6 address */
    530      tor_addr_from_ipv6_bytes(result_addr,
    531        socks5_server_reply_getarray_bind_addr_ipv6(reply));
    532    } else if (atype == SOCKS5_ATYPE_HOSTNAME) {
    533      /* Domain name */
    534      domainname_t *dn =
    535        socks5_server_reply_get_bind_addr_domainname(reply);
    536 
    537      *result_hostname = tor_strdup(domainname_getstr_name(dn));
    538    }
    539 
    540    socks5_server_reply_free(reply);
    541  }
    542 
    543  tor_close_socket(s);
    544  return 0;
    545 err:
    546  tor_close_socket(s);
    547  return -1;
    548 }
    549 
    550 /** Print a usage message and exit. */
    551 static void
    552 usage(void)
    553 {
    554  puts("Syntax: tor-resolve [-4] [-5] [-v] [-x] [-p port] "
    555       "hostname [sockshost[:socksport]]");
    556  exit(1);
    557 }
    558 
    559 /** Entry point to tor-resolve */
    560 int
    561 main(int argc, char **argv)
    562 {
    563  tor_addr_t sockshost;
    564  uint16_t socksport = 0, port_option = 0;
    565  int isSocks4 = 0, isVerbose = 0, isReverse = 0;
    566  char **arg;
    567  int n_args;
    568  tor_addr_t result;
    569  char *result_hostname = NULL;
    570 
    571  init_logging(1);
    572  sandbox_disable_getaddrinfo_cache();
    573 
    574  arg = &argv[1];
    575  n_args = argc-1;
    576 
    577  if (!n_args)
    578    usage();
    579 
    580  if (!strcmp(arg[0],"--version")) {
    581    printf("Tor version %s.\n",VERSION);
    582    return 0;
    583  }
    584  while (n_args && *arg[0] == '-') {
    585    if (!strcmp("-v", arg[0]))
    586      isVerbose = 1;
    587    else if (!strcmp("-4", arg[0]))
    588      isSocks4 = 1;
    589    else if (!strcmp("-5", arg[0]))
    590      isSocks4 = 0;
    591    else if (!strcmp("-x", arg[0]))
    592      isReverse = 1;
    593    else if (!strcmp("-p", arg[0])) {
    594      int p;
    595      if (n_args < 2) {
    596        fprintf(stderr, "No arguments given to -p\n");
    597        usage();
    598      }
    599      p = atoi(arg[1]);
    600      if (p<1 || p > 65535) {
    601        fprintf(stderr, "-p requires a number between 1 and 65535\n");
    602        usage();
    603      }
    604      port_option = (uint16_t) p;
    605      ++arg; /* skip the port */
    606      --n_args;
    607    } else {
    608      fprintf(stderr, "Unrecognized flag '%s'\n", arg[0]);
    609      usage();
    610    }
    611    ++arg;
    612    --n_args;
    613  }
    614 
    615  if (isSocks4 && isReverse) {
    616    fprintf(stderr, "Reverse lookups not supported with SOCKS4a\n");
    617    usage();
    618  }
    619 
    620  log_severity_list_t *severities =
    621    tor_malloc_zero(sizeof(log_severity_list_t));
    622  if (isVerbose)
    623    set_log_severity_config(LOG_DEBUG, LOG_ERR, severities);
    624  else
    625    set_log_severity_config(LOG_WARN, LOG_ERR, severities);
    626  add_stream_log(severities, "<stderr>", fileno(stderr));
    627  tor_free(severities);
    628 
    629  if (n_args == 1) {
    630    log_debug(LD_CONFIG, "defaulting to localhost");
    631    tor_addr_from_ipv4h(&sockshost, 0x7f000001u); /* localhost */
    632    if (port_option) {
    633      log_debug(LD_CONFIG, "Using port %d", (int)port_option);
    634      socksport = port_option;
    635    } else {
    636      log_debug(LD_CONFIG, "defaulting to port 9050");
    637      socksport = 9050; /* 9050 */
    638    }
    639  } else if (n_args == 2) {
    640    if (tor_addr_port_lookup(arg[1], &sockshost, &socksport)<0) {
    641      fprintf(stderr, "Couldn't parse/resolve address %s", arg[1]);
    642      return 1;
    643    }
    644    if (socksport && port_option && socksport != port_option) {
    645      log_warn(LD_CONFIG, "Conflicting ports; using %d, not %d",
    646               (int)socksport, (int)port_option);
    647    } else if (port_option) {
    648      socksport = port_option;
    649    } else if (!socksport) {
    650      log_debug(LD_CONFIG, "defaulting to port 9050");
    651      socksport = 9050;
    652    }
    653  } else {
    654    usage();
    655  }
    656 
    657  if (network_init()<0) {
    658    log_err(LD_BUG,"Error initializing network; exiting.");
    659    return 1;
    660  }
    661 
    662  if (do_resolve(arg[0], &sockshost, socksport, isReverse,
    663                 isSocks4 ? 4 : 5, &result,
    664                 &result_hostname))
    665    return 1;
    666 
    667  if (result_hostname) {
    668    printf("%s\n", result_hostname);
    669  } else {
    670    printf("%s\n", fmt_addr(&result));
    671  }
    672  return 0;
    673 }