Ticket #18620: tor.2.patch

File tor.2.patch, 16.6 KB (added by str4d, 3 years ago)

Patch to implement HSFORGET command in Tor 0.2.7.6

  • src/or/control.c

    diff --git a/src/or/control.c b/src/or/control.c
    index 220e7e5..84b617a 100644
    a b static int handle_control_hsfetch(control_connection_t *conn, uint32_t len, 
    176176                                  const char *body);
    177177static int handle_control_hspost(control_connection_t *conn, uint32_t len,
    178178                                 const char *body);
     179static int handle_control_hsforget(control_connection_t *conn, uint32_t len,
     180                                   const char *body);
    179181static int handle_control_add_onion(control_connection_t *conn, uint32_t len,
    180182                                    const char *body);
    181183static int handle_control_del_onion(control_connection_t *conn, uint32_t len,
    handle_control_hspost(control_connection_t *conn, 
    37253727  return 0;
    37263728}
    37273729
     3730/** Called when we get an HSFORGET command: parse the hidden service's onion
     3731 * address and purge any cached state related to the service. */
     3732static int
     3733handle_control_hsforget(control_connection_t *conn, uint32_t len,
     3734                        const char *body)
     3735{
     3736  smartlist_t *args;
     3737  char *onion_address, *descriptor_cookie_base64, *descriptor_cookie_decoded;
     3738  char descriptor_cookie_base64ext[REND_DESC_COOKIE_LEN_BASE64 + 2 + 1];
     3739
     3740  args = getargs_helper("HSFORGET", conn, body, 1, 2);
     3741  if (!args)
     3742    return -1;
     3743  onion_address = smartlist_get(args, 0);
     3744  if (smartlist_len(args) == 2) {
     3745    descriptor_cookie_base64 = smartlist_get(args, 1);
     3746    descriptor_cookie_decoded = tor_malloc(REND_DESC_COOKIE_LEN + 2);
     3747  } else {
     3748    descriptor_cookie_base64 = NULL;
     3749    descriptor_cookie_decoded = NULL;
     3750  }
     3751  smartlist_free(args);
     3752
     3753  if (!rend_valid_service_id(onion_address)) {
     3754    connection_write_str_to_buf("513 Invalid hidden service address\r\n", conn);
     3755    goto err;
     3756  }
     3757
     3758  if (descriptor_cookie_base64) {
     3759    if (strlen(descriptor_cookie_base64) != REND_DESC_COOKIE_LEN_BASE64) {
     3760      connection_write_str_to_buf("513 Invalid descriptor cookie\r\n", conn);
     3761      goto err;
     3762    }
     3763    /* Add trailing zero bytes (AA) to make base64-decoding happy. */
     3764    tor_snprintf(descriptor_cookie_base64ext,
     3765                 REND_DESC_COOKIE_LEN_BASE64 + 2 + 1,
     3766                 "%sAA", descriptor_cookie_base64);
     3767    if (base64_decode(descriptor_cookie_decoded,
     3768                      sizeof(descriptor_cookie_decoded),
     3769                      descriptor_cookie_base64ext,
     3770                      strlen(descriptor_cookie_base64ext)) < 0) {
     3771      connection_write_str_to_buf("513 Invalid descriptor cookie\r\n", conn);
     3772      goto err;
     3773    }
     3774  }
     3775
     3776  rend_client_purge_hidden_service(onion_address, descriptor_cookie_decoded);
     3777  tor_free(onion_address);
     3778  if (descriptor_cookie_base64) {
     3779    tor_free(descriptor_cookie_base64);
     3780    tor_free(descriptor_cookie_decoded);
     3781  }
     3782  send_control_done(conn);
     3783  return 0;
     3784
     3785err:
     3786  tor_free(onion_address);
     3787  if (descriptor_cookie_base64) {
     3788    tor_free(descriptor_cookie_base64);
     3789    tor_free(descriptor_cookie_decoded);
     3790  }
     3791  return -1;
     3792}
     3793
    37283794/** Called when we get a ADD_ONION command; parse the body, and set up
    37293795 * the new ephemeral Onion Service. */
    37303796static int
    connection_control_process_inbuf(control_connection_t *conn) 
    43964462  } else if (!strcasecmp(conn->incoming_cmd, "+HSPOST")) {
    43974463    if (handle_control_hspost(conn, cmd_data_len, args))
    43984464      return -1;
     4465  } else if (!strcasecmp(conn->incoming_cmd, "HSFORGET")) {
     4466    if (handle_control_hsforget(conn, cmd_data_len, args))
     4467      return -1;
    43994468  } else if (!strcasecmp(conn->incoming_cmd, "ADD_ONION")) {
    44004469    int ret = handle_control_add_onion(conn, cmd_data_len, args);
    44014470    memwipe(args, 0, cmd_data_len); /* Scrub the private key. */
  • src/or/rendcache.c

    diff --git a/src/or/rendcache.c b/src/or/rendcache.c
    index d4bdd68..5e5fbef 100644
    a b rend_cache_lookup_entry(const char *query, int version, rend_cache_entry_t **e) 
    535535  return ret;
    536536}
    537537
     538/** Remove any cached descriptors for <b>service_id</b>. */
     539void
     540rend_cache_remove_entry(const char *service_id)
     541{
     542  char key[REND_SERVICE_ID_LEN_BASE32 + 2]; /* <version><service_id>\0 */
     543  rend_cache_entry_t *removed;
     544
     545  tor_assert(rend_valid_service_id(service_id));
     546  if (!rend_cache)
     547    return;
     548
     549  tor_snprintf(key, sizeof(key), "2%s", service_id);
     550  removed = strmap_remove_lc(rend_cache, key);
     551  if (removed) {
     552    log_info(LD_REND, "Removed cached v2 descriptor for service %s.",
     553               safe_str_client(service_id));
     554    rend_cache_entry_free(removed);
     555  }
     556
     557  tor_snprintf(key, sizeof(key), "0%s", service_id);
     558  removed = strmap_remove_lc(rend_cache, key);
     559  if (removed) {
     560    log_info(LD_REND, "Removed cached v0 descriptor for service %s.",
     561               safe_str_client(service_id));
     562    rend_cache_entry_free(removed);
     563  }
     564}
     565
    538566/** Lookup the v2 service descriptor with base32-encoded <b>desc_id</b> and
    539567 * copy the pointer to it to *<b>desc</b>.  Return 1 on success, 0 on
    540568 * well-formed-but-not-found, and -1 on failure.
  • src/or/rendcache.h

    diff --git a/src/or/rendcache.h b/src/or/rendcache.h
    index 0512058..2c8ebfd 100644
    a b void rend_cache_purge(void); 
    5656void rend_cache_free_all(void);
    5757int rend_cache_lookup_entry(const char *query, int version,
    5858                            rend_cache_entry_t **entry_out);
     59void rend_cache_remove_entry(const char *service_id);
    5960int rend_cache_lookup_v2_desc_as_dir(const char *query, const char **desc);
    6061/** Return value from rend_cache_store_v2_desc_as_{dir,client}. */
    6162typedef enum {
  • src/or/rendclient.c

    diff --git a/src/or/rendclient.c b/src/or/rendclient.c
    index a39e518..f02524a 100644
    a b  
    77 * \brief Client code to access location-hidden services.
    88 **/
    99
     10#define RENDCLIENT_PRIVATE
     11
    1012#include "or.h"
    1113#include "circpathbias.h"
    1214#include "circuitbuild.h"
    get_last_hid_serv_requests(void) 
    499501 * assign the current time <b>now</b> and return that. Otherwise, return the
    500502 * most recent request time, or 0 if no such request has been sent before.
    501503 */
    502 static time_t
     504STATIC time_t
    503505lookup_last_hid_serv_request(routerstatus_t *hs_dir,
    504506                             const char *desc_id_base32,
    505507                             time_t now, int set)
    rend_client_purge_last_hid_serv_requests(void) 
    608610  }
    609611}
    610612
     613/** Purge all cached state relating to the given hidden service. If the
     614 * optional unencoded descriptor cookie is supplied, it is used for
     615 * calculating the hidden service's descriptor IDs. */
     616void
     617rend_client_purge_hidden_service(const char *onion_address,
     618                                 const char *descriptor_cookie)
     619{
     620  time_t now = time(NULL);
     621  uint8_t replica;
     622  char desc_id[DIGEST_LEN];
     623
     624  tor_assert(rend_valid_service_id(onion_address));
     625  rend_cache_remove_entry(onion_address);
     626  for (replica = 0; replica < REND_NUMBER_OF_NON_CONSECUTIVE_REPLICAS;
     627       replica++) {
     628    if (rend_compute_v2_desc_id(desc_id, onion_address, descriptor_cookie,
     629                                now, replica) < 0)
     630      return;
     631    purge_hid_serv_from_last_hid_serv_requests(desc_id);
     632  }
     633}
     634
    611635/** This returns a good valid hs dir that should be used for the given
    612636 * descriptor id.
    613637 *
  • src/or/rendclient.h

    diff --git a/src/or/rendclient.h b/src/or/rendclient.h
    index 124433e..3bf0235 100644
    a b  
    1515#include "rendcache.h"
    1616
    1717void rend_client_purge_state(void);
     18void rend_client_purge_hidden_service(const char *onion_address,
     19                                      const char *descriptor_cookie);
    1820
    1921void rend_client_introcirc_has_opened(origin_circuit_t *circ);
    2022void rend_client_rendcirc_has_opened(origin_circuit_t *circ);
    rend_service_authorization_t *rend_client_lookup_service_authorization( 
    5153                                                const char *onion_address);
    5254void rend_service_authorization_free_all(void);
    5355
     56#ifdef RENDCLIENT_PRIVATE
     57STATIC time_t lookup_last_hid_serv_request(routerstatus_t *hs_dir,
     58                                           const char *desc_id_base32,
     59                                           time_t now, int set);
     60#endif
     61
    5462#endif
    5563
  • src/test/include.am

    diff --git a/src/test/include.am b/src/test/include.am
    index a37fe23..040bc2f 100644
    a b src_test_test_SOURCES = \ 
    8585        src/test/test_pt.c \
    8686        src/test/test_relay.c \
    8787        src/test/test_relaycell.c \
     88        src/test/test_rendcache.c \
     89        src/test/test_rendclient.c \
    8890        src/test/test_replay.c \
    8991        src/test/test_routerkeys.c \
    9092        src/test/test_routerlist.c \
  • src/test/test.c

    diff --git a/src/test/test.c b/src/test/test.c
    index e10e260..dcf1394 100644
    a b extern struct testcase_t policy_tests[]; 
    11481148extern struct testcase_t pt_tests[];
    11491149extern struct testcase_t relay_tests[];
    11501150extern struct testcase_t relaycell_tests[];
     1151extern struct testcase_t rendcache_tests[];
     1152extern struct testcase_t rendclient_tests[];
    11511153extern struct testcase_t replaycache_tests[];
    11521154extern struct testcase_t router_tests[];
    11531155extern struct testcase_t routerkeys_tests[];
    struct testgroup_t testgroups[] = { 
    11951197  { "pt/", pt_tests },
    11961198  { "relay/" , relay_tests },
    11971199  { "relaycell/", relaycell_tests },
     1200  { "rendcache/", rendcache_tests },
     1201  { "rendclient/", rendclient_tests },
    11981202  { "replaycache/", replaycache_tests },
    11991203  { "routerkeys/", routerkeys_tests },
    12001204  { "routerlist/", routerlist_tests },
  • new file src/test/test_rendcache.c

    diff --git a/src/test/test_rendcache.c b/src/test/test_rendcache.c
    new file mode 100644
    index 0000000..5451417
    - +  
     1/* Copyright (c) 2016, The Tor Project, Inc. */
     2/* See LICENSE for licensing information */
     3
     4#include "rendcache.h"
     5
     6#include "or.h"
     7
     8/* Test suite stuff */
     9#include "test.h"
     10
     11/* mock ID digest and longname for node that's in nodelist */
     12#define HSDIR_EXIST_ID "\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA" \
     13                       "\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA"
     14
     15/* DuckDuckGo descriptor as an example. */
     16#define STR_HS_ADDR "ajhb7kljbiru65qo"
     17#define STR_HS_CONTENT_DESC_ID "g5ojobzupf275beh5ra72uyhb3dkpxwg"
     18#define STR_DESC_ID_BASE32 "hba3gmcgpfivzfhx5rtfqkfdhv65yrj3"
     19static const char *hs_desc_content = "\
     20rendezvous-service-descriptor g5ojobzupf275beh5ra72uyhb3dkpxwg\r\n\
     21version 2\r\n\
     22permanent-key\r\n\
     23-----BEGIN RSA PUBLIC KEY-----\r\n\
     24MIGJAoGBAJ/SzzgrXPxTlFrKVhXh3buCWv2QfcNgncUpDpKouLn3AtPH5Ocys0jE\r\n\
     25aZSKdvaiQ62md2gOwj4x61cFNdi05tdQjS+2thHKEm/KsB9BGLSLBNJYY356bupg\r\n\
     26I5gQozM65ENelfxYlysBjJ52xSDBd8C4f/p9umdzaaaCmzXG/nhzAgMBAAE=\r\n\
     27-----END RSA PUBLIC KEY-----\r\n\
     28secret-id-part anmjoxxwiupreyajjt5yasimfmwcnxlf\r\n\
     29publication-time 2015-03-11 19:00:00\r\n\
     30protocol-versions 2,3\r\n\
     31introduction-points\r\n\
     32-----BEGIN MESSAGE-----\r\n\
     33aW50cm9kdWN0aW9uLXBvaW50IDd1bnd4cmg2dG5kNGh6eWt1Z3EzaGZzdHduc2ll\r\n\
     34cmhyCmlwLWFkZHJlc3MgMTg4LjEzOC4xMjEuMTE4Cm9uaW9uLXBvcnQgOTAwMQpv\r\n\
     35bmlvbi1rZXkKLS0tLS1CRUdJTiBSU0EgUFVCTElDIEtFWS0tLS0tCk1JR0pBb0dC\r\n\
     36QUxGRVVyeVpDbk9ROEhURmV5cDVjMTRObWVqL1BhekFLTTBxRENTNElKUWh0Y3g1\r\n\
     37NXpRSFdOVWIKQ2hHZ0JqR1RjV3ZGRnA0N3FkdGF6WUZhVXE2c0lQKzVqeWZ5b0Q4\r\n\
     38UmJ1bzBwQmFWclJjMmNhYUptWWM0RDh6Vgpuby9sZnhzOVVaQnZ1cWY4eHIrMDB2\r\n\
     39S0JJNmFSMlA2OE1WeDhrMExqcUpUU2RKOE9idm9yQWdNQkFBRT0KLS0tLS1FTkQg\r\n\
     40UlNBIFBVQkxJQyBLRVktLS0tLQpzZXJ2aWNlLWtleQotLS0tLUJFR0lOIFJTQSBQ\r\n\
     41VUJMSUMgS0VZLS0tLS0KTUlHSkFvR0JBTnJHb0ozeTlHNXQzN2F2ekI1cTlwN1hG\r\n\
     42VUplRUVYMUNOaExnWmJXWGJhVk5OcXpoZFhyL0xTUQppM1Z6dW5OaUs3cndUVnE2\r\n\
     43K2QyZ1lRckhMMmIvMXBBY3ZKWjJiNSs0bTRRc0NibFpjRENXTktRbHJnRWN5WXRJ\r\n\
     44CkdscXJTbFFEaXA0ZnNrUFMvNDVkWTI0QmJsQ3NGU1k3RzVLVkxJck4zZFpGbmJr\r\n\
     45NEZIS1hBZ01CQUFFPQotLS0tLUVORCBSU0EgUFVCTElDIEtFWS0tLS0tCmludHJv\r\n\
     46ZHVjdGlvbi1wb2ludCBiNGM3enlxNXNheGZzN2prNXFibG1wN3I1b3pwdHRvagpp\r\n\
     47cC1hZGRyZXNzIDEwOS4xNjkuNDUuMjI2Cm9uaW9uLXBvcnQgOTAwMQpvbmlvbi1r\r\n\
     48ZXkKLS0tLS1CRUdJTiBSU0EgUFVCTElDIEtFWS0tLS0tCk1JR0pBb0dCQU8xSXpw\r\n\
     49WFFUTUY3RXZUb1NEUXpzVnZiRVFRQUQrcGZ6NzczMVRXZzVaUEJZY1EyUkRaeVp4\r\n\
     50OEQKNUVQSU1FeUE1RE83cGd0ak5LaXJvYXJGMC8yempjMkRXTUlSaXZyU29YUWVZ\r\n\
     51ZXlMM1pzKzFIajJhMDlCdkYxZAp6MEswblRFdVhoNVR5V3lyMHdsbGI1SFBnTlI0\r\n\
     52MS9oYkprZzkwZitPVCtIeGhKL1duUml2QWdNQkFBRT0KLS0tLS1FTkQgUlNBIFBV\r\n\
     53QkxJQyBLRVktLS0tLQpzZXJ2aWNlLWtleQotLS0tLUJFR0lOIFJTQSBQVUJMSUMg\r\n\
     54S0VZLS0tLS0KTUlHSkFvR0JBSzNWZEJ2ajFtQllLL3JrcHNwcm9Ub0llNUtHVmth\r\n\
     55QkxvMW1tK1I2YUVJek1VZFE1SjkwNGtyRwpCd3k5NC8rV0lGNFpGYXh5Z2phejl1\r\n\
     56N2pKY1k3ZGJhd1pFeG1hYXFCRlRwL2h2ZG9rcHQ4a1ByRVk4OTJPRHJ1CmJORUox\r\n\
     57N1FPSmVMTVZZZk5Kcjl4TWZCQ3JQai8zOGh2RUdrbWVRNmRVWElvbVFNaUJGOVRB\r\n\
     58Z01CQUFFPQotLS0tLUVORCBSU0EgUFVCTElDIEtFWS0tLS0tCmludHJvZHVjdGlv\r\n\
     59bi1wb2ludCBhdjVtcWl0Y2Q3cjJkandsYmN0c2Jlc2R3eGt0ZWtvegppcC1hZGRy\r\n\
     60ZXNzIDE0NC43Ni44LjczCm9uaW9uLXBvcnQgNDQzCm9uaW9uLWtleQotLS0tLUJF\r\n\
     61R0lOIFJTQSBQVUJMSUMgS0VZLS0tLS0KTUlHSkFvR0JBTzVweVZzQmpZQmNmMXBE\r\n\
     62dklHUlpmWXUzQ05nNldka0ZLMGlvdTBXTGZtejZRVDN0NWhzd3cyVwpjejlHMXhx\r\n\
     63MmN0Nkd6VWkrNnVkTDlITTRVOUdHTi9BbW8wRG9GV1hKWHpBQkFXd2YyMVdsd1lW\r\n\
     64eFJQMHRydi9WCkN6UDkzcHc5OG5vSmdGUGRUZ05iMjdKYmVUZENLVFBrTEtscXFt\r\n\
     65b3NveUN2RitRa25vUS9BZ01CQUFFPQotLS0tLUVORCBSU0EgUFVCTElDIEtFWS0t\r\n\
     66LS0tCnNlcnZpY2Uta2V5Ci0tLS0tQkVHSU4gUlNBIFBVQkxJQyBLRVktLS0tLQpN\r\n\
     67SUdKQW9HQkFMVjNKSmtWN3lTNU9jc1lHMHNFYzFQOTVRclFRR3ZzbGJ6Wi9zRGxl\r\n\
     68RlpKYXFSOUYvYjRUVERNClNGcFMxcU1GbldkZDgxVmRGMEdYRmN2WVpLamRJdHU2\r\n\
     69SndBaTRJeEhxeXZtdTRKdUxrcXNaTEFLaXRLVkx4eGsKeERlMjlDNzRWMmJrOTRJ\r\n\
     70MEgybTNKS2tzTHVwc3VxWWRVUmhOVXN0SElKZmgyZmNIalF0bEFnTUJBQUU9Ci0t\r\n\
     71LS0tRU5EIFJTQSBQVUJMSUMgS0VZLS0tLS0KCg==\r\n\
     72-----END MESSAGE-----\r\n\
     73signature\r\n\
     74-----BEGIN SIGNATURE-----\r\n\
     75d4OuCE5OLAOnRB6cQN6WyMEmg/BHem144Vec+eYgeWoKwx3MxXFplUjFxgnMlmwN\r\n\
     76PcftsZf2ztN0sbNCtPgDL3d0PqvxY3iHTQAI8EbaGq/IAJUZ8U4y963dD5+Bn6JQ\r\n\
     77myE3ctmh0vy5+QxSiRjmQBkuEpCyks7LvWvHYrhnmcg=\r\n\
     78-----END SIGNATURE-----";
     79
     80static void
     81test_rend_cache_remove_valid_entry(void *arg)
     82{
     83  rend_data_t rend_query;
     84  rend_cache_entry_t *entry = NULL;
     85
     86  rend_cache_init();
     87
     88  /* setup rend_query struct */
     89  memset(&rend_query, 0, sizeof(rend_query));
     90  strncpy(rend_query.onion_address, STR_HS_ADDR, REND_SERVICE_ID_LEN_BASE32+1);
     91  rend_query.auth_type = REND_NO_AUTH;
     92  rend_query.hsdirs_fp = smartlist_new();
     93  smartlist_add(rend_query.hsdirs_fp, tor_memdup(HSDIR_EXIST_ID, DIGEST_LEN));
     94
     95  /* add entry to cache */
     96  tt_int_op(RCS_OKAY, OP_EQ,
     97            rend_cache_store_v2_desc_as_client(hs_desc_content,
     98                                               STR_DESC_ID_BASE32, &rend_query,
     99                                               NULL));
     100  tt_int_op(0, OP_EQ, rend_cache_lookup_entry(STR_HS_ADDR, -1, &entry));
     101  tt_str_op(entry->desc, OP_EQ, hs_desc_content);
     102
     103  /* remove entry from cache */
     104  rend_cache_remove_entry(STR_HS_ADDR);
     105  entry = NULL;
     106  tt_int_op(-ENOENT, OP_EQ, rend_cache_lookup_entry(STR_HS_ADDR, -1, &entry));
     107  tt_assert(!entry);
     108
     109 done:
     110  rend_cache_free_all();
     111}
     112
     113struct testcase_t rendcache_tests[] = {
     114  { "remove_valid_entry", test_rend_cache_remove_valid_entry, TT_FORK,
     115    NULL, NULL },
     116  END_OF_TESTCASES
     117};
     118
  • new file src/test/test_rendclient.c

    diff --git a/src/test/test_rendclient.c b/src/test/test_rendclient.c
    new file mode 100644
    index 0000000..3399776
    - +  
     1/* Copyright (c) 2016, The Tor Project, Inc. */
     2/* See LICENSE for licensing information */
     3
     4#define RENDCLIENT_PRIVATE
     5
     6#include "rendclient.h"
     7
     8#include "or.h"
     9#include "rendcache.h"
     10
     11/* Test suite stuff */
     12#include "test.h"
     13
     14#define STR_HS_DIR_ID_DIGEST "cccccccccccccccccccc"
     15#define STR_HS_ADDR "ajhb7kljbiru65qo"
     16
     17static void
     18test_rend_client_purge_hidden_service(void *arg)
     19{
     20  time_t now = time(NULL);
     21  routerstatus_t hs_dir;
     22  char desc_id[DIGEST_LEN];
     23  char desc_id_base32[REND_DESC_ID_V2_LEN_BASE32 + 1];
     24
     25  rend_cache_init();
     26
     27  memset(&hs_dir, 0, sizeof(hs_dir));
     28  strncpy(hs_dir.identity_digest, STR_HS_DIR_ID_DIGEST, DIGEST_LEN);
     29
     30  rend_compute_v2_desc_id(desc_id, STR_HS_ADDR, NULL, now, 0);
     31  base32_encode(desc_id_base32, sizeof(desc_id_base32), desc_id, DIGEST_LEN);
     32
     33  /* cache serv request time */
     34  lookup_last_hid_serv_request(&hs_dir, desc_id_base32, now, 1);
     35  tt_int_op(now, OP_EQ, lookup_last_hid_serv_request(&hs_dir, desc_id_base32,
     36                                                     0, 0));
     37
     38  /* purge hidden service */
     39  rend_client_purge_hidden_service(STR_HS_ADDR, NULL);
     40  tt_int_op(0, OP_EQ, lookup_last_hid_serv_request(&hs_dir, desc_id_base32,
     41                                                   0, 0));
     42
     43 done:
     44  rend_cache_free_all();
     45}
     46
     47struct testcase_t rendclient_tests[] = {
     48  { "purge_hidden_service", test_rend_client_purge_hidden_service, TT_FORK,
     49    NULL, NULL },
     50  END_OF_TESTCASES
     51};
     52