diff options
author | Matthias Wachs <wachs@net.in.tum.de> | 2010-09-09 09:41:06 +0000 |
---|---|---|
committer | Matthias Wachs <wachs@net.in.tum.de> | 2010-09-09 09:41:06 +0000 |
commit | 0da6349b297bfb66a1314964100fbb9c07887415 (patch) | |
tree | 9b2646732d8c84718569e6615369fa92e968baa7 /src/transport/plugin_transport_http.c | |
parent | b90d29c3ceef065739c46539ec748a71ea83cde3 (diff) | |
download | gnunet-0da6349b297bfb66a1314964100fbb9c07887415.tar.gz gnunet-0da6349b297bfb66a1314964100fbb9c07887415.zip |
merging http and https: next steps
Diffstat (limited to 'src/transport/plugin_transport_http.c')
-rw-r--r-- | src/transport/plugin_transport_http.c | 189 |
1 files changed, 175 insertions, 14 deletions
diff --git a/src/transport/plugin_transport_http.c b/src/transport/plugin_transport_http.c index 317cd45b3..86fde4a64 100644 --- a/src/transport/plugin_transport_http.c +++ b/src/transport/plugin_transport_http.c | |||
@@ -403,6 +403,17 @@ struct Plugin | |||
403 | * Closure passed by MHD to the mhd_logger function | 403 | * Closure passed by MHD to the mhd_logger function |
404 | */ | 404 | */ |
405 | void * mhd_log; | 405 | void * mhd_log; |
406 | |||
407 | #if BUILD_HTTPS | ||
408 | /* The certificate MHD uses as an \0 terminated string */ | ||
409 | char * cert; | ||
410 | |||
411 | /* The private key MHD uses as an \0 terminated string */ | ||
412 | char * key; | ||
413 | |||
414 | /* crypto init string */ | ||
415 | char * crypto_init; | ||
416 | #endif | ||
406 | }; | 417 | }; |
407 | 418 | ||
408 | 419 | ||
@@ -2458,6 +2469,38 @@ libgnunet_plugin_transport_http_done (void *cls) | |||
2458 | return NULL; | 2469 | return NULL; |
2459 | } | 2470 | } |
2460 | 2471 | ||
2472 | #if BUILD_HTTPS | ||
2473 | static char * | ||
2474 | load_certificate( const char * file ) | ||
2475 | { | ||
2476 | struct GNUNET_DISK_FileHandle * gn_file; | ||
2477 | |||
2478 | struct stat fstat; | ||
2479 | char * text = NULL; | ||
2480 | |||
2481 | if (0!=STAT(file, &fstat)) | ||
2482 | return NULL; | ||
2483 | text = GNUNET_malloc (fstat.st_size+1); | ||
2484 | gn_file = GNUNET_DISK_file_open(file,GNUNET_DISK_OPEN_READ, GNUNET_DISK_PERM_USER_READ); | ||
2485 | if (gn_file==NULL) | ||
2486 | { | ||
2487 | GNUNET_free(text); | ||
2488 | return NULL; | ||
2489 | } | ||
2490 | if (GNUNET_SYSERR == GNUNET_DISK_file_read(gn_file, text, fstat.st_size)) | ||
2491 | { | ||
2492 | GNUNET_free(text); | ||
2493 | GNUNET_DISK_file_close(gn_file); | ||
2494 | return NULL; | ||
2495 | } | ||
2496 | text[fstat.st_size] = '\0'; | ||
2497 | GNUNET_DISK_file_close(gn_file); | ||
2498 | |||
2499 | return text; | ||
2500 | } | ||
2501 | #endif | ||
2502 | |||
2503 | |||
2461 | /** | 2504 | /** |
2462 | * Entry point for the plugin. | 2505 | * Entry point for the plugin. |
2463 | */ | 2506 | */ |
@@ -2470,11 +2513,16 @@ LIBGNUNET_PLUGIN_TRANSPORT_INIT (void *cls) | |||
2470 | struct GNUNET_TIME_Relative gn_timeout; | 2513 | struct GNUNET_TIME_Relative gn_timeout; |
2471 | long long unsigned int port; | 2514 | long long unsigned int port; |
2472 | char * component_name; | 2515 | char * component_name; |
2516 | #if BUILD_HTTPS | ||
2517 | char * key_file = NULL; | ||
2518 | char * cert_file = NULL; | ||
2519 | #endif | ||
2473 | 2520 | ||
2474 | GNUNET_assert(cls !=NULL); | 2521 | GNUNET_assert(cls !=NULL); |
2475 | #if DEBUG_HTTP | 2522 | #if DEBUG_HTTP |
2476 | GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,"Starting %s plugin...\n", PROTOCOL_PREFIX); | 2523 | GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,"Starting %s plugin...\n", PROTOCOL_PREFIX); |
2477 | #endif | 2524 | #endif |
2525 | GNUNET_asprintf(&component_name,"transport-%s",PROTOCOL_PREFIX); | ||
2478 | 2526 | ||
2479 | plugin = GNUNET_malloc (sizeof (struct Plugin)); | 2527 | plugin = GNUNET_malloc (sizeof (struct Plugin)); |
2480 | plugin->stats = env->stats; | 2528 | plugin->stats = env->stats; |
@@ -2492,7 +2540,6 @@ LIBGNUNET_PLUGIN_TRANSPORT_INIT (void *cls) | |||
2492 | api->check_address = &http_plugin_address_suggested; | 2540 | api->check_address = &http_plugin_address_suggested; |
2493 | api->address_to_string = &http_plugin_address_to_string; | 2541 | api->address_to_string = &http_plugin_address_to_string; |
2494 | 2542 | ||
2495 | GNUNET_asprintf(&component_name,"transport-%s",PROTOCOL_PREFIX); | ||
2496 | /* Hashing our identity to use it in URLs */ | 2543 | /* Hashing our identity to use it in URLs */ |
2497 | GNUNET_CRYPTO_hash_to_enc ( &(plugin->env->my_identity->hashPubKey), &plugin->my_ascii_hash_ident); | 2544 | GNUNET_CRYPTO_hash_to_enc ( &(plugin->env->my_identity->hashPubKey), &plugin->my_ascii_hash_ident); |
2498 | 2545 | ||
@@ -2509,8 +2556,7 @@ LIBGNUNET_PLUGIN_TRANSPORT_INIT (void *cls) | |||
2509 | component_name, "USE_IPv4")) | 2556 | component_name, "USE_IPv4")) |
2510 | { | 2557 | { |
2511 | plugin->use_ipv4 = GNUNET_CONFIGURATION_get_value_yesno (env->cfg, | 2558 | plugin->use_ipv4 = GNUNET_CONFIGURATION_get_value_yesno (env->cfg, |
2512 | component_name, | 2559 | component_name,"USE_IPv4"); |
2513 | "USE_IPv4"); | ||
2514 | } | 2560 | } |
2515 | /* Reading port number from config file */ | 2561 | /* Reading port number from config file */ |
2516 | if ((GNUNET_OK != | 2562 | if ((GNUNET_OK != |
@@ -2521,20 +2567,21 @@ LIBGNUNET_PLUGIN_TRANSPORT_INIT (void *cls) | |||
2521 | (port > 65535) ) | 2567 | (port > 65535) ) |
2522 | { | 2568 | { |
2523 | GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR, | 2569 | GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR, |
2524 | component_name, | 2570 | component_name, |
2525 | _("Require valid port number for transport plugin `%s' in configuration!\n"), | 2571 | _("Require valid port number for transport plugin `%s' in configuration!\n"), |
2526 | PROTOCOL_PREFIX); | 2572 | PROTOCOL_PREFIX); |
2573 | GNUNET_free(component_name); | ||
2527 | libgnunet_plugin_transport_http_done (api); | 2574 | libgnunet_plugin_transport_http_done (api); |
2528 | return NULL; | 2575 | return NULL; |
2529 | } | 2576 | } |
2530 | 2577 | ||
2531 | /* Reading ipv4 addresse to bind to from config file */ | 2578 | /* Reading ipv4 addresse to bind to from config file */ |
2532 | if ((plugin->use_ipv4==GNUNET_YES) && (GNUNET_CONFIGURATION_have_value (env->cfg, | 2579 | if ((plugin->use_ipv4==GNUNET_YES) && (GNUNET_CONFIGURATION_have_value (env->cfg, |
2533 | component_name, "BINDTO4"))) | 2580 | component_name, "BINDTO4"))) |
2534 | { | 2581 | { |
2535 | GNUNET_break (GNUNET_OK == | 2582 | GNUNET_break (GNUNET_OK == |
2536 | GNUNET_CONFIGURATION_get_value_string (env->cfg, | 2583 | GNUNET_CONFIGURATION_get_value_string (env->cfg, |
2537 | component_name, | 2584 | component_name, |
2538 | "BINDTO4", | 2585 | "BINDTO4", |
2539 | &plugin->bind_hostname)); | 2586 | &plugin->bind_hostname)); |
2540 | plugin->bind4_address = GNUNET_malloc(sizeof(struct sockaddr_in)); | 2587 | plugin->bind4_address = GNUNET_malloc(sizeof(struct sockaddr_in)); |
@@ -2558,9 +2605,9 @@ LIBGNUNET_PLUGIN_TRANSPORT_INIT (void *cls) | |||
2558 | component_name, "BINDTO6"))) | 2605 | component_name, "BINDTO6"))) |
2559 | { | 2606 | { |
2560 | if (GNUNET_OK == GNUNET_CONFIGURATION_get_value_string (env->cfg, | 2607 | if (GNUNET_OK == GNUNET_CONFIGURATION_get_value_string (env->cfg, |
2561 | component_name, | 2608 | component_name, |
2562 | "BINDTO6", | 2609 | "BINDTO6", |
2563 | &plugin->bind_hostname)) | 2610 | &plugin->bind_hostname)) |
2564 | { | 2611 | { |
2565 | plugin->bind6_address = GNUNET_malloc(sizeof(struct sockaddr_in6)); | 2612 | plugin->bind6_address = GNUNET_malloc(sizeof(struct sockaddr_in6)); |
2566 | plugin->bind6_address->sin6_family = AF_INET6; | 2613 | plugin->bind6_address->sin6_family = AF_INET6; |
@@ -2569,7 +2616,7 @@ LIBGNUNET_PLUGIN_TRANSPORT_INIT (void *cls) | |||
2569 | if (inet_pton(AF_INET6,plugin->bind_hostname, &plugin->bind6_address->sin6_addr)<=0) | 2616 | if (inet_pton(AF_INET6,plugin->bind_hostname, &plugin->bind6_address->sin6_addr)<=0) |
2570 | { | 2617 | { |
2571 | GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR, | 2618 | GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR, |
2572 | component_name, | 2619 | component_name, |
2573 | _("Misconfigured address to bind to in configuration!\n")); | 2620 | _("Misconfigured address to bind to in configuration!\n")); |
2574 | GNUNET_free(plugin->bind6_address); | 2621 | GNUNET_free(plugin->bind6_address); |
2575 | GNUNET_free(plugin->bind_hostname); | 2622 | GNUNET_free(plugin->bind_hostname); |
@@ -2579,6 +2626,101 @@ LIBGNUNET_PLUGIN_TRANSPORT_INIT (void *cls) | |||
2579 | } | 2626 | } |
2580 | } | 2627 | } |
2581 | 2628 | ||
2629 | #if BUILD_HTTPS | ||
2630 | /* Reading HTTPS crypto related configuration */ | ||
2631 | /* Get crypto init string from config */ | ||
2632 | if (GNUNET_CONFIGURATION_have_value (env->cfg, | ||
2633 | "transport-https", "CRYPTO_INIT")) | ||
2634 | { | ||
2635 | GNUNET_CONFIGURATION_get_value_string (env->cfg, | ||
2636 | "transport-https", | ||
2637 | "CRYPTO_INIT", | ||
2638 | &plugin->crypto_init); | ||
2639 | } | ||
2640 | else | ||
2641 | { | ||
2642 | GNUNET_asprintf(&plugin->crypto_init,"NORMAL"); | ||
2643 | } | ||
2644 | |||
2645 | /* Get private key file from config */ | ||
2646 | if (GNUNET_CONFIGURATION_have_value (env->cfg, | ||
2647 | "transport-https", "KEY_FILE")) | ||
2648 | { | ||
2649 | GNUNET_CONFIGURATION_get_value_string (env->cfg, | ||
2650 | "transport-https", | ||
2651 | "KEY_FILE", | ||
2652 | &key_file); | ||
2653 | } | ||
2654 | if (key_file==NULL) | ||
2655 | GNUNET_asprintf(&key_file,"https.key"); | ||
2656 | |||
2657 | /* Get private key file from config */ | ||
2658 | if (GNUNET_CONFIGURATION_have_value (env->cfg,"transport-https", "CERT_FILE")) | ||
2659 | { | ||
2660 | GNUNET_CONFIGURATION_get_value_string (env->cfg, | ||
2661 | "transport-https", | ||
2662 | "CERT_FILE", | ||
2663 | &cert_file); | ||
2664 | } | ||
2665 | if (cert_file==NULL) | ||
2666 | GNUNET_asprintf(&cert_file,"https.cert"); | ||
2667 | |||
2668 | /* read key & certificates from file */ | ||
2669 | GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Loading TLS certificate `%s' `%s'\n", key_file, cert_file); | ||
2670 | |||
2671 | plugin->key = load_certificate( key_file ); | ||
2672 | plugin->cert = load_certificate( cert_file ); | ||
2673 | |||
2674 | if ((plugin->key==NULL) || (plugin->cert==NULL)) | ||
2675 | { | ||
2676 | char * cmd; | ||
2677 | int ret = 0; | ||
2678 | GNUNET_asprintf(&cmd,"gnunet-transport-certificate-creation %s %s", key_file, cert_file); | ||
2679 | GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "No usable TLS certificate found, creating certificate \n"); | ||
2680 | ret = system(cmd); | ||
2681 | |||
2682 | if (ret != 0) | ||
2683 | { | ||
2684 | GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR, | ||
2685 | "https", | ||
2686 | _("Could not create a new TLS certificate, shell script `%s' failed!\n"),cmd, | ||
2687 | "transport-https"); | ||
2688 | GNUNET_free (key_file); | ||
2689 | GNUNET_free (cert_file); | ||
2690 | GNUNET_free (component_name); | ||
2691 | |||
2692 | libgnunet_plugin_transport_http_done(api); | ||
2693 | GNUNET_free (cmd); | ||
2694 | return NULL; | ||
2695 | } | ||
2696 | |||
2697 | GNUNET_free (cmd); | ||
2698 | |||
2699 | plugin->key = load_certificate( key_file ); | ||
2700 | plugin->cert = load_certificate( cert_file ); | ||
2701 | |||
2702 | if ((plugin->key==NULL) || (plugin->cert==NULL)) | ||
2703 | { | ||
2704 | GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR, | ||
2705 | "https", | ||
2706 | _("No usable TLS certificate found and creating one failed! \n"), | ||
2707 | "transport-https"); | ||
2708 | GNUNET_free (key_file); | ||
2709 | GNUNET_free (cert_file); | ||
2710 | libgnunet_plugin_transport_http_done(api); | ||
2711 | return NULL; | ||
2712 | } | ||
2713 | } | ||
2714 | |||
2715 | GNUNET_free (key_file); | ||
2716 | GNUNET_free (cert_file); | ||
2717 | |||
2718 | |||
2719 | GNUNET_assert((plugin->key!=NULL) && (plugin->cert!=NULL)); | ||
2720 | GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "TLS certificate loaded\n"); | ||
2721 | |||
2722 | #endif | ||
2723 | |||
2582 | GNUNET_assert ((port > 0) && (port <= 65535)); | 2724 | GNUNET_assert ((port > 0) && (port <= 65535)); |
2583 | plugin->port_inbound = port; | 2725 | plugin->port_inbound = port; |
2584 | gn_timeout = GNUNET_CONSTANTS_IDLE_CONNECTION_TIMEOUT; | 2726 | gn_timeout = GNUNET_CONSTANTS_IDLE_CONNECTION_TIMEOUT; |
@@ -2590,6 +2732,9 @@ LIBGNUNET_PLUGIN_TRANSPORT_INIT (void *cls) | |||
2590 | #if DEBUG_MHD | 2732 | #if DEBUG_MHD |
2591 | MHD_USE_DEBUG | | 2733 | MHD_USE_DEBUG | |
2592 | #endif | 2734 | #endif |
2735 | #if BUILD_HTTPS | ||
2736 | MHD_USE_SSL | | ||
2737 | #endif | ||
2593 | MHD_USE_IPv6, | 2738 | MHD_USE_IPv6, |
2594 | port, | 2739 | port, |
2595 | &mhd_accept_cb, | 2740 | &mhd_accept_cb, |
@@ -2597,6 +2742,11 @@ LIBGNUNET_PLUGIN_TRANSPORT_INIT (void *cls) | |||
2597 | MHD_OPTION_SOCK_ADDR, tmp, | 2742 | MHD_OPTION_SOCK_ADDR, tmp, |
2598 | MHD_OPTION_CONNECTION_LIMIT, (unsigned int) 32, | 2743 | MHD_OPTION_CONNECTION_LIMIT, (unsigned int) 32, |
2599 | //MHD_OPTION_PER_IP_CONNECTION_LIMIT, (unsigned int) 6, | 2744 | //MHD_OPTION_PER_IP_CONNECTION_LIMIT, (unsigned int) 6, |
2745 | #if BUILD_HTTPS | ||
2746 | MHD_OPTION_HTTPS_PRIORITIES, plugin->crypto_init, | ||
2747 | MHD_OPTION_HTTPS_MEM_KEY, plugin->key, | ||
2748 | MHD_OPTION_HTTPS_MEM_CERT, plugin->cert, | ||
2749 | #endif | ||
2600 | MHD_OPTION_CONNECTION_TIMEOUT, (unsigned int) timeout, | 2750 | MHD_OPTION_CONNECTION_TIMEOUT, (unsigned int) timeout, |
2601 | MHD_OPTION_CONNECTION_MEMORY_LIMIT, (size_t) (2 * GNUNET_SERVER_MAX_MESSAGE_SIZE), | 2751 | MHD_OPTION_CONNECTION_MEMORY_LIMIT, (size_t) (2 * GNUNET_SERVER_MAX_MESSAGE_SIZE), |
2602 | MHD_OPTION_NOTIFY_COMPLETED, &mhd_termination_cb, NULL, | 2752 | MHD_OPTION_NOTIFY_COMPLETED, &mhd_termination_cb, NULL, |
@@ -2609,6 +2759,9 @@ LIBGNUNET_PLUGIN_TRANSPORT_INIT (void *cls) | |||
2609 | #if DEBUG_MHD | 2759 | #if DEBUG_MHD |
2610 | MHD_USE_DEBUG | | 2760 | MHD_USE_DEBUG | |
2611 | #endif | 2761 | #endif |
2762 | #if BUILD_HTTPS | ||
2763 | MHD_USE_SSL | | ||
2764 | #endif | ||
2612 | MHD_NO_FLAG, | 2765 | MHD_NO_FLAG, |
2613 | port, | 2766 | port, |
2614 | &mhd_accept_cb, | 2767 | &mhd_accept_cb, |
@@ -2616,6 +2769,11 @@ LIBGNUNET_PLUGIN_TRANSPORT_INIT (void *cls) | |||
2616 | MHD_OPTION_SOCK_ADDR, (struct sockaddr_in *)plugin->bind4_address, | 2769 | MHD_OPTION_SOCK_ADDR, (struct sockaddr_in *)plugin->bind4_address, |
2617 | MHD_OPTION_CONNECTION_LIMIT, (unsigned int) 32, | 2770 | MHD_OPTION_CONNECTION_LIMIT, (unsigned int) 32, |
2618 | //MHD_OPTION_PER_IP_CONNECTION_LIMIT, (unsigned int) 6, | 2771 | //MHD_OPTION_PER_IP_CONNECTION_LIMIT, (unsigned int) 6, |
2772 | #if BUILD_HTTPS | ||
2773 | MHD_OPTION_HTTPS_PRIORITIES, plugin->crypto_init, | ||
2774 | MHD_OPTION_HTTPS_MEM_KEY, plugin->key, | ||
2775 | MHD_OPTION_HTTPS_MEM_CERT, plugin->cert, | ||
2776 | #endif | ||
2619 | MHD_OPTION_CONNECTION_TIMEOUT, (unsigned int) timeout, | 2777 | MHD_OPTION_CONNECTION_TIMEOUT, (unsigned int) timeout, |
2620 | MHD_OPTION_CONNECTION_MEMORY_LIMIT, (size_t) (2 * GNUNET_SERVER_MAX_MESSAGE_SIZE), | 2778 | MHD_OPTION_CONNECTION_MEMORY_LIMIT, (size_t) (2 * GNUNET_SERVER_MAX_MESSAGE_SIZE), |
2621 | MHD_OPTION_NOTIFY_COMPLETED, &mhd_termination_cb, NULL, | 2779 | MHD_OPTION_NOTIFY_COMPLETED, &mhd_termination_cb, NULL, |
@@ -2657,8 +2815,9 @@ LIBGNUNET_PLUGIN_TRANSPORT_INIT (void *cls) | |||
2657 | GNUNET_asprintf(&tmp,"with IPv6 enabled"); | 2815 | GNUNET_asprintf(&tmp,"with IPv6 enabled"); |
2658 | if ((plugin->use_ipv6 == GNUNET_NO) && (plugin->use_ipv4 == GNUNET_NO)) | 2816 | if ((plugin->use_ipv6 == GNUNET_NO) && (plugin->use_ipv4 == GNUNET_NO)) |
2659 | GNUNET_asprintf(&tmp,"with NO IP PROTOCOL enabled"); | 2817 | GNUNET_asprintf(&tmp,"with NO IP PROTOCOL enabled"); |
2660 | GNUNET_log (GNUNET_ERROR_TYPE_ERROR,"HTTP Server with %s could not be started on port %u! https plugin failed!\n",tmp, port); | 2818 | GNUNET_log (GNUNET_ERROR_TYPE_ERROR,"HTTP Server with %s could not be started on port %u! %s plugin failed!\n",tmp, port, PROTOCOL_PREFIX); |
2661 | GNUNET_free(tmp); | 2819 | GNUNET_free(tmp); |
2820 | GNUNET_free(component_name); | ||
2662 | libgnunet_plugin_transport_http_done (api); | 2821 | libgnunet_plugin_transport_http_done (api); |
2663 | return NULL; | 2822 | return NULL; |
2664 | } | 2823 | } |
@@ -2670,9 +2829,10 @@ LIBGNUNET_PLUGIN_TRANSPORT_INIT (void *cls) | |||
2670 | if ( NULL == plugin->multi_handle ) | 2829 | if ( NULL == plugin->multi_handle ) |
2671 | { | 2830 | { |
2672 | GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR, | 2831 | GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR, |
2673 | component_name, | 2832 | component_name, |
2674 | _("Could not initialize curl multi handle, failed to start %s plugin!\n"), | 2833 | _("Could not initialize curl multi handle, failed to start %s plugin!\n"), |
2675 | PROTOCOL_PREFIX); | 2834 | PROTOCOL_PREFIX); |
2835 | GNUNET_free(component_name); | ||
2676 | libgnunet_plugin_transport_http_done (api); | 2836 | libgnunet_plugin_transport_http_done (api); |
2677 | return NULL; | 2837 | return NULL; |
2678 | } | 2838 | } |
@@ -2680,6 +2840,7 @@ LIBGNUNET_PLUGIN_TRANSPORT_INIT (void *cls) | |||
2680 | plugin->peers = GNUNET_CONTAINER_multihashmap_create (10); | 2840 | plugin->peers = GNUNET_CONTAINER_multihashmap_create (10); |
2681 | GNUNET_OS_network_interfaces_list (&process_interfaces, plugin); | 2841 | GNUNET_OS_network_interfaces_list (&process_interfaces, plugin); |
2682 | 2842 | ||
2843 | GNUNET_free(component_name); | ||
2683 | return api; | 2844 | return api; |
2684 | } | 2845 | } |
2685 | 2846 | ||