hw_pk11.c revision 7c478bd95313f5f23a4c958a745db2134aa03244
/*
* Copyright 2004 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
/* This product includes software developed by the OpenSSL Project for
* use in the OpenSSL Toolkit (http://www.openssl.org/).
*
* This project also referenced hw_pkcs11-0.9.7b.patch written by
* Afchine Madjlessi.
*/
/* ====================================================================
* Copyright (c) 2000-2001 The OpenSSL Project. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* distribution.
*
* 3. All advertising materials mentioning features or use of this
* software must display the following acknowledgment:
* "This product includes software developed by the OpenSSL Project
* for use in the OpenSSL Toolkit. (http://www.OpenSSL.org/)"
*
* 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to
* endorse or promote products derived from this software without
* prior written permission. For written permission, please contact
* licensing@OpenSSL.org.
*
* 5. Products derived from this software may not be called "OpenSSL"
* nor may "OpenSSL" appear in their names without prior written
* permission of the OpenSSL Project.
*
* 6. Redistributions of any form whatsoever must retain the following
* acknowledgment:
* "This product includes software developed by the OpenSSL Project
* for use in the OpenSSL Toolkit (http://www.OpenSSL.org/)"
*
* THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY
* EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OpenSSL PROJECT OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
* ====================================================================
*
* This product includes cryptographic software written by Eric Young
* (eay@cryptsoft.com). This product includes software written by Tim
* Hudson (tjh@cryptsoft.com).
*
*/
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <cryptlib.h>
#ifndef OPENSSL_NO_HW
#ifndef OPENSSL_NO_HW_PK11
#include "security/cryptoki.h"
#include "hw_pk11_err.c"
/* The head of the free PK11 session list */
/* Create all secret key objects in a global session so that they are available
* to use for other sessions. These other sessions may be opened or closed
* without losing the secret key objects */
/* ENGINE level stuff */
static int pk11_library_init(ENGINE *e);
static int pk11_finish(ENGINE *e);
static int pk11_destroy(ENGINE *e);
/* RAND stuff */
static void pk11_rand_cleanup(void);
static int pk11_rand_status(void);
/* These functions are also used in other files */
/* Local helper functions */
static int pk11_free_all_sessions();
static const char *get_PK11_LIBNAME(void);
static void free_PK11_LIBNAME(void);
static long set_PK11_LIBNAME(const char *name);
/* Symmetric cipher and digest support functions */
static int cipher_nid_to_pk11(int nid);
static int pk11_usable_ciphers(const int **nids);
static int pk11_usable_digests(const int **nids);
static int md_nid_to_pk11(int nid);
unsigned long count);
static int pk11_choose_slot();
/* Index for the supported ciphers */
#define PK11_DES_CBC 0
#define PK11_DES3_CBC 1
#define PK11_AES_CBC 2
#define PK11_RC4 3
/* Index for the supported digests */
#define PK11_MD5 0
#define PK11_SHA1 1
#define PK11_KEY_LEN_MAX 24
static int cipher_nids[PK11_CIPHER_MAX];
static int digest_nids[PK11_DIGEST_MAX];
static int cipher_count = 0;
static int digest_count = 0;
typedef struct PK11_CIPHER_st
{
int id;
int nid;
int ivmax;
int key_len;
} PK11_CIPHER;
static PK11_CIPHER ciphers[] =
{
{CKM_DES_CBC, NULL, 0},},
{CKM_DES3_CBC, NULL, 0},},
{CKM_AES_CBC, NULL, 0},},
};
typedef struct PK11_DIGEST_st
{
int id;
int nid;
} PK11_DIGEST;
static PK11_DIGEST digests[] =
{
};
/* Structure to be used for the cipher_data/md_data in
* EVP_CIPHER_CTX/EVP_MD_CTX structures in order to use the same
* pk11 session in multiple cipher_update calls
*/
typedef struct PK11_CIPHER_STATE_st
{
/* libcrypto EVP stuff - this is how we get wired to EVP so the engine
* gets called when libcrypto requests a cipher NID.
* Note how the PK11_CIPHER_STATE is used here.
*/
/* DES CBC EVP */
static const EVP_CIPHER pk11_des_cbc =
{
8, 8, 8,
sizeof(PK11_CIPHER_STATE),
};
/* 3DES CBC EVP */
static const EVP_CIPHER pk11_3des_cbc =
{
8, 24, 8,
sizeof(PK11_CIPHER_STATE),
};
static const EVP_CIPHER pk11_aes_cbc =
{
16, 16, 16,
sizeof(PK11_CIPHER_STATE),
};
static const EVP_CIPHER pk11_rc4 =
{
1,16,0,
sizeof(PK11_CIPHER_STATE),
NULL,
NULL,
};
{
0,
sizeof(PK11_CIPHER_STATE),
};
{
0,
sizeof(PK11_CIPHER_STATE),
};
/* Initialization function. Sets up various pk11 library components.
*/
/* The definitions for control commands specific to this engine
*/
#define PK11_CMD_SO_PATH ENGINE_CMD_BASE
static const ENGINE_CMD_DEFN pk11_cmd_defns[] =
{
{
"SO_PATH",
"Specifies the path to the 'pkcs#11' shared library",
},
};
static RAND_METHOD pk11_random =
{
};
/* Constants used when creating the ENGINE
*/
static const char *engine_pk11_id = "pkcs11";
static const char *engine_pk11_name = "PKCS #11 engine support";
static const char PK11_GET_FUNCTION_LIST[] = "C_GetFunctionList";
/* These are the static string constants for the DSO file name and the function
* symbol names to bind to.
*/
static const char def_PK11_LIBNAME[] = "/usr/lib/64/libpkcs11.so.1";
#else
static const char def_PK11_LIBNAME[] = "/usr/lib/libpkcs11.so.1";
#endif
/* CRYPTO_LOCK_RSA is defined in OpenSSL for RSA method. Since this pk11
* engine replaces RSA method, we may reuse this lock here.
*/
static CK_SLOT_ID SLOTID = 0;
static int pk11_library_initialized = 0;
/*
* This internal function is used by ENGINE_pk11() and "dynamic" ENGINE support.
*/
{
if (!pk11_library_initialized)
if(!ENGINE_set_id(e, engine_pk11_id) ||
!ENGINE_set_name(e, engine_pk11_name) ||
return 0;
#ifndef OPENSSL_NO_RSA
if(pk11_have_rsa == CK_TRUE)
{
if(!ENGINE_set_RSA(e, PK11_RSA()) ||
return 0;
#ifdef DEBUG_SLOT_SELECTION
#endif /* DEBUG_SLOT_SELECTION */
}
#endif
#ifndef OPENSSL_NO_DSA
if(pk11_have_dsa == CK_TRUE)
{
if (!ENGINE_set_DSA(e, PK11_DSA()))
return 0;
#ifdef DEBUG_SLOT_SELECTION
#endif /* DEBUG_SLOT_SELECTION */
}
#endif
#ifndef OPENSSL_NO_DH
if(pk11_have_dh == CK_TRUE)
{
if (!ENGINE_set_DH(e, PK11_DH()))
return 0;
#ifdef DEBUG_SLOT_SELECTION
#endif /* DEBUG_SLOT_SELECTION */
}
#endif
if(pk11_have_random)
{
if(!ENGINE_set_RAND(e, &pk11_random))
return 0;
#ifdef DEBUG_SLOT_SELECTION
#endif /* DEBUG_SLOT_SELECTION */
}
if(!ENGINE_set_init_function(e, pk11_init) ||
!ENGINE_set_ctrl_function(e, pk11_ctrl) ||
return 0;
/* Apache calls OpenSSL function RSA_blinding_on() once during startup
* which in turn calls bn_mod_exp. Since we do not implement bn_mod_exp
* here, we wire it back to the OpenSSL software implementation.
* Since it is used only once, performance is not a concern. */
#ifndef OPENSSL_NO_RSA
rsa = RSA_PKCS1_SSLeay();
#endif
/* Ensure the pk11 error handling is set up */
return 1;
}
/* Dynamic engine support is disabled at a higher level for Solaris
*/
#ifdef ENGINE_DYNAMIC_SUPPORT
{
return 0;
if (!bind_pk11(e))
return 0;
return 1;
}
#else
static ENGINE *engine_pk11(void)
{
if (!ret)
return NULL;
{
return NULL;
}
return ret;
}
void ENGINE_load_pk11(void)
{
/* Do not use dynamic PKCS#11 library on Solaris due to
* security reasons. We will link it in statically
*/
/* Attempt to load PKCS#11 library
*/
if (!pk11_dso)
{
return;
}
e_pk11 = engine_pk11();
if (!e_pk11)
{
return;
}
/* At this point, the pk11 shared library is either dynamically
* loaded or statically linked in. So, initialize the pk11
* library before calling ENGINE_set_default since the latter
* needs cipher and digest algorithm information
*/
if (!pk11_library_init(e_pk11))
{
return;
}
}
#endif
/* These are the static string constants for the DSO file name and
* the function symbol names to bind to.
*/
static const char *PK11_LIBNAME = NULL;
static const char *get_PK11_LIBNAME(void)
{
if (PK11_LIBNAME)
return PK11_LIBNAME;
return def_PK11_LIBNAME;
}
static void free_PK11_LIBNAME(void)
{
if (PK11_LIBNAME)
OPENSSL_free((void*)PK11_LIBNAME);
PK11_LIBNAME = NULL;
}
static long set_PK11_LIBNAME(const char *name)
{
}
/* Initialization function for the pk11 engine */
{
return pk11_library_init(e);
}
/* Initialization function. Sets up various pk11 library components.
* It selects a slot based on predefined critiera. In the process, it also
* count how many ciphers and digests to support. Since the cipher and
* digest information is needed when setting default engine, this function
* needs to be called before calling ENGINE_set_default.
*/
static int pk11_library_init(ENGINE *e)
{
char tmp_buf[20];
return 1;
{
goto err;
}
/* get the C_GetFunctionList function from the loaded library
*/
if ( !p )
{
goto err;
}
/* get the full function list from the loaded library
*/
{
goto err;
}
{
goto err;
}
{
goto err;
}
if (pk11_choose_slot() == 0)
goto err;
if (global_session == CK_INVALID_HANDLE)
{
/* Open the global_session for the new process */
{
goto err;
}
}
/* Disable digest if C_GetOperationState is not supported since
* this function is required by OpenSSL digest copy function */
digest_count = 0;
return 1;
err:
return 0;
}
/* Destructor (complements the "ENGINE_pk11()" constructor)
*/
static int pk11_destroy(ENGINE *e)
{
return 1;
}
/* Termination function to clean up the session, the token, and
* the pk11 library.
*/
static int pk11_finish(ENGINE *e)
{
{
goto err;
}
if (pk11_free_all_sessions() == 0)
goto err;
/* Since we are part of a library (libcrypto.so), calling this
* function may have side-effects.
pFuncList->C_Finalize(NULL);
*/
{
goto err;
}
return 1;
err:
return 0;
}
/* Standard engine interface function to set the dynamic library path */
{
switch(cmd)
{
case PK11_CMD_SO_PATH:
if (p == NULL)
{
return 0;
}
if (initialized)
{
return 0;
}
return set_PK11_LIBNAME((const char*)p);
default:
break;
}
return 0;
}
/* Required function by the engine random interface. It does nothing here
*/
static void pk11_rand_cleanup(void)
{
return;
}
{
return;
/* Ignore any errors (e.g. CKR_RANDOM_SEED_NOT_SUPPORTED) since
* the calling functions do not care anyway
*/
return;
}
{
}
{
return 0;
{
char tmp_buf[20];
return 0;
}
return 1;
}
/* Required function by the engine random interface. It does nothing here
*/
static int pk11_rand_status(void)
{
return 1;
}
{
char tmp_buf[20];
{
{
goto err;
}
}
else
{
}
{
/* We are a new process and thus need to free any inherated
* PK11_SESSION objects.
*/
{
}
/* Initialize the process */
{
goto err;
}
/* Choose slot here since the slot table is different on
* this process.
*/
if (pk11_choose_slot() == 0)
goto err;
/* Open the global_session for the new process */
{
goto err;
}
/* It is an inherited session and needs re-initialization.
*/
if (pk11_setup_session(sp) == 0)
{
}
}
{
/* It is a new session and needs initialization.
*/
if (pk11_setup_session(sp) == 0)
{
}
}
err:
if (sp)
return sp;
}
{
return;
free_session = sp;
}
/* Destroy all objects. This function is called when the engine is finished
*/
static int pk11_free_all_sessions()
{
int ret = 0;
{
{
{
char tmp_buf[20];
}
}
{
{
char tmp_buf[20];
}
}
}
ret = 1;
err:
return ret;
}
{
if (rv == CKR_CRYPTOKI_NOT_INITIALIZED)
{
/*
* We are probably a child process so force the
* reinitialize of the session
*/
(void) pk11_library_init(NULL);
}
{
char tmp_buf[20];
return 0;
}
{
char tmp_buf[20];
return 0;
}
return 1;
}
{
int ret = 0;
if (session)
else
{
{
sp->rsa_pub_key) == 0)
goto err;
}
{
sp->rsa_priv_key) == 0)
goto err;
}
}
ret = 1;
err:
return ret;
}
{
int ret = 0;
if (session)
else
{
{
sp->dsa_pub_key) == 0)
goto err;
}
{
sp->dsa_priv_key) == 0)
goto err;
}
}
ret = 1;
err:
return ret;
}
{
int ret = 0;
if (session)
else
{
{
goto err;
}
}
ret = 1;
err:
return ret;
}
{
{
char tmp_buf[20];
tmp_buf);
return 0;
}
return 1;
}
/* Symmetric ciphers and digests support functions
*/
static int
cipher_nid_to_pk11(int nid)
{
int i;
for (i = 0; i < PK11_CIPHER_MAX; i++)
return (-1);
}
static int
pk11_usable_ciphers(const int **nids)
{
if (cipher_count > 0)
*nids = cipher_nids;
else
return (cipher_count);
}
static int
pk11_usable_digests(const int **nids)
{
if (digest_count > 0)
*nids = digest_nids;
else
return (digest_count);
}
static int
{
int index;
char tmp_buf[20];
return 0;
return 0;
return 0;
/* The key object is destroyed here if it is not the current key
*/
/* If the key is the same and the encryption is also the same,
* then just reuse it
*/
{
return 1;
}
/* Check if the key has been invalidated. If so, a new key object
* needs to be created.
*/
{
}
{
/* The previous encryption/decryption
* is different. Need to terminate the previous
* active encryption/decryption here
*/
if (!pk11_cipher_final(sp))
{
return 0;
}
}
{
return 0;
}
{
}
/* If we get here, the encryption needs to be reinitialized */
{
sp->cipher_key);
{
return 0;
}
}
else
{
sp->cipher_key);
{
return 0;
}
}
return 1;
}
/* When reusing the same key in an encryption/decryption session for a
* decryption/encryption session, we need to close the active session
* and recreate a new one. Note that the key is in the global session so
* that it needs not be recreated.
*
* It is more appropriate to use C_En/DecryptFinish here. At the time of this
* development, these two functions in the PKCS#11 libraries used return
* unexpected errors when passing in 0 length output. It may be a good
* idea to try them again if performance is a problem here and fix
* C_En/DecryptFinial if there are bugs there causing the problem.
*/
static int
{
char tmp_buf[20];
{
return 0;
}
{
return 0;
}
return 1;
}
/* An engine interface function. The calling function allocates sufficient
* memory for the output buffer "out" to hold the results */
static int
{
char tmp_buf[20];
return 0;
if (!inl)
return 1;
/* RC4 is the only stream cipher we support */
return 0;
{
{
return 0;
}
}
else
{
{
return 0;
}
}
/* for DES_CBC, DES3_CBC, AES_CBC, and RC4, the output size is always
* the same size of input
* The application has guaranteed to call the block ciphers with
* correctly aligned buffers.
*/
return 0;
return 1;
}
/* Return the session to the pool. The C_EncryptFinal and C_DecryptFinal are
* not used. Once a secret key is initialized, it is used until destroyed.
*/
static int
{
{
}
return 1;
}
/* Registered by the ENGINE when used to find out how to deal with
* a particular NID in the ENGINE. This says what we'll do at the
* top level - note, that list is restricted by what we answer with
*/
static int
{
if (!cipher)
return (pk11_usable_ciphers(nids));
switch (nid)
{
case NID_des_ede3_cbc:
*cipher = &pk11_3des_cbc;
break;
case NID_des_cbc:
*cipher = &pk11_des_cbc;
break;
case NID_aes_128_cbc:
*cipher = &pk11_aes_cbc;
break;
case NID_rc4:
break;
default:
break;
}
}
static int
{
if (!digest)
return (pk11_usable_digests(nids));
switch (nid)
{
case NID_md5:
break;
case NID_sha1:
break;
default:
break;
}
}
/* Create a secret key object in a PKCS#11 session
*/
{
char tmp_buf[20];
{
{CKA_TOKEN, &false, sizeof(false)},
{CKA_ENCRYPT, &true, sizeof(true)},
{CKA_DECRYPT, &true, sizeof(true)},
};
/* Create secret key object in global_session. All other sessions
* can use the key handles. Here is why:
* OpenSSL will call EncryptInit and EncryptUpdate using a secret key.
* It may then call DecryptInit and DecryptUpdate using the same key.
* To use the same key object, we need to call EncryptFinal with
* a 0 length message. Currently, this does not work for 3DES
* mechanism. To get around this problem, we close the session and
* then create a new session to use the same key object. When a session
* is closed, all the object handles will be invalid. Thus, create key
* objects in a global session, an individual session may be closed to
* terminate the active operation.
*/
{
goto err;
}
{
goto err;
}
{
goto err;
}
if (found == 0)
{
{
goto err;
}
}
/* Save the key information used in this session.
* The max can be saved is PK11_KEY_LEN_MAX.
*/
err:
return h_key;
}
static int
md_nid_to_pk11(int nid)
{
int i;
for (i = 0; i < PK11_DIGEST_MAX; i++)
return (-1);
}
static int
{
int index;
return 0;
return 0;
{
char tmp_buf[20];
return 0;
}
return 1;
}
static int
{
/* 0 length message will cause a failure in C_DigestFinal */
if (count == 0)
return 1;
return 0;
count);
{
char tmp_buf[20];
return 0;
}
return 1;
}
static int
{
unsigned long len;
return 0;
{
char tmp_buf[20];
return 0;
}
return 0;
/* Final is called and digest is returned, so return the session
* to the pool
*/
return 1;
}
static int
{
int ret = 0;
char tmp_buf[20];
/* The copy-from state */
goto err;
/* Initialize the copy-to state */
if (!pk11_digest_init(to))
goto err;
/* Get the size of the operation state of the copy-from session */
&ul_state_len);
{
goto err;
}
if (ul_state_len == 0)
{
goto err;
}
{
goto err;
}
/* Get the operation state of the copy-from session */
&ul_state_len);
{
goto err;
}
/* Set the operation state of the copy-to session */
ul_state_len, 0, 0);
{
goto err;
}
ret = 1;
err:
return ret;
}
/* Return any pending session state to the pool */
static int
{
{
}
return 1;
}
/* Check if the new key is the same as the key object in the session.
* If the key is the same, no need to create a new key object. Otherwise,
* the old key object needs to be destroyed and a new one will be created
*/
{
}
/* Destroy one or more secret key objects.
*/
{
int ret = 0;
if (session)
else
{
{
/* The secret key object is created in the
* global_session. See pk11_get_cipher_key
*/
sp->cipher_key) == 0)
goto err;
}
}
ret = 1;
err:
return ret;
}
/*
* Required mechanisms
*
* CKM_RSA_X_509
* CKM_RSA_PKCS
* CKM_DSA
*
* As long as these required mechanisms are met, it will return success.
* Otherwise, it will return failure and the engine initialization will fail.
* The application will then decide whether to use another engine or
* no engine.
*
* Symmetric ciphers optionally supported
*
* CKM_DES3_CBC
* CKM_DES_CBC
* CKM_AES_CBC
* CKM_RC4
*
* Digests optionally supported
*
* CKM_MD5
* CKM_SHA_1
*/
static int
{
CK_ULONG ulSlotCount = 0;
int i;
int slot_n_cipher = 0;
int slot_n_digest = 0;
CK_SLOT_ID current_slot = 0;
int current_slot_n_cipher = 0;
int current_slot_n_digest = 0;
char tmp_buf[20];
int retval = 0;
/* Get slot list for memory alloction */
{
return retval;
}
if (ulSlotCount == 0)
{
return retval;
}
{
return retval;
}
/* Get the slot list for processing */
{
return retval;
}
for (i = 0; i < ulSlotCount; i++)
{
current_slot = pSlotList[i];
#ifdef DEBUG_SLOT_SELECTION
#endif
/* Check if slot has random support. */
continue;
/*
* Check if this slot is capable of signing and
* verifying with CKM_RSA_PKCS.
*/
&mech_info);
{
/*
* Check if this slot is capable of encryption,
* decryption, sign, and verify with CKM_RSA_X_509.
*/
}
/*
* Check if this slot is capable of signing and
* verifying with CKM_DSA.
*/
&mech_info);
/*
* Check if this slot is capable of DH key generataion and
* derivation.
*/
{
}
if (!found_candidate_slot &&
{
#ifdef DEBUG_SLOT_SELECTION
"OPENSSL_PKCS#11_ENGINE: potential slot: %d\n",
#endif
#ifdef DEBUG_SLOT_SELECTION
"OPENSSL_PKCS#11_ENGINE: best so far slot: %d\n",
#endif
}
/* Count symmetric cipher support. */
continue;
continue;
continue;
PK11_RC4))
continue;
/* Count digest support */
PK11_MD5))
continue;
continue;
/*
* the previous best one we change the current best to this one.
* otherwise leave it where it is.
*/
if (((current_slot_n_cipher > slot_n_cipher) &&
(current_slot_n_digest > slot_n_digest)) &&
((slot_has_rsa == pk11_have_rsa) &&
(slot_has_dsa == pk11_have_dsa) &&
(slot_has_dh == pk11_have_dh)))
{
sizeof(local_cipher_nids));
sizeof(local_digest_nids));
}
}
if (found_candidate_slot)
{
retval = 1;
}
else
{
cipher_count = 0;
digest_count = 0;
}
#ifdef DEBUG_SLOT_SELECTION
"OPENSSL_PKCS#11_ENGINE: choose slot: %d\n", SLOTID);
"OPENSSL_PKCS#11_ENGINE: pk11_have_rsa %d\n", pk11_have_rsa);
"OPENSSL_PKCS#11_ENGINE: pk11_have_dsa %d\n", pk11_have_dsa);
"OPENSSL_PKCS#11_ENGINE: pk11_have_dh %d\n", pk11_have_dh);
"OPENSSL_PKCS#11_ENGINE: pk11_have_random %d\n", pk11_have_random);
#endif /* DEBUG_SLOT_SELECTION */
if (pSlotList)
return retval;
}
{
return 0;
{
}
return 1;
}
{
return 0;
{
}
return 1;
}
#endif
#endif