diff options
author | Paul Querna | 2004-09-27 06:54:58 +0000 |
---|---|---|
committer | Paul Querna | 2004-09-27 06:54:58 +0000 |
commit | 6a8a8396ceed9fb87a6586ebcb144b5f4b62a39a (patch) | |
tree | 17f5b6a7c04e09c1d5545ac605a2af0d2e875166 /src/mod_gnutls.c | |
parent | 16068f4173883029a5b0aac7540fedcfd9e81940 (diff) |
updated
Diffstat (limited to 'src/mod_gnutls.c')
-rw-r--r-- | src/mod_gnutls.c | 204 |
1 files changed, 155 insertions, 49 deletions
diff --git a/src/mod_gnutls.c b/src/mod_gnutls.c index bf0d9c3..e9ad89c 100644 --- a/src/mod_gnutls.c +++ b/src/mod_gnutls.c | |||
@@ -34,32 +34,42 @@ GCRY_THREAD_OPTION_PTHREAD_IMPL; | |||
34 | 34 | ||
35 | module AP_MODULE_DECLARE_DATA gnutls_module; | 35 | module AP_MODULE_DECLARE_DATA gnutls_module; |
36 | 36 | ||
37 | #ifdef GNUTLS_AS_FILTER | ||
37 | #define GNUTLS_OUTPUT_FILTER_NAME "GnuTLS Output Filter" | 38 | #define GNUTLS_OUTPUT_FILTER_NAME "GnuTLS Output Filter" |
38 | #define GNUTLS_INPUT_FILTER_NAME "GnuTLS Input Filter" | 39 | #define GNUTLS_INPUT_FILTER_NAME "GnuTLS Input Filter" |
40 | #endif | ||
39 | 41 | ||
40 | #define GNUTLS_ENABLED_FALSE 0 | 42 | #define GNUTLS_ENABLED_FALSE 0 |
41 | #define GNUTLS_ENABLED_TRUE 1 | 43 | #define GNUTLS_ENABLED_TRUE 1 |
42 | 44 | ||
43 | 45 | ||
44 | typedef struct gnutls_srvconf_t gnutls_srvconf_t; | 46 | typedef struct { |
45 | struct gnutls_srvconf_t | ||
46 | { | ||
47 | gnutls_certificate_credentials_t certs; | 47 | gnutls_certificate_credentials_t certs; |
48 | gnutls_anon_server_credentials_t anoncred; | ||
48 | char *key_file; | 49 | char *key_file; |
49 | char *cert_file; | 50 | char *cert_file; |
50 | int enabled; | 51 | int enabled; |
51 | }; | 52 | int non_https; |
53 | int ciphers[16]; | ||
54 | int key_exchange[16]; | ||
55 | int macs[16]; | ||
56 | int protocol[16]; | ||
57 | int compression[16]; | ||
58 | } gnutls_srvconf_rec; | ||
52 | 59 | ||
53 | typedef struct gnutls_handle_t gnutls_handle_t; | 60 | typedef struct gnutls_handle_t gnutls_handle_t; |
54 | struct gnutls_handle_t | 61 | struct gnutls_handle_t |
55 | { | 62 | { |
56 | gnutls_srvconf_t *sc; | 63 | gnutls_srvconf_rec *sc; |
57 | gnutls_session_t session; | 64 | gnutls_session_t session; |
65 | #ifdef GNUTLS_AS_FILTER | ||
58 | ap_filter_t *input_filter; | 66 | ap_filter_t *input_filter; |
59 | apr_bucket_brigade *input_bb; | 67 | apr_bucket_brigade *input_bb; |
60 | apr_read_type_e input_block; | 68 | apr_read_type_e input_block; |
69 | #endif | ||
61 | }; | 70 | }; |
62 | 71 | ||
72 | #ifdef GNUTLS_AS_FILTER | ||
63 | static apr_status_t gnutls_filter_input(ap_filter_t * f, | 73 | static apr_status_t gnutls_filter_input(ap_filter_t * f, |
64 | apr_bucket_brigade * bb, | 74 | apr_bucket_brigade * bb, |
65 | ap_input_mode_t mode, | 75 | ap_input_mode_t mode, |
@@ -105,6 +115,8 @@ static apr_status_t gnutls_filter_output(ap_filter_t * f, | |||
105 | return status; | 115 | return status; |
106 | } | 116 | } |
107 | 117 | ||
118 | #endif /* GNUTLS_AS_FILTER */ | ||
119 | |||
108 | static apr_status_t gnutls_cleanup_pre_config(void *data) | 120 | static apr_status_t gnutls_cleanup_pre_config(void *data) |
109 | { | 121 | { |
110 | gnutls_global_deinit(); | 122 | gnutls_global_deinit(); |
@@ -134,48 +146,44 @@ static int gnutls_hook_post_config(apr_pool_t * p, apr_pool_t * plog, | |||
134 | apr_pool_t * ptemp, | 146 | apr_pool_t * ptemp, |
135 | server_rec * base_server) | 147 | server_rec * base_server) |
136 | { | 148 | { |
137 | gnutls_srvconf_t *sc; | 149 | gnutls_srvconf_rec *sc; |
138 | server_rec *s; | 150 | server_rec *s; |
139 | gnutls_dh_params_t dh_params; | 151 | gnutls_dh_params_t dh_params; |
140 | gnutls_rsa_params_t rsa_params; | 152 | gnutls_rsa_params_t rsa_params; |
141 | 153 | ||
142 | 154 | ||
143 | /* TODO: Should we regenerate these after X requests / X time ? */ | 155 | /* TODO: Should we regenerate these after X requests / X time ? */ |
144 | gnutls_dh_params_init(&dh_params); | 156 | // gnutls_dh_params_init(&dh_params); |
145 | gnutls_dh_params_generate2(dh_params, DH_BITS); | 157 | // gnutls_dh_params_generate2(dh_params, DH_BITS); |
146 | gnutls_rsa_params_init(&rsa_params); | 158 | // gnutls_rsa_params_init(&rsa_params); |
147 | gnutls_rsa_params_generate2(rsa_params, RSA_BITS); | 159 | // gnutls_rsa_params_generate2(rsa_params, RSA_BITS); |
148 | 160 | ||
149 | for (s = base_server; s; s = s->next) { | 161 | for (s = base_server; s; s = s->next) { |
150 | sc = (gnutls_srvconf_t *) ap_get_module_config(s->module_config, | 162 | sc = (gnutls_srvconf_rec *) ap_get_module_config(s->module_config, |
151 | &gnutls_module); | 163 | &gnutls_module); |
152 | if (sc->cert_file != NULL && sc->key_file != NULL) { | 164 | if (sc->cert_file != NULL && sc->key_file != NULL) { |
153 | gnutls_certificate_set_x509_key_file(sc->certs, sc->cert_file, | 165 | gnutls_certificate_set_x509_key_file(sc->certs, sc->cert_file, |
154 | sc->key_file, | 166 | sc->key_file, |
155 | GNUTLS_X509_FMT_PEM); | 167 | GNUTLS_X509_FMT_PEM); |
168 | // gnutls_certificate_set_rsa_export_params(sc->certs, rsa_params); | ||
169 | // gnutls_certificate_set_dh_params(sc->certs, dh_params); | ||
156 | } | 170 | } |
157 | else { | 171 | else if(sc->enabled == GNUTLS_ENABLED_TRUE ){ |
158 | ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, | 172 | ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, |
159 | "[GnuTLS] - Host '%s' is missing a Cert and Key File!", | 173 | "[GnuTLS] - Host '%s:%d' is missing a Cert and Key File!", |
160 | s->server_hostname); | 174 | s->server_hostname, s->port); |
161 | } | 175 | } |
162 | |||
163 | /** | ||
164 | * TODO: Is it okay for all virtual hosts to | ||
165 | * share the same DH/RSAparams? | ||
166 | */ | ||
167 | gnutls_certificate_set_dh_params(sc->certs, dh_params); | ||
168 | gnutls_certificate_set_rsa_export_params(sc->certs, rsa_params); | ||
169 | } | 176 | } |
170 | 177 | ||
178 | |||
171 | ap_add_version_component(p, "GnuTLS/" LIBGNUTLS_VERSION); | 179 | ap_add_version_component(p, "GnuTLS/" LIBGNUTLS_VERSION); |
172 | return OK; | 180 | return OK; |
173 | } | 181 | } |
174 | 182 | ||
175 | static const char *gnutls_hook_http_method(const request_rec * r) | 183 | static const char *gnutls_hook_http_method(const request_rec * r) |
176 | { | 184 | { |
177 | gnutls_srvconf_t *sc = | 185 | gnutls_srvconf_rec *sc = |
178 | (gnutls_srvconf_t *) ap_get_module_config(r->server->module_config, | 186 | (gnutls_srvconf_rec *) ap_get_module_config(r->server->module_config, |
179 | &gnutls_module); | 187 | &gnutls_module); |
180 | 188 | ||
181 | if (sc->enabled == GNUTLS_ENABLED_FALSE) { | 189 | if (sc->enabled == GNUTLS_ENABLED_FALSE) { |
@@ -187,8 +195,8 @@ static const char *gnutls_hook_http_method(const request_rec * r) | |||
187 | 195 | ||
188 | static apr_port_t gnutls_hook_default_port(const request_rec * r) | 196 | static apr_port_t gnutls_hook_default_port(const request_rec * r) |
189 | { | 197 | { |
190 | gnutls_srvconf_t *sc = | 198 | gnutls_srvconf_rec *sc = |
191 | (gnutls_srvconf_t *) ap_get_module_config(r->server->module_config, | 199 | (gnutls_srvconf_rec *) ap_get_module_config(r->server->module_config, |
192 | &gnutls_module); | 200 | &gnutls_module); |
193 | 201 | ||
194 | if (sc->enabled == GNUTLS_ENABLED_FALSE) { | 202 | if (sc->enabled == GNUTLS_ENABLED_FALSE) { |
@@ -198,6 +206,7 @@ static apr_port_t gnutls_hook_default_port(const request_rec * r) | |||
198 | return 443; | 206 | return 443; |
199 | } | 207 | } |
200 | 208 | ||
209 | #ifdef GNUTLS_AS_FILTER | ||
201 | /** | 210 | /** |
202 | * From mod_ssl / ssl_engine_io.c | 211 | * From mod_ssl / ssl_engine_io.c |
203 | * This function will read from a brigade and discard the read buckets as it | 212 | * This function will read from a brigade and discard the read buckets as it |
@@ -336,15 +345,17 @@ static ssize_t gnutls_transport_write(gnutls_transport_ptr_t ptr, | |||
336 | //APR_BRIGADE_INSERT_TAIL(outctx->bb, bucket); | 345 | //APR_BRIGADE_INSERT_TAIL(outctx->bb, bucket); |
337 | return 0; | 346 | return 0; |
338 | } | 347 | } |
348 | #endif /* GNUTLS_AS_FILTER */ | ||
339 | 349 | ||
340 | static int gnutls_hook_pre_connection(conn_rec * c, void *csd) | 350 | static int gnutls_hook_pre_connection(conn_rec * c, void *csd) |
341 | { | 351 | { |
342 | #ifndef GNUTLS_AS_FILTER | 352 | #ifndef GNUTLS_AS_FILTER |
343 | int cfd; | 353 | int cfd; |
354 | int ret; | ||
344 | #endif | 355 | #endif |
345 | gnutls_handle_t *ctxt; | 356 | gnutls_handle_t *ctxt; |
346 | gnutls_srvconf_t *sc = | 357 | gnutls_srvconf_rec *sc = |
347 | (gnutls_srvconf_t *) ap_get_module_config(c->base_server-> | 358 | (gnutls_srvconf_rec *) ap_get_module_config(c->base_server-> |
348 | module_config, | 359 | module_config, |
349 | &gnutls_module); | 360 | &gnutls_module); |
350 | 361 | ||
@@ -357,13 +368,17 @@ static int gnutls_hook_pre_connection(conn_rec * c, void *csd) | |||
357 | ctxt->sc = sc; | 368 | ctxt->sc = sc; |
358 | gnutls_init(&ctxt->session, GNUTLS_SERVER); | 369 | gnutls_init(&ctxt->session, GNUTLS_SERVER); |
359 | 370 | ||
360 | gnutls_set_default_priority(ctxt->session); | 371 | gnutls_cipher_set_priority(ctxt->session, sc->ciphers); |
372 | gnutls_compression_set_priority(ctxt->session, sc->compression); | ||
373 | gnutls_kx_set_priority(ctxt->session, sc->key_exchange); | ||
374 | gnutls_protocol_set_priority(ctxt->session, sc->protocol); | ||
375 | gnutls_mac_set_priority(ctxt->session, sc->macs); | ||
361 | 376 | ||
362 | gnutls_credentials_set(ctxt->session, GNUTLS_CRD_CERTIFICATE, sc->certs); | 377 | gnutls_credentials_set(ctxt->session, GNUTLS_CRD_CERTIFICATE, sc->certs); |
378 | gnutls_certificate_server_set_request(ctxt->session, GNUTLS_CERT_IGNORE); | ||
363 | 379 | ||
364 | gnutls_certificate_server_set_request(ctxt->session, GNUTLS_CERT_REQUEST); | 380 | // gnutls_dh_set_prime_bits(ctxt->session, DH_BITS); |
365 | 381 | ||
366 | gnutls_dh_set_prime_bits(ctxt->session, DH_BITS); | ||
367 | 382 | ||
368 | ap_set_module_config(c->conn_config, &gnutls_module, ctxt); | 383 | ap_set_module_config(c->conn_config, &gnutls_module, ctxt); |
369 | 384 | ||
@@ -377,41 +392,97 @@ static int gnutls_hook_pre_connection(conn_rec * c, void *csd) | |||
377 | #else | 392 | #else |
378 | apr_os_sock_get(&cfd, csd); | 393 | apr_os_sock_get(&cfd, csd); |
379 | gnutls_transport_set_ptr(ctxt->session, (gnutls_transport_ptr)cfd); | 394 | gnutls_transport_set_ptr(ctxt->session, (gnutls_transport_ptr)cfd); |
395 | gnutls_credentials_set(ctxt->session, GNUTLS_CRD_ANON, sc->anoncred); | ||
396 | |||
397 | do{ | ||
398 | ret = gnutls_handshake(ctxt->session); | ||
399 | |||
400 | if(ret == GNUTLS_E_INTERRUPTED || ret == GNUTLS_E_AGAIN){ | ||
401 | continue; | ||
402 | } | ||
403 | |||
404 | if (ret < 0) { | ||
405 | if (ret == GNUTLS_E_WARNING_ALERT_RECEIVED || ret == GNUTLS_E_FATAL_ALERT_RECEIVED) { | ||
406 | ret = gnutls_alert_get(ctxt->session); | ||
407 | ap_log_error(APLOG_MARK, APLOG_ERR, 0, c->base_server, | ||
408 | "GnuTLS: Hanshake Alert (%d) '%s'.\n", ret, gnutls_alert_get_name(ret)); | ||
409 | } | ||
410 | |||
411 | if (gnutls_error_is_fatal(ret) != 0) { | ||
412 | gnutls_deinit(ctxt->session); | ||
413 | ap_log_error(APLOG_MARK, APLOG_ERR, 0, c->base_server, | ||
414 | "GnuTLS: Handshake Failed (%d) '%s'",ret, gnutls_strerror(ret)); | ||
415 | sc->non_https = 1; | ||
416 | break; | ||
417 | } | ||
418 | } | ||
419 | break; /* all done with the handshake */ | ||
420 | } while(1); | ||
380 | #endif | 421 | #endif |
381 | return OK; | 422 | return OK; |
382 | } | 423 | } |
383 | 424 | ||
384 | static const char *gnutls_set_ca_file(cmd_parms * parms, void *dummy, | 425 | static const char *gnutls_set_cert_file(cmd_parms * parms, void *dummy, |
385 | const char *arg) | 426 | const char *arg) |
386 | { | 427 | { |
387 | gnutls_srvconf_t *sc = | 428 | gnutls_srvconf_rec *sc = |
388 | (gnutls_srvconf_t *) ap_get_module_config(parms->server-> | 429 | (gnutls_srvconf_rec *) ap_get_module_config(parms->server-> |
389 | module_config, | 430 | module_config, |
390 | &gnutls_module); | 431 | &gnutls_module); |
391 | /* TODO: CRL, CAFile */ | 432 | sc->cert_file = apr_pstrdup(parms->pool, arg); |
392 | // gnutls_certificate_set_x509_trust_file(sc->certs, CAFILE, | 433 | return NULL; |
393 | // GNUTLS_X509_FMT_PEM); | 434 | } |
435 | |||
436 | static const char *gnutls_set_key_file(cmd_parms * parms, void *dummy, | ||
437 | const char *arg) | ||
438 | { | ||
439 | gnutls_srvconf_rec *sc = | ||
440 | (gnutls_srvconf_rec *) ap_get_module_config(parms->server-> | ||
441 | module_config, | ||
442 | &gnutls_module); | ||
443 | sc->key_file = apr_pstrdup(parms->pool, arg); | ||
444 | return NULL; | ||
445 | } | ||
446 | |||
447 | static const char *gnutls_set_enabled(cmd_parms * parms, void *dummy, | ||
448 | const char *arg) | ||
449 | { | ||
450 | gnutls_srvconf_rec *sc = | ||
451 | (gnutls_srvconf_rec *) ap_get_module_config(parms->server-> | ||
452 | module_config, | ||
453 | &gnutls_module); | ||
454 | if (!strcasecmp(arg, "On")) { | ||
455 | sc->enabled = GNUTLS_ENABLED_TRUE; | ||
456 | } | ||
457 | else if (!strcasecmp(arg, "Off")) { | ||
458 | sc->enabled = GNUTLS_ENABLED_FALSE; | ||
459 | } | ||
460 | else { | ||
461 | return "GnuTLSEnable must be set to 'On' or 'Off'"; | ||
462 | } | ||
463 | |||
394 | return NULL; | 464 | return NULL; |
395 | } | 465 | } |
396 | 466 | ||
397 | static const command_rec gnutls_cmds[] = { | 467 | static const command_rec gnutls_cmds[] = { |
398 | AP_INIT_FLAG("GnuTLSEnable", ap_set_flag_slot, | 468 | AP_INIT_TAKE1("GnuTLSCertificateFile", gnutls_set_cert_file, |
399 | (void *) APR_OFFSETOF(gnutls_srvconf_t, enabled), RSRC_CONF, | 469 | NULL, |
400 | "Whether this server has GnuTLS Enabled. Default: Off"), | ||
401 | AP_INIT_TAKE1("GnuTLSCertificateFile", ap_set_string_slot, | ||
402 | (void *) APR_OFFSETOF(gnutls_srvconf_t, cert_file), | ||
403 | RSRC_CONF, | 470 | RSRC_CONF, |
404 | "SSL Server Key file"), | 471 | "SSL Server Key file"), |
405 | AP_INIT_TAKE1("GnuTLSKeyFile", ap_set_string_slot, | 472 | AP_INIT_TAKE1("GnuTLSKeyFile", gnutls_set_key_file, |
406 | (void *) APR_OFFSETOF(gnutls_srvconf_t, key_file), | 473 | NULL, |
407 | RSRC_CONF, | 474 | RSRC_CONF, |
408 | "SSL Server Certificate file"), | 475 | "SSL Server Certificate file"), |
476 | AP_INIT_TAKE1("GnuTLSEnable", gnutls_set_enabled, | ||
477 | NULL, RSRC_CONF, | ||
478 | "Whether this server has GnuTLS Enabled. Default: Off"), | ||
479 | |||
409 | {NULL} | 480 | {NULL} |
410 | }; | 481 | }; |
411 | 482 | ||
412 | /* TODO: CACertificateFile & Client Authentication | 483 | /* TODO: CACertificateFile & Client Authentication |
413 | * AP_INIT_TAKE1("GnuTLSCACertificateFile", ap_set_server_string_slot, | 484 | * AP_INIT_TAKE1("GnuTLSCACertificateFile", ap_set_server_string_slot, |
414 | * (void *) APR_OFFSETOF(gnutls_srvconf_t, key_file), NULL, | 485 | * (void *) APR_OFFSETOF(gnutls_srvconf_rec, key_file), NULL, |
415 | * RSRC_CONF, | 486 | * RSRC_CONF, |
416 | * "CA"), | 487 | * "CA"), |
417 | */ | 488 | */ |
@@ -430,24 +501,59 @@ static void gnutls_hooks(apr_pool_t * p) | |||
430 | /* ap_register_output_filter ("UPGRADE_FILTER", | 501 | /* ap_register_output_filter ("UPGRADE_FILTER", |
431 | * ssl_io_filter_Upgrade, NULL, AP_FTYPE_PROTOCOL + 5); | 502 | * ssl_io_filter_Upgrade, NULL, AP_FTYPE_PROTOCOL + 5); |
432 | */ | 503 | */ |
433 | 504 | #ifdef GNUTLS_AS_FILTER | |
434 | ap_register_input_filter(GNUTLS_INPUT_FILTER_NAME, gnutls_filter_input, | 505 | ap_register_input_filter(GNUTLS_INPUT_FILTER_NAME, gnutls_filter_input, |
435 | NULL, AP_FTYPE_CONNECTION + 5); | 506 | NULL, AP_FTYPE_CONNECTION + 5); |
436 | ap_register_output_filter(GNUTLS_OUTPUT_FILTER_NAME, gnutls_filter_output, | 507 | ap_register_output_filter(GNUTLS_OUTPUT_FILTER_NAME, gnutls_filter_output, |
437 | NULL, AP_FTYPE_CONNECTION + 5); | 508 | NULL, AP_FTYPE_CONNECTION + 5); |
438 | 509 | #endif | |
439 | } | 510 | } |
440 | 511 | ||
441 | static void *gnutls_config_server_create(apr_pool_t * p, server_rec * s) | 512 | static void *gnutls_config_server_create(apr_pool_t * p, server_rec * s) |
442 | { | 513 | { |
443 | gnutls_srvconf_t *sc = apr_pcalloc(p, sizeof *sc); | 514 | int i; |
515 | gnutls_srvconf_rec *sc = apr_pcalloc(p, sizeof(*sc)); | ||
444 | 516 | ||
445 | sc->enabled = GNUTLS_ENABLED_FALSE; | 517 | sc->enabled = GNUTLS_ENABLED_FALSE; |
518 | sc->non_https = 0; | ||
446 | 519 | ||
447 | gnutls_certificate_allocate_credentials(&sc->certs); | 520 | gnutls_certificate_allocate_credentials(&sc->certs); |
448 | 521 | gnutls_anon_allocate_server_credentials(&sc->anoncred); | |
449 | sc->key_file = NULL; | 522 | sc->key_file = NULL; |
450 | sc->cert_file = NULL; | 523 | sc->cert_file = NULL; |
524 | |||
525 | i = 0; | ||
526 | sc->ciphers[i++] = GNUTLS_CIPHER_RIJNDAEL_128_CBC; | ||
527 | sc->ciphers[i++] = GNUTLS_CIPHER_ARCFOUR_128; | ||
528 | sc->ciphers[i++] = GNUTLS_CIPHER_3DES_CBC; | ||
529 | sc->ciphers[i++] = GNUTLS_CIPHER_ARCFOUR_40; | ||
530 | sc->ciphers[i] = 0; | ||
531 | |||
532 | i = 0; | ||
533 | sc->key_exchange[i++] = GNUTLS_KX_RSA; | ||
534 | sc->key_exchange[i++] = GNUTLS_KX_RSA_EXPORT; | ||
535 | sc->key_exchange[i++] = GNUTLS_KX_DHE_RSA; | ||
536 | sc->key_exchange[i++] = GNUTLS_KX_DHE_DSS; | ||
537 | sc->key_exchange[i] = 0; | ||
538 | |||
539 | i = 0; | ||
540 | sc->macs[i++] = GNUTLS_MAC_MD5; | ||
541 | sc->macs[i++] = GNUTLS_MAC_SHA; | ||
542 | sc->macs[i++] = GNUTLS_MAC_RMD160; | ||
543 | sc->macs[i] = 0; | ||
544 | |||
545 | i = 0; | ||
546 | sc->protocol[i++] = GNUTLS_TLS1_1; | ||
547 | sc->protocol[i++] = GNUTLS_TLS1; | ||
548 | sc->protocol[i++] = GNUTLS_SSL3; | ||
549 | sc->protocol[i] = 0; | ||
550 | |||
551 | i = 0; | ||
552 | sc->compression[i++] = GNUTLS_COMP_NULL; | ||
553 | sc->compression[i++] = GNUTLS_COMP_ZLIB; | ||
554 | sc->compression[i++] = GNUTLS_COMP_LZO; | ||
555 | sc->compression[i] = 0; | ||
556 | |||
451 | return sc; | 557 | return sc; |
452 | } | 558 | } |
453 | 559 | ||