From f7ca27a73e69a8c224d65768be3416ff1388c1d7 Mon Sep 17 00:00:00 2001 From: Phil Date: Thu, 26 Jul 2018 02:31:30 +0200 Subject: change namestore, json handling; fix identity, gns --- src/json/json_gnsrecord.c | 163 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 163 insertions(+) create mode 100644 src/json/json_gnsrecord.c (limited to 'src/json/json_gnsrecord.c') diff --git a/src/json/json_gnsrecord.c b/src/json/json_gnsrecord.c new file mode 100644 index 000000000..48b78f38b --- /dev/null +++ b/src/json/json_gnsrecord.c @@ -0,0 +1,163 @@ +/* + This file is part of GNUnet. + Copyright (C) 2009-2013 GNUnet e.V. + + GNUnet is free software: you can redistribute it and/or modify it + under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, + or (at your option) any later version. + + GNUnet is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +/** + * @file json/json_gnsrecord.c + * @brief JSON handling of GNS record data + * @author Philippe Buschmann + */ +#include "platform.h" +#include "gnunet_util_lib.h" +#include "gnunet_gnsrecord_lib.h" +#include "gnunet_json_lib.h" + +#define GNUNET_JSON_GNSRECORD_VALUE "value" +#define GNUNET_JSON_GNSRECORD_TYPE "type" +#define GNUNET_JSON_GNSRECORD_EXPIRATION_TIME "expiration_time" +#define GNUNET_JSON_GNSRECORD_FLAG "flag" +#define GNUNET_JSON_GNSRECORD_LABEL "label" +#define GNUNET_JSON_GNSRECORD_NEVER "never" + + +/** + * Parse given JSON object to gns record + * + * @param cls closure, NULL + * @param root the json object representing data + * @param spec where to write the data + * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error + */ +static int +parse_gnsrecordobject (void *cls, + json_t *root, + struct GNUNET_JSON_Specification *spec) +{ + struct GNUNET_GNSRECORD_Data *gnsrecord_object; + struct GNUNET_TIME_Absolute abs_expiration_time; + int unpack_state=0; + const char *value; + const char *expiration_time; + const char *record_type; + const char *label; + int flag; + void *rdata; + size_t rdata_size; + + GNUNET_assert(NULL != root); + if(!json_is_object(root)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Error json is not array nor object!\n"); + return GNUNET_SYSERR; + } + //interpret single gns record + unpack_state = json_unpack(root, + "{s:s, s:s, s:s, s?:i, s:s!}", + GNUNET_JSON_GNSRECORD_VALUE, &value, + GNUNET_JSON_GNSRECORD_TYPE, &record_type, + GNUNET_JSON_GNSRECORD_EXPIRATION_TIME, &expiration_time, + GNUNET_JSON_GNSRECORD_FLAG, &flag, + GNUNET_JSON_GNSRECORD_LABEL, &label); + if (GNUNET_SYSERR == unpack_state) + { + GNUNET_log(GNUNET_ERROR_TYPE_DEBUG, + "Error json object has a wrong format!\n"); + return GNUNET_SYSERR; + } + gnsrecord_object = GNUNET_new (struct GNUNET_GNSRECORD_Data); + gnsrecord_object->record_type = GNUNET_GNSRECORD_typename_to_number(record_type); + if (UINT32_MAX == gnsrecord_object->record_type) + { + GNUNET_log(GNUNET_ERROR_TYPE_DEBUG,"Unsupported type"); + return GNUNET_SYSERR; + } + if (GNUNET_OK + != GNUNET_GNSRECORD_string_to_value (gnsrecord_object->record_type, + value, + &rdata, + &rdata_size)) + { + GNUNET_log(GNUNET_ERROR_TYPE_DEBUG,"Value invalid for record type"); + return GNUNET_SYSERR; + } + + gnsrecord_object->data = rdata; + gnsrecord_object->data_size = rdata_size; + + if (0 == strcmp (expiration_time, GNUNET_JSON_GNSRECORD_NEVER)) + { + gnsrecord_object->expiration_time = GNUNET_TIME_UNIT_FOREVER_ABS.abs_value_us; + } + else if (GNUNET_OK + == GNUNET_STRINGS_fancy_time_to_absolute (expiration_time, + &abs_expiration_time)) + { + gnsrecord_object->expiration_time = abs_expiration_time.abs_value_us; + } + else + { + GNUNET_log(GNUNET_ERROR_TYPE_DEBUG, "Value invalid for expiration time"); + return GNUNET_SYSERR; + } + gnsrecord_object->flags = (enum GNUNET_GNSRECORD_Flags)flag; + *(struct GNUNET_GNSRECORD_Data **) spec->ptr = gnsrecord_object; + return GNUNET_OK; +} + +/** + * Cleanup data left from parsing RSA public key. + * + * @param cls closure, NULL + * @param[out] spec where to free the data + */ +static void +clean_gnsrecordobject (void *cls, struct GNUNET_JSON_Specification *spec) +{ + struct GNUNET_GNSRECORD_Data **gnsrecord_object; + gnsrecord_object = (struct GNUNET_GNSRECORD_Data **) spec->ptr; + if (NULL != *gnsrecord_object) + { + if (NULL != (*gnsrecord_object)->data) + GNUNET_free((char*)(*gnsrecord_object)->data); + + GNUNET_free(*gnsrecord_object); + *gnsrecord_object = NULL; + } +} + +/** + * JSON Specification for GNS Records. + * + * @param gnsrecord_object struct of GNUNET_GNSRECORD_Data to fill + * @return JSON Specification + */ +struct GNUNET_JSON_Specification +GNUNET_JSON_spec_gnsrecord_data (struct GNUNET_GNSRECORD_Data **gnsrecord_object) +{ + struct GNUNET_JSON_Specification ret = { + .parser = &parse_gnsrecordobject, + .cleaner = &clean_gnsrecordobject, + .cls = NULL, + .field = NULL, + .ptr = gnsrecord_object, + .ptr_size = 0, + .size_ptr = NULL + }; + *gnsrecord_object = NULL; + return ret; +} -- cgit v1.2.3 From 83095b7bbf49263d66ab1d89d0535e8fee2a9d01 Mon Sep 17 00:00:00 2001 From: Phil Date: Wed, 1 Aug 2018 03:26:50 +0200 Subject: -fix json and namestore --- src/json/json_generator.c | 2 +- src/json/json_gnsrecord.c | 12 ++++++++++-- src/namestore/plugin_rest_namestore.c | 10 +--------- 3 files changed, 12 insertions(+), 12 deletions(-) (limited to 'src/json/json_gnsrecord.c') diff --git a/src/json/json_generator.c b/src/json/json_generator.c index 7b24a3c12..d8c82bc86 100644 --- a/src/json/json_generator.c +++ b/src/json/json_generator.c @@ -166,7 +166,7 @@ GNUNET_JSON_from_rsa_signature (const struct GNUNET_CRYPTO_RsaSignature *sig) */ json_t * GNUNET_JSON_from_gns_record (const char* rname, - const struct GNUNET_GNSRECORD_Data *rd) + const struct GNUNET_GNSRECORD_Data *rd) { struct GNUNET_TIME_Absolute expiration_time; const char *expiration_time_str; diff --git a/src/json/json_gnsrecord.c b/src/json/json_gnsrecord.c index 48b78f38b..4f6d30748 100644 --- a/src/json/json_gnsrecord.c +++ b/src/json/json_gnsrecord.c @@ -23,7 +23,6 @@ */ #include "platform.h" #include "gnunet_util_lib.h" -#include "gnunet_gnsrecord_lib.h" #include "gnunet_json_lib.h" #define GNUNET_JSON_GNSRECORD_VALUE "value" @@ -111,7 +110,16 @@ parse_gnsrecordobject (void *cls, } else { - GNUNET_log(GNUNET_ERROR_TYPE_DEBUG, "Value invalid for expiration time"); + GNUNET_log(GNUNET_ERROR_TYPE_DEBUG, "Expiration time invalid"); + return GNUNET_SYSERR; + } + // check if flag is a valid enum value + if ((GNUNET_GNSRECORD_RF_NONE != flag) + && (GNUNET_GNSRECORD_RF_PRIVATE != flag) + && (GNUNET_GNSRECORD_RF_RELATIVE_EXPIRATION != flag) + && (GNUNET_GNSRECORD_RF_SHADOW_RECORD) != flag) + { + GNUNET_log(GNUNET_ERROR_TYPE_DEBUG, "Flag invalid"); return GNUNET_SYSERR; } gnsrecord_object->flags = (enum GNUNET_GNSRECORD_Flags)flag; diff --git a/src/namestore/plugin_rest_namestore.c b/src/namestore/plugin_rest_namestore.c index 6924c53a4..90928165e 100644 --- a/src/namestore/plugin_rest_namestore.c +++ b/src/namestore/plugin_rest_namestore.c @@ -32,6 +32,7 @@ #include "microhttpd.h" #include + #define GNUNET_REST_API_NS_NAMESTORE "/namestore" #define GNUNET_REST_SUBSYSTEM_NAMESTORE "namestore" @@ -40,8 +41,6 @@ #define GNUNET_REST_NAMESTORE_RD_COUNT 1 -//TODO define other variables - /** * The configuration handle */ @@ -60,8 +59,6 @@ struct Plugin const struct GNUNET_CONFIGURATION_Handle *cfg; }; -//TODO add specific structs - /** * The default namestore ego */ @@ -86,8 +83,6 @@ struct EgoEntry struct RequestHandle { - //TODO add specific entries - /** * Records to store */ @@ -180,8 +175,6 @@ struct RequestHandle }; - -//TODO add specific cleanup /** * Cleanup lookup handle * @param handle Handle to clean up @@ -638,7 +631,6 @@ options_cont (struct GNUNET_REST_RequestHandle *con_handle, static void init_cont (struct RequestHandle *handle) { - //TODO specify parameter of init_cont if necessary struct GNUNET_REST_RequestHandlerError err; static const struct GNUNET_REST_RequestHandler handlers[] = { {MHD_HTTP_METHOD_GET, GNUNET_REST_API_NS_NAMESTORE, &namestore_get}, -- cgit v1.2.3 From 0d239903075f518edef85277f13d1b5d303443bf Mon Sep 17 00:00:00 2001 From: Phil Date: Wed, 8 Aug 2018 23:24:10 +0200 Subject: Namestore Rest API finished --- src/json/json_gnsrecord.c | 18 ++++--- src/namestore/plugin_rest_namestore.c | 22 ++++++-- src/namestore/test_plugin_rest_namestore.sh | 78 +++-------------------------- 3 files changed, 37 insertions(+), 81 deletions(-) (limited to 'src/json/json_gnsrecord.c') diff --git a/src/json/json_gnsrecord.c b/src/json/json_gnsrecord.c index 4f6d30748..7bdf97f06 100644 --- a/src/json/json_gnsrecord.c +++ b/src/json/json_gnsrecord.c @@ -54,7 +54,7 @@ parse_gnsrecordobject (void *cls, const char *record_type; const char *label; int flag; - void *rdata; + void *rdata = NULL; size_t rdata_size; GNUNET_assert(NULL != root); @@ -72,7 +72,7 @@ parse_gnsrecordobject (void *cls, GNUNET_JSON_GNSRECORD_EXPIRATION_TIME, &expiration_time, GNUNET_JSON_GNSRECORD_FLAG, &flag, GNUNET_JSON_GNSRECORD_LABEL, &label); - if (GNUNET_SYSERR == unpack_state) + if (0 != unpack_state) { GNUNET_log(GNUNET_ERROR_TYPE_DEBUG, "Error json object has a wrong format!\n"); @@ -82,7 +82,8 @@ parse_gnsrecordobject (void *cls, gnsrecord_object->record_type = GNUNET_GNSRECORD_typename_to_number(record_type); if (UINT32_MAX == gnsrecord_object->record_type) { - GNUNET_log(GNUNET_ERROR_TYPE_DEBUG,"Unsupported type"); + GNUNET_log(GNUNET_ERROR_TYPE_DEBUG,"Unsupported type\n"); + GNUNET_free(gnsrecord_object); return GNUNET_SYSERR; } if (GNUNET_OK @@ -91,7 +92,8 @@ parse_gnsrecordobject (void *cls, &rdata, &rdata_size)) { - GNUNET_log(GNUNET_ERROR_TYPE_DEBUG,"Value invalid for record type"); + GNUNET_log(GNUNET_ERROR_TYPE_DEBUG,"Value invalid for record type\n"); + GNUNET_free(gnsrecord_object); return GNUNET_SYSERR; } @@ -110,7 +112,9 @@ parse_gnsrecordobject (void *cls, } else { - GNUNET_log(GNUNET_ERROR_TYPE_DEBUG, "Expiration time invalid"); + GNUNET_log(GNUNET_ERROR_TYPE_DEBUG, "Expiration time invalid\n"); + GNUNET_free_non_null(rdata); + GNUNET_free(gnsrecord_object); return GNUNET_SYSERR; } // check if flag is a valid enum value @@ -119,7 +123,9 @@ parse_gnsrecordobject (void *cls, && (GNUNET_GNSRECORD_RF_RELATIVE_EXPIRATION != flag) && (GNUNET_GNSRECORD_RF_SHADOW_RECORD) != flag) { - GNUNET_log(GNUNET_ERROR_TYPE_DEBUG, "Flag invalid"); + GNUNET_log(GNUNET_ERROR_TYPE_DEBUG, "Flag invalid\n"); + GNUNET_free_non_null(rdata); + GNUNET_free(gnsrecord_object); return GNUNET_SYSERR; } gnsrecord_object->flags = (enum GNUNET_GNSRECORD_Flags)flag; diff --git a/src/namestore/plugin_rest_namestore.c b/src/namestore/plugin_rest_namestore.c index 3801431b2..f14707cce 100644 --- a/src/namestore/plugin_rest_namestore.c +++ b/src/namestore/plugin_rest_namestore.c @@ -641,16 +641,17 @@ namestore_add (struct GNUNET_REST_RequestHandle *con_handle, { handle->emsg = GNUNET_strdup("Invalid data"); GNUNET_SCHEDULER_add_now (&do_error, handle); - GNUNET_free_non_null(gns_record); + GNUNET_JSON_parse_free(gnsspec); json_decref (data_js); return; } + handle->rd = gns_record; + name_json = json_object_get(data_js, "label"); if (!json_is_string(name_json)) { handle->emsg = GNUNET_strdup("Missing name"); GNUNET_SCHEDULER_add_now (&do_error, handle); - GNUNET_free_non_null(gns_record); json_decref (data_js); return; } @@ -659,7 +660,6 @@ namestore_add (struct GNUNET_REST_RequestHandle *con_handle, { handle->emsg = GNUNET_strdup("Missing name"); GNUNET_SCHEDULER_add_now (&do_error, handle); - GNUNET_free_non_null(gns_record); json_decref (data_js); return; } @@ -667,12 +667,10 @@ namestore_add (struct GNUNET_REST_RequestHandle *con_handle, { handle->emsg = GNUNET_strdup("Missing name"); GNUNET_SCHEDULER_add_now (&do_error, handle); - GNUNET_free_non_null(gns_record); json_decref (data_js); return; } json_decref (data_js); - handle->rd = gns_record; //change zone if pubkey or name specified GNUNET_CRYPTO_hash (GNUNET_REST_API_PARAM_PUBKEY, @@ -711,6 +709,12 @@ namestore_add (struct GNUNET_REST_RequestHandle *con_handle, { handle->zone_pkey = GNUNET_IDENTITY_ego_get_private_key(ego_entry->ego); } + if (NULL == handle->zone_pkey) + { + handle->emsg = GNUNET_strdup("No default identity for namestore"); + GNUNET_SCHEDULER_add_now (&do_error, handle); + return; + } handle->add_qe = GNUNET_NAMESTORE_records_lookup (handle->ns_handle, handle->zone_pkey, handle->label_name, @@ -813,6 +817,14 @@ namestore_delete (struct GNUNET_REST_RequestHandle *con_handle, } handle->label_name = GNUNET_strdup( GNUNET_CONTAINER_multihashmap_get (con_handle->url_param_map, &key)); + + if (NULL == handle->zone_pkey) + { + handle->emsg = GNUNET_strdup("No default identity for namestore"); + GNUNET_SCHEDULER_add_now (&do_error, handle); + return; + } + handle->add_qe = GNUNET_NAMESTORE_records_lookup (handle->ns_handle, handle->zone_pkey, handle->label_name, diff --git a/src/namestore/test_plugin_rest_namestore.sh b/src/namestore/test_plugin_rest_namestore.sh index 7c1e97397..de02dfafc 100755 --- a/src/namestore/test_plugin_rest_namestore.sh +++ b/src/namestore/test_plugin_rest_namestore.sh @@ -11,7 +11,7 @@ curl_get () { #$1 is link #$2 is grep cache="$(curl -v "$1" 2>&1 | grep "$2")" - echo $cache + #echo $cache if [ "" == "$cache" ] then exit 1 @@ -23,7 +23,7 @@ curl_post () { #$2 is data #$3 is grep cache="$(curl -v -X "POST" "$1" --data "$2" 2>&1 | grep "$3")" - echo $cache + #echo $cache if [ "" == "$cache" ] then exit 1 @@ -34,7 +34,7 @@ curl_delete () { #$1 is link #$2 is grep cache="$(curl -v -X "DELETE" "$1" 2>&1 | grep "$2")" - echo $cache + #echo $cache if [ "" == "$cache" ] then exit 1 @@ -197,74 +197,12 @@ gnunet-namestore -z $name -p -a -n "test_entry" -e "1d" -V "HVX38H2CB7WJM0WCPWT9 curl_delete "${namestore_link}?label=test_entry&name=$name" "HTTP/1.1 204" gnunet-namestore -z $name -p -a -n "test_entry" -e "1d" -V "HVX38H2CB7WJM0WCPWT9CFX6GASMYJVR65RN75SJSSKAYVYXHMRG" -t "PKEY" curl_delete "${namestore_link}?label=test_entry&pubkey=$public" "HTTP/1.1 204" +gnunet-namestore -z $name -p -a -n "test_entry" -e "1d" -V "HVX38H2CB7WJM0WCPWT9CFX6GASMYJVR65RN75SJSSKAYVYXHMRG" -t "PKEY" +curl_delete "${namestore_link}?label=test_entry&pubkey=$name" "HTTP/1.1 404" -exit 0; - - - -#pubkey zone -#name zone -curl_post "${namestore_link}" '{"name":"test_plugin_rest_identity"}' "HTTP/1.1 201 Created" -curl_post "${namestore_link}" '{"name":"test_plugin_rest_identity"}' "HTTP/1.1 409" -curl_post "${namestore_link}" '{"name":"Test_plugin_rest_identity"}' "HTTP/1.1 409" -curl_post "${namestore_link}" '{}' "error" -curl_post "${namestore_link}" '' "error" -curl_post "${namestore_link}" '{"name":""}' "error" -curl_post "${namestore_link}" '{"name":123}' "error" -curl_post "${namestore_link}" '{"name":[]}' "error" -curl_post "${namestore_link}" '{"name1":"test_plugin_rest_identity"}' "error" -curl_post "${namestore_link}" '{"other":""}' "error" -curl_post "${namestore_link}" '{"name":"test_plugin_rest_identity1", "other":"test_plugin_rest_identity2"}' "error" - -#Test PUT -name="$(gnunet-identity -d | grep "test_plugin_rest_identity" | awk 'NR==1{print $1}')" -public="$(gnunet-identity -d | grep "test_plugin_rest_identity" | awk 'NR==1{print $3}')" - -curl_put "${namestore_link}" '{"newname":"test_plugin_rest_identity1","pubkey":"'$public'"}' "HTTP/1.1 204" -curl_put "${namestore_link}" '{"newname":"test_plugin_rest_identity1","pubkey":"'$public'"}' "HTTP/1.1 409" -curl_put "${namestore_link}" '{"newname":"test_plugin_rest_identity1","pubkey":"'$public'xx"}' "HTTP/1.1 404" -curl_put "${namestore_link}" '{"newname":"test_plugin_rest_identity1","pubkey":""}' "HTTP/1.1 404" -curl_put "${namestore_link}" '{"newname":"test_plugin_rest_identity1","pubke":""}' "HTTP/1.1 404" -curl_put "${namestore_link}" '{"newname":"test_plugin_rest_identity1","pubke":"","other":"sdfdsf"}' "HTTP/1.1 404" -curl_put "${namestore_link}" '{"newname":"test_plugin_rest_identity1","pubke":"","name":"sdfdsf"}' "HTTP/1.1 404" -curl_put "${namestore_link}" '{"newname":"test_plugin_rest_identity","pubke":"","name":"test_plugin_rest_identity1"}' "HTTP/1.1 204" -curl_put "${namestore_link}" '{"newnam":"test_plugin_rest_identity","pubkey":"'$public'"}' "error" -curl_put "${namestore_link}" '{"newname":"test_plugin_rest_identity1","name":"test_plugin_rest_identity"}' "HTTP/1.1 204" -curl_put "${namestore_link}" '{"newname":"TEST_plugin_rest_identity1","name":"test_plugin_rest_identity1"}' "HTTP/1.1 409" -curl_put "${namestore_link}" '{"newname":"test_plugin_rest_identity1","name":"test_plugin_rest_identity1"}' "HTTP/1.1 409" -curl_put "${namestore_link}" '{"newname":"test_plugin_rest_identity","name":"test_plugin_rest_identityxxx"}' "HTTP/1.1 404" -curl_put "${namestore_link}" '{"newname":"test_plugin_rest_identity","name":"test_plugin_rest_identity1"}' "HTTP/1.1 204" -curl_put "${namestore_link}" '{"newnam":"test_plugin_rest_identityfail","name":"test_plugin_rest_identity"}' "error" - - -#Test subsystem -curl_put "${identity_link}" '{"subsystem":"namestore","name":"test_plugin_rest_identity"}' "HTTP/1.1 204" -curl_put "${identity_link}" '{"subsystem":"namestore","name":"test_plugin_rest_identity"}' "HTTP/1.1 204" -curl_get "${identity_link}?subsystem=namestore" "test_plugin_rest_identity" -curl_post "${identity_link}" '{"name":"test_plugin_rest_identity1"}' "HTTP/1.1 201 Created" -public="$(gnunet-identity -d | grep "test_plugin_rest_identity" | awk 'NR==1{print $3}')" -curl_put "${identity_link}" '{"subsystem":"namestore","pubkey":"'"$public"'"}' "HTTP/1.1 204" -curl_get "${identity_link}?subsystem=namestore" "test_plugin_rest_identity1" -curl_get "${identity_link}?subsystem=test_plugin_rest_identity_no_subsystem" "error" -curl_put "${identity_link}" '{"subsystem":"test_plugin_rest_identity_no_subsystem","name":"test_plugin_rest_identity1"}' "HTTP/1.1 204" -curl_get "${identity_link}?subsystem=test_plugin_rest_identity_no_subsystem" "test_plugin_rest_identity1" - -curl_put "${identity_link}" '{"subsyste":"test_plugin_rest_identity_no_subsystem","name":"test_plugin_rest_identity1"}' "error" -curl_put "${identity_link}" '{"subsystem":"test_plugin_rest_identity_no_subsystem","name":"Test_plugin_rest_identity1"}' "HTTP/1.1 204" - -#Test DELETE -curl_delete "${identity_link}?name=test_plugin_rest_identity" "HTTP/1.1 204" -curl_get "${identity_link}?name=test_plugin_rest_identity" "error" -curl_delete "${identity_link}?name=TEST_plugin_rest_identity1" "HTTP/1.1 404" -curl_delete "${identity_link}?name=test_plugin_rest_identity1" "HTTP/1.1 204" -curl_get "${identity_link}?name=test_plugin_rest_identity1" "error" -curl_delete "${identity_link}?name=test_plugin_rest_identity_not_found" "HTTP/1.1 404" -curl_post "${identity_link}" '{"name":"test_plugin_rest_identity1"}' "HTTP/1.1 201 Created" -public="$(gnunet-identity -d | grep "test_plugin_rest_identity1" | awk 'NR==1{print $3}')" -curl_delete "${identity_link}?pubkey=$public" "HTTP/1.1 204" -curl_delete "${identity_link}?pubke=$public" "error" -curl_delete "${identity_link}?pubkey=$public&other=232" "HTTP/1.1 404" -#test default subsystem +#Test default identity +#not possible without defining exit 0; + -- cgit v1.2.3