Home | History | Annotate | Line # | Download | only in statem
      1 /*
      2  * Copyright 2014-2026 The OpenSSL Project Authors. All Rights Reserved.
      3  *
      4  * Licensed under the Apache License 2.0 (the "License").  You may not use
      5  * this file except in compliance with the License.  You can obtain a copy
      6  * in the file LICENSE in the source distribution or at
      7  * https://www.openssl.org/source/license.html
      8  */
      9 
     10 /* Custom extension utility functions */
     11 
     12 #include <openssl/ct.h>
     13 #include "../ssl_local.h"
     14 #include "internal/cryptlib.h"
     15 #include "internal/ssl_unwrap.h"
     16 #include "statem_local.h"
     17 
     18 typedef struct {
     19     void *add_arg;
     20     custom_ext_add_cb add_cb;
     21     custom_ext_free_cb free_cb;
     22 } custom_ext_add_cb_wrap;
     23 
     24 typedef struct {
     25     void *parse_arg;
     26     custom_ext_parse_cb parse_cb;
     27 } custom_ext_parse_cb_wrap;
     28 
     29 /*
     30  * Provide thin wrapper callbacks which convert new style arguments to old style
     31  */
     32 static int custom_ext_add_old_cb_wrap(SSL *s, unsigned int ext_type,
     33     unsigned int context,
     34     const unsigned char **out,
     35     size_t *outlen, X509 *x, size_t chainidx,
     36     int *al, void *add_arg)
     37 {
     38     custom_ext_add_cb_wrap *add_cb_wrap = (custom_ext_add_cb_wrap *)add_arg;
     39 
     40     if (add_cb_wrap->add_cb == NULL)
     41         return 1;
     42 
     43     return add_cb_wrap->add_cb(s, ext_type, out, outlen, al,
     44         add_cb_wrap->add_arg);
     45 }
     46 
     47 static void custom_ext_free_old_cb_wrap(SSL *s, unsigned int ext_type,
     48     unsigned int context,
     49     const unsigned char *out, void *add_arg)
     50 {
     51     custom_ext_add_cb_wrap *add_cb_wrap = (custom_ext_add_cb_wrap *)add_arg;
     52 
     53     if (add_cb_wrap->free_cb == NULL)
     54         return;
     55 
     56     add_cb_wrap->free_cb(s, ext_type, out, add_cb_wrap->add_arg);
     57 }
     58 
     59 static int custom_ext_parse_old_cb_wrap(SSL *s, unsigned int ext_type,
     60     unsigned int context,
     61     const unsigned char *in,
     62     size_t inlen, X509 *x, size_t chainidx,
     63     int *al, void *parse_arg)
     64 {
     65     custom_ext_parse_cb_wrap *parse_cb_wrap = (custom_ext_parse_cb_wrap *)parse_arg;
     66 
     67     if (parse_cb_wrap->parse_cb == NULL)
     68         return 1;
     69 
     70     return parse_cb_wrap->parse_cb(s, ext_type, in, inlen, al,
     71         parse_cb_wrap->parse_arg);
     72 }
     73 
     74 /*
     75  * Find a custom extension from the list. The |role| param is there to
     76  * support the legacy API where custom extensions for client and server could
     77  * be set independently on the same SSL_CTX. It is set to ENDPOINT_SERVER if we
     78  * are trying to find a method relevant to the server, ENDPOINT_CLIENT for the
     79  * client, or ENDPOINT_BOTH for either
     80  */
     81 custom_ext_method *custom_ext_find(const custom_ext_methods *exts,
     82     ENDPOINT role, unsigned int ext_type,
     83     size_t *idx)
     84 {
     85     size_t i;
     86     custom_ext_method *meth = exts->meths;
     87 
     88     for (i = 0; i < exts->meths_count; i++, meth++) {
     89         if (ext_type == meth->ext_type
     90             && (role == ENDPOINT_BOTH || role == meth->role
     91                 || meth->role == ENDPOINT_BOTH)) {
     92             if (idx != NULL)
     93                 *idx = i;
     94             return meth;
     95         }
     96     }
     97     return NULL;
     98 }
     99 
    100 /*
    101  * Initialise custom extensions flags to indicate neither sent nor received.
    102  */
    103 void custom_ext_init(custom_ext_methods *exts)
    104 {
    105     size_t i;
    106     custom_ext_method *meth = exts->meths;
    107 
    108     for (i = 0; i < exts->meths_count; i++, meth++)
    109         meth->ext_flags &= ~(SSL_EXT_FLAG_SENT | SSL_EXT_FLAG_RECEIVED);
    110 }
    111 
    112 /* Pass received custom extension data to the application for parsing. */
    113 int custom_ext_parse(SSL_CONNECTION *s, unsigned int context,
    114     unsigned int ext_type,
    115     const unsigned char *ext_data, size_t ext_size, X509 *x,
    116     size_t chainidx)
    117 {
    118     int al = 0;
    119     custom_ext_methods *exts = &s->cert->custext;
    120     custom_ext_method *meth;
    121     ENDPOINT role = ENDPOINT_BOTH;
    122 
    123     if ((context & (SSL_EXT_CLIENT_HELLO | SSL_EXT_TLS1_2_SERVER_HELLO)) != 0)
    124         role = s->server ? ENDPOINT_SERVER : ENDPOINT_CLIENT;
    125 
    126     meth = custom_ext_find(exts, role, ext_type, NULL);
    127     /* If not found return success */
    128     if (!meth)
    129         return 1;
    130 
    131     /* Check if extension is defined for our protocol. If not, skip */
    132     if (!extension_is_relevant(s, meth->context, context))
    133         return 1;
    134 
    135     if ((context & (SSL_EXT_TLS1_2_SERVER_HELLO | SSL_EXT_TLS1_3_SERVER_HELLO | SSL_EXT_TLS1_3_ENCRYPTED_EXTENSIONS)) != 0) {
    136         /*
    137          * If it's ServerHello or EncryptedExtensions we can't have any
    138          * extensions not sent in ClientHello.
    139          */
    140         if ((meth->ext_flags & SSL_EXT_FLAG_SENT) == 0) {
    141             SSLfatal(s, TLS1_AD_UNSUPPORTED_EXTENSION, SSL_R_BAD_EXTENSION);
    142             return 0;
    143         }
    144     }
    145 
    146     /*
    147      * Extensions received in the ClientHello or CertificateRequest are marked
    148      * with the SSL_EXT_FLAG_RECEIVED. This is so we know to add the equivalent
    149      * extensions in the response messages
    150      */
    151     if ((context & (SSL_EXT_CLIENT_HELLO | SSL_EXT_TLS1_3_CERTIFICATE_REQUEST))
    152         != 0)
    153         meth->ext_flags |= SSL_EXT_FLAG_RECEIVED;
    154 
    155     /* If no parse function set return success */
    156     if (meth->parse_cb == NULL)
    157         return 1;
    158 
    159     if (meth->parse_cb(SSL_CONNECTION_GET_USER_SSL(s), ext_type, context, ext_data,
    160             ext_size, x, chainidx, &al, meth->parse_arg)
    161         <= 0) {
    162         SSLfatal(s, al, SSL_R_BAD_EXTENSION);
    163         return 0;
    164     }
    165 
    166     return 1;
    167 }
    168 
    169 /*
    170  * Request custom extension data from the application and add to the return
    171  * buffer.
    172  */
    173 int custom_ext_add(SSL_CONNECTION *s, int context, WPACKET *pkt, X509 *x,
    174     size_t chainidx, int maxversion)
    175 {
    176     custom_ext_methods *exts = &s->cert->custext;
    177     custom_ext_method *meth;
    178     size_t i;
    179     int al;
    180     int for_comp = (context & SSL_EXT_TLS1_3_CERTIFICATE_COMPRESSION) != 0;
    181 
    182     for (i = 0; i < exts->meths_count; i++) {
    183         const unsigned char *out = NULL;
    184         size_t outlen = 0;
    185 
    186         meth = exts->meths + i;
    187 
    188         if (!should_add_extension(s, meth->context, context, maxversion))
    189             continue;
    190 
    191         if ((context & (SSL_EXT_TLS1_2_SERVER_HELLO | SSL_EXT_TLS1_3_SERVER_HELLO | SSL_EXT_TLS1_3_ENCRYPTED_EXTENSIONS | SSL_EXT_TLS1_3_CERTIFICATE | SSL_EXT_TLS1_3_RAW_PUBLIC_KEY | SSL_EXT_TLS1_3_HELLO_RETRY_REQUEST)) != 0) {
    192             /* Only send extensions present in ClientHello/CertificateRequest */
    193             if (!(meth->ext_flags & SSL_EXT_FLAG_RECEIVED))
    194                 continue;
    195         }
    196         /*
    197          * We skip it if the callback is absent - except for a ClientHello where
    198          * we add an empty extension.
    199          */
    200         if ((context & SSL_EXT_CLIENT_HELLO) == 0 && meth->add_cb == NULL)
    201             continue;
    202 
    203         if (meth->add_cb != NULL) {
    204             int cb_retval = meth->add_cb(SSL_CONNECTION_GET_USER_SSL(s),
    205                 meth->ext_type, context, &out,
    206                 &outlen, x, chainidx, &al,
    207                 meth->add_arg);
    208 
    209             if (cb_retval < 0) {
    210                 if (!for_comp)
    211                     SSLfatal(s, al, SSL_R_CALLBACK_FAILED);
    212                 return 0; /* error */
    213             }
    214             if (cb_retval == 0)
    215                 continue; /* skip this extension */
    216         }
    217 
    218         if (!WPACKET_put_bytes_u16(pkt, meth->ext_type)
    219             || !WPACKET_start_sub_packet_u16(pkt)
    220             || (outlen > 0 && !WPACKET_memcpy(pkt, out, outlen))
    221             || !WPACKET_close(pkt)) {
    222             if (meth->free_cb != NULL)
    223                 meth->free_cb(SSL_CONNECTION_GET_USER_SSL(s), meth->ext_type,
    224                     context, out, meth->add_arg);
    225             if (!for_comp)
    226                 SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR);
    227             return 0;
    228         }
    229         if ((context & SSL_EXT_CLIENT_HELLO) != 0) {
    230             /*
    231              * We can't send duplicates: code logic should prevent this.
    232              */
    233             if (!ossl_assert((meth->ext_flags & SSL_EXT_FLAG_SENT) == 0)) {
    234                 if (meth->free_cb != NULL)
    235                     meth->free_cb(SSL_CONNECTION_GET_USER_SSL(s), meth->ext_type,
    236                         context, out, meth->add_arg);
    237                 if (!for_comp)
    238                     SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR);
    239                 return 0;
    240             }
    241             /*
    242              * Indicate extension has been sent: this is both a sanity check to
    243              * ensure we don't send duplicate extensions and indicates that it
    244              * is not an error if the extension is present in ServerHello.
    245              */
    246             meth->ext_flags |= SSL_EXT_FLAG_SENT;
    247         }
    248         if (meth->free_cb != NULL)
    249             meth->free_cb(SSL_CONNECTION_GET_USER_SSL(s), meth->ext_type,
    250                 context, out, meth->add_arg);
    251     }
    252     return 1;
    253 }
    254 
    255 /* Copy the flags from src to dst for any extensions that exist in both */
    256 int custom_exts_copy_flags(custom_ext_methods *dst,
    257     const custom_ext_methods *src)
    258 {
    259     size_t i;
    260     custom_ext_method *methsrc = src->meths;
    261 
    262     for (i = 0; i < src->meths_count; i++, methsrc++) {
    263         custom_ext_method *methdst = custom_ext_find(dst, methsrc->role,
    264             methsrc->ext_type, NULL);
    265 
    266         if (methdst == NULL)
    267             continue;
    268 
    269         methdst->ext_flags = methsrc->ext_flags;
    270     }
    271 
    272     return 1;
    273 }
    274 
    275 /* Copy old style API wrapper arguments */
    276 static void custom_ext_copy_old_cb(custom_ext_method *methdst,
    277     const custom_ext_method *methsrc,
    278     int *err)
    279 {
    280     if (methsrc->add_cb != custom_ext_add_old_cb_wrap)
    281         return;
    282 
    283     if (*err) {
    284         methdst->add_arg = NULL;
    285         methdst->parse_arg = NULL;
    286         return;
    287     }
    288 
    289     methdst->add_arg = OPENSSL_memdup(methsrc->add_arg,
    290         sizeof(custom_ext_add_cb_wrap));
    291     methdst->parse_arg = OPENSSL_memdup(methsrc->parse_arg,
    292         sizeof(custom_ext_parse_cb_wrap));
    293 
    294     if (methdst->add_arg == NULL || methdst->parse_arg == NULL)
    295         *err = 1;
    296 
    297     return;
    298 }
    299 
    300 /* Copy table of custom extensions */
    301 int custom_exts_copy(custom_ext_methods *dst, const custom_ext_methods *src)
    302 {
    303     size_t i;
    304     int err = 0;
    305 
    306     if (src->meths_count > 0) {
    307         dst->meths = OPENSSL_memdup(src->meths,
    308             sizeof(*src->meths) * src->meths_count);
    309         if (dst->meths == NULL)
    310             return 0;
    311         dst->meths_count = src->meths_count;
    312 
    313         for (i = 0; i < src->meths_count; i++)
    314             custom_ext_copy_old_cb(&dst->meths[i], &src->meths[i], &err);
    315     }
    316 
    317     if (err) {
    318         custom_exts_free(dst);
    319         return 0;
    320     }
    321 
    322     return 1;
    323 }
    324 
    325 /* Copy custom extensions that were set on connection */
    326 int custom_exts_copy_conn(custom_ext_methods *dst,
    327     const custom_ext_methods *src)
    328 {
    329     size_t i;
    330     int err = 0;
    331 
    332     if (src->meths_count > 0) {
    333         size_t meths_count = 0;
    334 
    335         for (i = 0; i < src->meths_count; i++)
    336             if ((src->meths[i].ext_flags & SSL_EXT_FLAG_CONN) != 0)
    337                 meths_count++;
    338 
    339         if (meths_count > 0) {
    340             custom_ext_method *methdst = OPENSSL_realloc(dst->meths,
    341                 (dst->meths_count + meths_count) * sizeof(custom_ext_method));
    342 
    343             if (methdst == NULL)
    344                 return 0;
    345 
    346             dst->meths = methdst;
    347             methdst += dst->meths_count;
    348 
    349             for (i = 0; i < src->meths_count; i++) {
    350                 custom_ext_method *methsrc = &src->meths[i];
    351 
    352                 if ((methsrc->ext_flags & SSL_EXT_FLAG_CONN) == 0)
    353                     continue;
    354 
    355                 memcpy(methdst, methsrc, sizeof(custom_ext_method));
    356                 custom_ext_copy_old_cb(methdst, methsrc, &err);
    357                 methdst++;
    358             }
    359 
    360             dst->meths_count += meths_count;
    361         }
    362     }
    363 
    364     if (err) {
    365         custom_exts_free(dst);
    366         return 0;
    367     }
    368 
    369     return 1;
    370 }
    371 
    372 void custom_exts_free(custom_ext_methods *exts)
    373 {
    374     size_t i;
    375     custom_ext_method *meth;
    376 
    377     for (i = 0, meth = exts->meths; i < exts->meths_count; i++, meth++) {
    378         if (meth->add_cb != custom_ext_add_old_cb_wrap)
    379             continue;
    380 
    381         /* Old style API wrapper. Need to free the arguments too */
    382         OPENSSL_free(meth->add_arg);
    383         OPENSSL_free(meth->parse_arg);
    384     }
    385     OPENSSL_free(exts->meths);
    386     exts->meths = NULL;
    387     exts->meths_count = 0;
    388 }
    389 
    390 /* Return true if a client custom extension exists, false otherwise */
    391 int SSL_CTX_has_client_custom_ext(const SSL_CTX *ctx, unsigned int ext_type)
    392 {
    393     return custom_ext_find(&ctx->cert->custext, ENDPOINT_CLIENT, ext_type,
    394                NULL)
    395         != NULL;
    396 }
    397 
    398 int ossl_tls_add_custom_ext_intern(SSL_CTX *ctx, custom_ext_methods *exts,
    399     ENDPOINT role, unsigned int ext_type,
    400     unsigned int context,
    401     SSL_custom_ext_add_cb_ex add_cb,
    402     SSL_custom_ext_free_cb_ex free_cb,
    403     void *add_arg,
    404     SSL_custom_ext_parse_cb_ex parse_cb,
    405     void *parse_arg)
    406 {
    407     custom_ext_method *meth, *tmp;
    408 
    409     /*
    410      * Check application error: if add_cb is not set free_cb will never be
    411      * called.
    412      */
    413     if (add_cb == NULL && free_cb != NULL)
    414         return 0;
    415 
    416     if (exts == NULL)
    417         exts = &ctx->cert->custext;
    418 
    419 #ifndef OPENSSL_NO_CT
    420     /*
    421      * We don't want applications registering callbacks for SCT extensions
    422      * whilst simultaneously using the built-in SCT validation features, as
    423      * these two things may not play well together.
    424      */
    425     if (ext_type == TLSEXT_TYPE_signed_certificate_timestamp
    426         && (context & SSL_EXT_CLIENT_HELLO) != 0
    427         && ctx != NULL
    428         && SSL_CTX_ct_is_enabled(ctx))
    429         return 0;
    430 #endif
    431 
    432     /*
    433      * Don't add if extension supported internally, but make exception
    434      * for extension types that previously were not supported, but now are.
    435      */
    436     if (SSL_extension_supported(ext_type)
    437         && ext_type != TLSEXT_TYPE_signed_certificate_timestamp)
    438         return 0;
    439 
    440     /* Extension type must fit in 16 bits */
    441     if (ext_type > 0xffff)
    442         return 0;
    443     /* Search for duplicate */
    444     if (custom_ext_find(exts, role, ext_type, NULL))
    445         return 0;
    446     tmp = OPENSSL_realloc(exts->meths,
    447         (exts->meths_count + 1) * sizeof(custom_ext_method));
    448     if (tmp == NULL)
    449         return 0;
    450 
    451     exts->meths = tmp;
    452     meth = exts->meths + exts->meths_count;
    453     memset(meth, 0, sizeof(*meth));
    454     meth->role = role;
    455     meth->context = context;
    456     meth->parse_cb = parse_cb;
    457     meth->add_cb = add_cb;
    458     meth->free_cb = free_cb;
    459     meth->ext_type = ext_type;
    460     meth->ext_flags = (ctx == NULL) ? SSL_EXT_FLAG_CONN : 0;
    461     meth->add_arg = add_arg;
    462     meth->parse_arg = parse_arg;
    463     exts->meths_count++;
    464     return 1;
    465 }
    466 
    467 static int add_old_custom_ext(SSL_CTX *ctx, ENDPOINT role,
    468     unsigned int ext_type,
    469     unsigned int context,
    470     custom_ext_add_cb add_cb,
    471     custom_ext_free_cb free_cb,
    472     void *add_arg,
    473     custom_ext_parse_cb parse_cb, void *parse_arg)
    474 {
    475     custom_ext_add_cb_wrap *add_cb_wrap
    476         = OPENSSL_malloc(sizeof(*add_cb_wrap));
    477     custom_ext_parse_cb_wrap *parse_cb_wrap
    478         = OPENSSL_malloc(sizeof(*parse_cb_wrap));
    479     int ret;
    480 
    481     if (add_cb_wrap == NULL || parse_cb_wrap == NULL) {
    482         OPENSSL_free(add_cb_wrap);
    483         OPENSSL_free(parse_cb_wrap);
    484         return 0;
    485     }
    486 
    487     add_cb_wrap->add_arg = add_arg;
    488     add_cb_wrap->add_cb = add_cb;
    489     add_cb_wrap->free_cb = free_cb;
    490     parse_cb_wrap->parse_arg = parse_arg;
    491     parse_cb_wrap->parse_cb = parse_cb;
    492 
    493     ret = ossl_tls_add_custom_ext_intern(ctx, NULL, role, ext_type,
    494         context,
    495         custom_ext_add_old_cb_wrap,
    496         custom_ext_free_old_cb_wrap,
    497         add_cb_wrap,
    498         custom_ext_parse_old_cb_wrap,
    499         parse_cb_wrap);
    500 
    501     if (!ret) {
    502         OPENSSL_free(add_cb_wrap);
    503         OPENSSL_free(parse_cb_wrap);
    504     }
    505 
    506     return ret;
    507 }
    508 
    509 /* Application level functions to add the old custom extension callbacks */
    510 int SSL_CTX_add_client_custom_ext(SSL_CTX *ctx, unsigned int ext_type,
    511     custom_ext_add_cb add_cb,
    512     custom_ext_free_cb free_cb,
    513     void *add_arg,
    514     custom_ext_parse_cb parse_cb, void *parse_arg)
    515 {
    516     return add_old_custom_ext(ctx, ENDPOINT_CLIENT, ext_type,
    517         SSL_EXT_TLS1_2_AND_BELOW_ONLY
    518             | SSL_EXT_CLIENT_HELLO
    519             | SSL_EXT_TLS1_2_SERVER_HELLO
    520             | SSL_EXT_IGNORE_ON_RESUMPTION,
    521         add_cb, free_cb, add_arg, parse_cb, parse_arg);
    522 }
    523 
    524 int SSL_CTX_add_server_custom_ext(SSL_CTX *ctx, unsigned int ext_type,
    525     custom_ext_add_cb add_cb,
    526     custom_ext_free_cb free_cb,
    527     void *add_arg,
    528     custom_ext_parse_cb parse_cb, void *parse_arg)
    529 {
    530     return add_old_custom_ext(ctx, ENDPOINT_SERVER, ext_type,
    531         SSL_EXT_TLS1_2_AND_BELOW_ONLY
    532             | SSL_EXT_CLIENT_HELLO
    533             | SSL_EXT_TLS1_2_SERVER_HELLO
    534             | SSL_EXT_IGNORE_ON_RESUMPTION,
    535         add_cb, free_cb, add_arg, parse_cb, parse_arg);
    536 }
    537 
    538 int SSL_CTX_add_custom_ext(SSL_CTX *ctx, unsigned int ext_type,
    539     unsigned int context,
    540     SSL_custom_ext_add_cb_ex add_cb,
    541     SSL_custom_ext_free_cb_ex free_cb,
    542     void *add_arg,
    543     SSL_custom_ext_parse_cb_ex parse_cb, void *parse_arg)
    544 {
    545     return ossl_tls_add_custom_ext_intern(ctx, NULL, ENDPOINT_BOTH, ext_type,
    546         context, add_cb, free_cb, add_arg,
    547         parse_cb, parse_arg);
    548 }
    549 
    550 int SSL_extension_supported(unsigned int ext_type)
    551 {
    552     switch (ext_type) {
    553         /* Internally supported extensions. */
    554     case TLSEXT_TYPE_application_layer_protocol_negotiation:
    555     case TLSEXT_TYPE_ec_point_formats:
    556     case TLSEXT_TYPE_supported_groups:
    557     case TLSEXT_TYPE_key_share:
    558 #ifndef OPENSSL_NO_NEXTPROTONEG
    559     case TLSEXT_TYPE_next_proto_neg:
    560 #endif
    561     case TLSEXT_TYPE_padding:
    562     case TLSEXT_TYPE_renegotiate:
    563     case TLSEXT_TYPE_max_fragment_length:
    564     case TLSEXT_TYPE_server_name:
    565     case TLSEXT_TYPE_session_ticket:
    566     case TLSEXT_TYPE_signature_algorithms:
    567 #ifndef OPENSSL_NO_SRP
    568     case TLSEXT_TYPE_srp:
    569 #endif
    570 #ifndef OPENSSL_NO_OCSP
    571     case TLSEXT_TYPE_status_request:
    572 #endif
    573 #ifndef OPENSSL_NO_CT
    574     case TLSEXT_TYPE_signed_certificate_timestamp:
    575 #endif
    576 #ifndef OPENSSL_NO_SRTP
    577     case TLSEXT_TYPE_use_srtp:
    578 #endif
    579     case TLSEXT_TYPE_encrypt_then_mac:
    580     case TLSEXT_TYPE_supported_versions:
    581     case TLSEXT_TYPE_extended_master_secret:
    582     case TLSEXT_TYPE_psk_kex_modes:
    583     case TLSEXT_TYPE_cookie:
    584     case TLSEXT_TYPE_early_data:
    585     case TLSEXT_TYPE_certificate_authorities:
    586     case TLSEXT_TYPE_psk:
    587     case TLSEXT_TYPE_post_handshake_auth:
    588     case TLSEXT_TYPE_compress_certificate:
    589     case TLSEXT_TYPE_client_cert_type:
    590     case TLSEXT_TYPE_server_cert_type:
    591         return 1;
    592     default:
    593         return 0;
    594     }
    595 }
    596