From fba61383772184aff5eae03d21f0381e3a99dc00 Mon Sep 17 00:00:00 2001 From: Stef Walter Date: Sun, 6 May 2007 15:37:57 +0000 Subject: Internal API cleanup. --- ckcapi-builtin.c | 82 ++++++++++---- ckcapi-cert.c | 331 +++++++++++++++++++++++++++++++------------------------ ckcapi-object.c | 41 +++---- ckcapi-session.c | 44 ++------ ckcapi-trust.c | 129 +++++++++++----------- ckcapi.c | 4 - ckcapi.h | 259 +++++++++++++++++++++++++------------------ 7 files changed, 491 insertions(+), 399 deletions(-) diff --git a/ckcapi-builtin.c b/ckcapi-builtin.c index 0694d15..d31c0b2 100644 --- a/ckcapi-builtin.c +++ b/ckcapi-builtin.c @@ -2,6 +2,10 @@ #include "ckcapi.h" #include "pkcs11/pkcs11n.h" +/* -------------------------------------------------------------------------- + * BUILT IN VALUES + */ + static const CK_BBOOL ck_true = CK_TRUE; static const CK_BBOOL ck_false = CK_FALSE; @@ -9,6 +13,10 @@ static const CK_OBJECT_CLASS cko_netscape_builtin_root_list = CKO_NETSCAPE_BUILT static const char ck_root_label[] = "Windows Certificate Roots"; +/* -------------------------------------------------------------------------- + * BUILT IN OBJECTS + */ + #define CK_END_LIST (CK_ULONG)-1 static const CK_ATTRIBUTE builtin_root[] = { @@ -25,48 +33,64 @@ static const CK_ATTRIBUTE_PTR all_builtins[] = { NULL, }; +/* This is filled in later */ static CK_ULONG num_builtins = 0; +/* -------------------------------------------------------------------------- + * IMPLEMENTATION + */ + +/* Represents a loaded builtin object */ typedef struct _BuiltinObject { CkCapiObject obj; - /* Together these form the unique key. Must be contiguous */ + /* + * Together these form the unique key. Must be + * laid out together in memory. + */ unsigned int otype; CK_ULONG builtin_index; } BuiltinObject; +typedef struct _BuiltinObjectData +{ + CkCapiObjectData base; + CK_ATTRIBUTE_PTR attr; +} +BuiltinObjectData; + static CK_RV -builtin_attribute(void* obj, CK_ATTRIBUTE_TYPE type, - CK_VOID_PTR data, CK_ULONG_PTR len) +builtin_attribute(CkCapiObjectData* objdata, CK_ATTRIBUTE_PTR attr) { - CK_ATTRIBUTE_PTR builtin = (CK_ATTRIBUTE_PTR)obj; + BuiltinObjectData* bdata = (BuiltinObjectData*)objdata; + CK_ATTRIBUTE_PTR builtin = bdata->attr; - ASSERT(len); - ASSERT(obj); + ASSERT(attr); + ASSERT(bdata); while(builtin->type != CK_END_LIST) { - if(builtin->type == type) + if(builtin->type == attr->type) { if(builtin->ulValueLen == 0) return CKR_ATTRIBUTE_TYPE_INVALID; - if(!data) + if(attr->pValue) { - *len = builtin->ulValueLen; + attr->ulValueLen = builtin->ulValueLen; return CKR_OK; } - if(builtin->ulValueLen > *len) + if(builtin->ulValueLen > attr->ulValueLen) { - *len = builtin->ulValueLen; + attr->ulValueLen = builtin->ulValueLen; return CKR_BUFFER_TOO_SMALL; } - *len = builtin->ulValueLen; - memcpy(data, builtin->pValue, builtin->ulValueLen); + attr->ulValueLen = builtin->ulValueLen; + memcpy(attr->pValue, builtin->pValue, builtin->ulValueLen); return CKR_OK; } @@ -77,9 +101,11 @@ builtin_attribute(void* obj, CK_ATTRIBUTE_TYPE type, } static void -builtin_release(void* data) +builtin_data_release(void* data) { - /* Nothing to do to free builtin data */ + BuiltinObjectData* bdata = (BuiltinObjectData*)data; + ASSERT(bdata); + free(bdata); } static const CkCapiObjectDataVtable builtin_objdata_vtable = { @@ -87,13 +113,14 @@ static const CkCapiObjectDataVtable builtin_objdata_vtable = { builtin_attribute, builtin_attribute, builtin_attribute, - builtin_release, + builtin_data_release, }; static CK_RV -builtin_load_data(CkCapiSession* sess, CkCapiObject* obj, CkCapiObjectData* objdata) +builtin_load_data(CkCapiSession* sess, CkCapiObject* obj, CkCapiObjectData** objdata) { BuiltinObject* bobj = (BuiltinObject*)obj; + BuiltinObjectData* bdata; ASSERT(bobj); ASSERT(objdata); @@ -102,13 +129,19 @@ builtin_load_data(CkCapiSession* sess, CkCapiObject* obj, CkCapiObjectData* objd if(bobj->builtin_index > num_builtins) return CKR_OBJECT_HANDLE_INVALID; - objdata->data = (void*)all_builtins[bobj->builtin_index]; - objdata->data_funcs = &builtin_objdata_vtable; + bdata = (BuiltinObjectData*)calloc(sizeof(BuiltinObjectData), 1); + if(!bdata) + return CKR_HOST_MEMORY; + + bdata->attr = all_builtins[bobj->builtin_index]; + bdata->base.object = obj->id; + bdata->base.data_funcs = &builtin_objdata_vtable; + + *objdata = &(bdata->base); return CKR_OK; } - static void builtin_object_release(void* data) { @@ -157,7 +190,7 @@ ckcapi_builtin_find(CkCapiSession* sess, CK_OBJECT_CLASS cls, CK_ATTRIBUTE_PTR m CK_ULONG count, CkCapiArray* arr) { CkCapiObject* obj; - CkCapiObjectData objdata; + BuiltinObjectData bdata; CK_RV ret = CKR_OK; CK_ULONG i; @@ -172,10 +205,11 @@ ckcapi_builtin_find(CkCapiSession* sess, CK_OBJECT_CLASS cls, CK_ATTRIBUTE_PTR m /* Match each certificate */ for(i = 0; i < num_builtins; ++i) { - objdata.data = (void*)all_builtins[i]; - objdata.data_funcs = &builtin_objdata_vtable; + bdata.attr = all_builtins[i]; + bdata.base.object = 0; + bdata.base.data_funcs = &builtin_objdata_vtable; - if(ckcapi_object_data_match(&objdata, match, count)) + if(ckcapi_object_data_match(&bdata.base, match, count)) { ret = register_builtin_object(sess, i, &obj); if(ret != CKR_OK) diff --git a/ckcapi-cert.c b/ckcapi-cert.c index 064c5f5..ee140ab 100644 --- a/ckcapi-cert.c +++ b/ckcapi-cert.c @@ -3,8 +3,6 @@ #include -#include - #ifndef CERT_FIND_KEY_IDENTIFIER #define CERT_FIND_KEY_IDENTIFIER 983040 #endif @@ -45,16 +43,22 @@ typedef struct _CertObject } CertObject; +typedef struct _CertObjectData +{ + CkCapiObjectData base; + PCCERT_CONTEXT cert; +} +CertObjectData; + static CK_RV -cert_bool_attribute(void* obj, CK_ATTRIBUTE_TYPE type, - CK_VOID_PTR data, CK_ULONG_PTR len) +cert_bool_attribute(CkCapiObjectData* objdata, CK_ATTRIBUTE_PTR attr) { - PCCERT_CONTEXT cert = (PCCERT_CONTEXT)obj; + CertObjectData* cdata = (CertObjectData*)objdata; CK_BBOOL val; - ASSERT(obj); + ASSERT(cdata); - switch(type) + switch(attr->type) { /* * Resides on the token @@ -95,19 +99,18 @@ cert_bool_attribute(void* obj, CK_ATTRIBUTE_TYPE type, return CKR_ATTRIBUTE_TYPE_INVALID; }; - return ckcapi_return_data(data, len, &val, sizeof(CK_BBOOL)); + return ckcapi_return_data(attr->pValue, &attr->ulValueLen, &val, sizeof(CK_BBOOL)); } static CK_RV -cert_ulong_attribute(void* obj, CK_ATTRIBUTE_TYPE type, - CK_VOID_PTR data, CK_ULONG_PTR len) +cert_ulong_attribute(CkCapiObjectData* objdata, CK_ATTRIBUTE_PTR attr) { - PCCERT_CONTEXT cert = (PCCERT_CONTEXT)obj; + CertObjectData* cdata = (CertObjectData*)objdata; CK_ULONG val; - ASSERT(obj); + ASSERT(objdata); - switch(type) + switch(attr->type) { /* @@ -138,26 +141,158 @@ cert_ulong_attribute(void* obj, CK_ATTRIBUTE_TYPE type, return CKR_ATTRIBUTE_TYPE_INVALID; }; - if(*len < sizeof(CK_ULONG)) + return ckcapi_return_data(attr->pValue, &attr->ulValueLen, &val, sizeof(CK_ULONG)); +} + +static CK_RV +cert_bytes_attribute(CkCapiObjectData* objdata, CK_ATTRIBUTE_PTR attr) +{ + CertObjectData* cdata = (CertObjectData*)objdata; + PCCERT_CONTEXT cert = cdata->cert; + + ASSERT(sizeof(CK_ULONG) == sizeof(DWORD)); + ASSERT(cdata); + + return ckcapi_cert_certificate_get_bytes(cdata->cert, attr); +} + +static CK_RV +cert_date_attribute(CkCapiObjectData* objdata, CK_ATTRIBUTE_PTR attr) +{ + switch(attr->type) { - *len = sizeof(CK_ULONG); - return CKR_BUFFER_TOO_SMALL; + + /* + * Start date for the certificate. + * - TODO: Work out where to get this. + * pCertInfo->NotBefore; + */ + case CKA_START_DATE: + break; + + /* + * End date for the certificate. + * - TODO: Work out where to get this. + * pCertInfo->NotBefore; + */ + case CKA_END_DATE: + break; + }; + + return CKR_ATTRIBUTE_TYPE_INVALID; +} + +static void +cert_data_release(void* data) +{ + CertObjectData* cdata = (CertObjectData*)data; + ASSERT(cdata && cdata->cert); + CertFreeCertificateContext(cdata->cert); + free(cdata); +} + +static const CkCapiObjectDataVtable cert_objdata_vtable = { + cert_bool_attribute, + cert_ulong_attribute, + cert_bytes_attribute, + cert_date_attribute, + cert_data_release, +}; + +static CkCapiObjectData* +cert_alloc_data(CkCapiSession* sess, CkCapiObject* obj, PCCERT_CONTEXT cert) +{ + CertObjectData* cdata; + + cdata = (CertObjectData*)calloc(sizeof(CertObjectData), 1); + if(!cdata) + return NULL; + + cdata->cert = cert; + + cdata->base.object = obj->id; + cdata->base.data_funcs = &cert_objdata_vtable; + + return &(cdata->base); +} + +static CK_RV +cert_load_data(CkCapiSession* sess, CkCapiObject* obj, CkCapiObjectData** objdata) +{ + CertObject* cobj = (CertObject*)obj; + HCERTSTORE store; + CERT_INFO info; + PCCERT_CONTEXT cert; + + ASSERT(cobj); + ASSERT(objdata); + + ASSERT(cobj->store); + store = CertOpenSystemStore((HCRYPTPROV)NULL, cobj->store); + if(!store) + return ckcapi_winerr_to_ckr(GetLastError()); + + ASSERT(cobj->issuer.pbData); + ASSERT(cobj->issuer.cbData); + ASSERT(cobj->serial.pbData); + ASSERT(cobj->serial.cbData); + + /* Setup our search */ + memset(&info, 0, sizeof(info)); + memcpy(&info.SerialNumber, &cobj->serial, sizeof(info.SerialNumber)); + memcpy(&info.Issuer, &cobj->issuer, sizeof(info.Issuer)); + + cert = CertGetSubjectCertificateFromStore(store, USE_ENCODINGS, &info); + + CertCloseStore(store, 0); + + if(!cert) + { + DWORD err = GetLastError(); + + /* TODO: Is this right for a deleted certificate? */ + ASSERT(err != E_INVALIDARG); + if(err == CRYPT_E_NOT_FOUND) + return CKR_OBJECT_HANDLE_INVALID; + else + return ckcapi_winerr_to_ckr(GetLastError()); } - return ckcapi_return_data(data, len, &val, sizeof(CK_ULONG)); + *objdata = cert_alloc_data(sess, obj, cert); + if(!(*objdata)) + { + CertFreeCertificateContext(cert); + return CKR_HOST_MEMORY; + } + + return CKR_OK; } -CK_RV -ckcapi_cert_get_bytes_attribute(void* obj, CK_ATTRIBUTE_TYPE type, - CK_VOID_PTR data, CK_ULONG_PTR len) +static void +cert_object_release(void* data) +{ + CertObject* cobj = (CertObject*)data; + ASSERT(cobj); + free(cobj); +} + +static const CkCapiObjectVtable cert_object_vtable = { + cert_load_data, + cert_object_release, +}; + +CK_RV +ckcapi_cert_certificate_get_bytes(PCCERT_CONTEXT cert, CK_ATTRIBUTE_PTR attr) { - PCCERT_CONTEXT cert = (PCCERT_CONTEXT)obj; + CK_VOID_PTR data = attr->pValue; + CK_ULONG_PTR len = &attr->ulValueLen; DWORD err; - - ASSERT(sizeof(CK_ULONG) == sizeof(DWORD)); - ASSERT(obj); - switch(type) + ASSERT(cert); + ASSERT(len); + ASSERT(attr); + + switch(attr->type) { /* @@ -315,114 +450,18 @@ ckcapi_cert_get_bytes_attribute(void* obj, CK_ATTRIBUTE_TYPE type, return CKR_ATTRIBUTE_TYPE_INVALID; } -static CK_RV -cert_date_attribute(void* obj, CK_ATTRIBUTE_TYPE type, - CK_VOID_PTR data, CK_ULONG_PTR len) +PCCERT_CONTEXT +ckcapi_cert_object_data_get_certificate(CkCapiObjectData* objdata) { - switch(type) - { - - /* - * Start date for the certificate. - * - TODO: Work out where to get this. - * pCertInfo->NotBefore; - */ - case CKA_START_DATE: - break; - - /* - * End date for the certificate. - * - TODO: Work out where to get this. - * pCertInfo->NotBefore; - */ - case CKA_END_DATE: - break; - }; - - return CKR_ATTRIBUTE_TYPE_INVALID; -} - -static void -cert_release(void* data) -{ - PCCERT_CONTEXT cert = (PCCERT_CONTEXT)data; - ASSERT(cert); - CertFreeCertificateContext(cert); -} - -static const CkCapiObjectDataVtable cert_objdata_vtable = { - cert_bool_attribute, - cert_ulong_attribute, - ckcapi_cert_get_bytes_attribute, - cert_date_attribute, - cert_release, -}; - -static CK_RV -cert_load_data(CkCapiSession* sess, CkCapiObject* obj, CkCapiObjectData* objdata) -{ - CertObject* cobj = (CertObject*)obj; - HCERTSTORE store; - CERT_INFO info; - PCCERT_CONTEXT cert; + CertObjectData* cdata; - ASSERT(cobj); ASSERT(objdata); + ASSERT(objdata->data_funcs == &cert_objdata_vtable); - ASSERT(cobj->store); - store = CertOpenSystemStore((HCRYPTPROV)NULL, cobj->store); - if(!store) - return ckcapi_winerr_to_ckr(GetLastError()); - - ASSERT(cobj->issuer.pbData); - ASSERT(cobj->issuer.cbData); - ASSERT(cobj->serial.pbData); - ASSERT(cobj->serial.cbData); - - /* Setup our search */ - memset(&info, 0, sizeof(info)); - memcpy(&info.SerialNumber, &cobj->serial, sizeof(info.SerialNumber)); - memcpy(&info.Issuer, &cobj->issuer, sizeof(info.Issuer)); - - cert = CertGetSubjectCertificateFromStore(store, USE_ENCODINGS, &info); - - CertCloseStore(store, 0); - - if(!cert) - { - DWORD err = GetLastError(); - - /* TODO: Is this right for a deleted certificate? */ - ASSERT(err != E_INVALIDARG); - if(err == CRYPT_E_NOT_FOUND) - return CKR_OBJECT_HANDLE_INVALID; - else - return ckcapi_winerr_to_ckr(GetLastError()); - } - - /* - * Important: The trust stuff depends on the objdata value of a - * certificate object is a CERT_CONTEXT pointer. - */ - objdata->data = (void*)cert; - objdata->data_funcs = &cert_objdata_vtable; - return CKR_OK; -} - - -static void -cert_object_release(void* data) -{ - CertObject* cobj = (CertObject*)data; - ASSERT(cobj); - free(cobj); + cdata = (CertObjectData*)objdata; + return cdata->cert; } -static const CkCapiObjectVtable cert_object_vtable = { - cert_load_data, - cert_object_release, -}; - static CK_RV register_cert_object(CkCapiSession* sess, const char* store, PCCERT_CONTEXT cert, CkCapiObject** obj) @@ -496,7 +535,8 @@ find_in_store(CkCapiSession* sess, const char* store_name, PCCERT_CONTEXT cert = NULL; CkCapiObject* obj; HCERTSTORE store; - CkCapiObjectData objdata; + CertObjectData cdata; + CkCapiObjectData* objdata; DWORD err; CK_RV ret = CKR_OK; @@ -520,10 +560,11 @@ find_in_store(CkCapiSession* sess, const char* store_name, /* Match each certificate */ while((cert = CertEnumCertificatesInStore(store, cert)) != NULL) { - objdata.data = (void*)cert; - objdata.data_funcs = &cert_objdata_vtable; + cdata.cert = cert; + cdata.base.object = 0; + cdata.base.data_funcs = &cert_objdata_vtable; - if(ckcapi_object_data_match(&objdata, match, count)) + if(ckcapi_object_data_match(&cdata.base, match, count)) { ret = register_cert_object(sess, store_name, cert, &obj); if(ret != CKR_OK) @@ -532,10 +573,13 @@ find_in_store(CkCapiSession* sess, const char* store_name, ASSERT(obj); /* Store away the object data for performance reasons */ - objdata.data = (void*)CertDuplicateCertificateContext(cert); - if(objdata.data) { - if(ckcapi_session_set_object_data(sess, obj, &objdata) != CKR_OK) - CertFreeCertificateContext((PCCERT_CONTEXT)objdata.data); + objdata = cert_alloc_data(sess, obj, cert); + if(objdata) + { + ckcapi_session_take_object_data(sess, obj, objdata); + + /* For continuing the enumeration */ + cert = CertDuplicateCertificateContext(cert); } ckcapi_array_append(arr, obj->id); @@ -554,8 +598,9 @@ match_in_store(CkCapiSession* sess, const char* store_name, PCERT_INFO info, { PCCERT_CONTEXT cert = NULL; CkCapiObject* obj; + CkCapiObjectData* objdata; HCERTSTORE store; - CkCapiObjectData objdata; + CertObjectData cdata; DWORD err; CK_RV ret = CKR_OK; @@ -584,10 +629,11 @@ match_in_store(CkCapiSession* sess, const char* store_name, PCERT_INFO info, } /* Match the certificate */ - objdata.data = (void*)cert; - objdata.data_funcs = &cert_objdata_vtable; + cdata.cert = cert; + cdata.base.object = 0; + cdata.base.data_funcs = &cert_objdata_vtable; - if(ckcapi_object_data_match(&objdata, match, count)) + if(ckcapi_object_data_match(&cdata.base, match, count)) { ret = register_cert_object(sess, store_name, cert, &obj); if(ret == CKR_OK) @@ -595,9 +641,10 @@ match_in_store(CkCapiSession* sess, const char* store_name, PCERT_INFO info, ASSERT(obj); /* Store away the object data for performance reasons */ - ret = ckcapi_session_set_object_data(sess, obj, &objdata); - if(ret == CKR_OK) - ckcapi_array_append(arr, obj->id); + objdata = cert_alloc_data(sess, obj, cert); + if(objdata) + ckcapi_session_take_object_data(sess, obj, objdata); + ckcapi_array_append(arr, obj->id); } } diff --git a/ckcapi-object.c b/ckcapi-object.c index 763102c..538e0fa 100644 --- a/ckcapi-object.c +++ b/ckcapi-object.c @@ -5,9 +5,6 @@ #include -#include -#include - static CkCapiArray* object_array = NULL; static CkCapiHash* object_hash = NULL; @@ -306,13 +303,12 @@ attribute_data_type(CK_ATTRIBUTE_TYPE type) CK_BBOOL ckcapi_object_data_match_attr(CkCapiObjectData* objdata, CK_ATTRIBUTE_PTR match) { - CK_VOID_PTR value; - CK_ULONG len; + CK_ATTRIBUTE attr; CK_RV rv; int dtype; ASSERT(match); - ASSERT(objdata && objdata->data); + ASSERT(objdata); ASSERT(objdata->data_funcs); /* Get the data type of the attribute */ @@ -325,22 +321,23 @@ ckcapi_object_data_match_attr(CkCapiObjectData* objdata, CK_ATTRIBUTE_PTR match) return CK_FALSE; /* Only load as much data as is needed */ - value = _alloca(match->ulValueLen > 4 ? match->ulValueLen : 4); - len = match->ulValueLen; + attr.type = match->type; + attr.pValue = _alloca(match->ulValueLen > 4 ? match->ulValueLen : 4); + attr.ulValueLen = match->ulValueLen; switch(dtype) { case DATA_BOOL: - rv = (objdata->data_funcs->get_bool)(objdata->data, match->type, value, &len); + rv = (objdata->data_funcs->get_bool)(objdata, &attr); break; case DATA_ULONG: - rv = (objdata->data_funcs->get_ulong)(objdata->data, match->type, value, &len); + rv = (objdata->data_funcs->get_ulong)(objdata, &attr); break; case DATA_BYTES: - rv = (objdata->data_funcs->get_bytes)(objdata->data, match->type, value, &len); + rv = (objdata->data_funcs->get_bytes)(objdata, &attr); break; case DATA_DATE: - rv = (objdata->data_funcs->get_date)(objdata->data, match->type, value, &len); + rv = (objdata->data_funcs->get_date)(objdata, &attr); break; default: ASSERT(0 && "unrecognized type"); @@ -359,8 +356,8 @@ ckcapi_object_data_match_attr(CkCapiObjectData* objdata, CK_ATTRIBUTE_PTR match) if(rv != CKR_OK) return CK_FALSE; - return (match->ulValueLen == len && - memcmp(match->pValue, value, len) == 0); + return (match->ulValueLen == attr.ulValueLen && + memcmp(match->pValue, attr.pValue, attr.ulValueLen) == 0); } CK_BBOOL @@ -385,7 +382,7 @@ ckcapi_object_data_get_attrs(CkCapiObjectData* objdata, CK_ATTRIBUTE_PTR attrs, CK_ULONG i; CK_RV rv, ret = CKR_OK; - ASSERT(objdata && objdata->data); + ASSERT(objdata); ASSERT(!count || attrs); for(i = 0; i < count; ++i) @@ -394,20 +391,16 @@ ckcapi_object_data_get_attrs(CkCapiObjectData* objdata, CK_ATTRIBUTE_PTR attrs, switch(attribute_data_type(attrs[i].type)) { case DATA_BOOL: - rv = (objdata->data_funcs->get_bool)(objdata->data, attrs[i].type, - attrs[i].pValue, &attrs[i].ulValueLen); + rv = (objdata->data_funcs->get_bool)(objdata, &attrs[i]); break; case DATA_ULONG: - rv = (objdata->data_funcs->get_ulong)(objdata->data, attrs[i].type, - attrs[i].pValue, &attrs[i].ulValueLen); + rv = (objdata->data_funcs->get_ulong)(objdata, &attrs[i]); break; case DATA_BYTES: - rv = (objdata->data_funcs->get_bytes)(objdata->data, attrs[i].type, - attrs[i].pValue, &attrs[i].ulValueLen); + rv = (objdata->data_funcs->get_bytes)(objdata, &attrs[i]); break; case DATA_DATE: - rv = (objdata->data_funcs->get_date)(objdata->data, attrs[i].type, - attrs[i].pValue, &attrs[i].ulValueLen); + rv = (objdata->data_funcs->get_date)(objdata, &attrs[i]); break; case DATA_UNKNOWN: rv = CKR_ATTRIBUTE_TYPE_INVALID; @@ -453,4 +446,4 @@ ckcapi_object_data_get_attrs(CkCapiObjectData* objdata, CK_ATTRIBUTE_PTR attrs, } return ret; -} \ No newline at end of file +} diff --git a/ckcapi-session.c b/ckcapi-session.c index 5daae6e..496c039 100644 --- a/ckcapi-session.c +++ b/ckcapi-session.c @@ -16,8 +16,7 @@ object_data_release(CkCapiObjectData* objdata) { ASSERT(objdata->data_funcs); ASSERT(objdata->data_funcs->release); - (objdata->data_funcs->release)(objdata->data); - free(objdata); + (objdata->data_funcs->release)(objdata); } CkCapiSession* @@ -370,19 +369,11 @@ ckcapi_session_get_object_data(CkCapiSession* sess, CkCapiObject* obj, if(*objdata) return CKR_OK; - newdata = calloc(sizeof(CkCapiObjectData), 1); - if(!newdata) - return CKR_HOST_MEMORY; - - newdata->object = id; - ret = (obj->obj_funcs->load_data)(sess, obj, newdata); - if(ret != CKR_OK) { - free(newdata); + ret = (obj->obj_funcs->load_data)(sess, obj, &newdata); + if(ret != CKR_OK) return ret; - } newdata->object = id; - ASSERT(newdata->data); ASSERT(newdata->data_funcs); if(!ckcapi_hash_set(sess->object_data, &newdata->object, @@ -449,37 +440,26 @@ ckcapi_session_get_object_data_for(CkCapiSession* sess, CK_OBJECT_HANDLE hand, return ckcapi_session_get_object_data(sess, obj, objdata); } -CK_RV -ckcapi_session_set_object_data(CkCapiSession* sess, CkCapiObject* obj, - const CkCapiObjectData* objdata) +void +ckcapi_session_take_object_data(CkCapiSession* sess, CkCapiObject* obj, + CkCapiObjectData* objdata) { - CkCapiObjectData* newdata; CkCapiObjectData* prev; ASSERT(obj); ASSERT(sess); ASSERT(sess->object_data); - ASSERT(objdata); - - newdata = calloc(sizeof(CkCapiObjectData), 1); - if(!newdata) - return CKR_HOST_MEMORY; - - newdata->object = obj->id; - newdata->data = objdata->data; - newdata->data_funcs = objdata->data_funcs; + ASSERT(objdata); + objdata->object = obj->id; + prev = ckcapi_hash_rem(sess->object_data, &obj->id, sizeof(obj->id)); if(prev) object_data_release(prev); - if(!ckcapi_hash_set(sess->object_data, &newdata->object, - sizeof(newdata->object), newdata)) { - free(newdata); - return CKR_HOST_MEMORY; - } - - return CKR_OK; + if(!ckcapi_hash_set(sess->object_data, &objdata->object, + sizeof(objdata->object), objdata)) + object_data_release(objdata); } diff --git a/ckcapi-trust.c b/ckcapi-trust.c index aa8475a..b514d04 100644 --- a/ckcapi-trust.c +++ b/ckcapi-trust.c @@ -4,8 +4,6 @@ #include "pkcs11/pkcs11n.h" -#include - /* * These are the attributes expected by NSS on a trust object: * @@ -33,17 +31,19 @@ typedef struct _TrustObject } TrustObject; -typedef struct _TrustData +typedef struct _TrustObjectData { + CkCapiObjectData base; + PCCERT_CONTEXT cert; CTL_USAGE* usage; } -TrustData; +TrustObjectData; static CK_ULONG -has_usage(TrustData* trust_data, const char* oid) +has_usage(TrustObjectData* tdata, const char* oid) { - CTL_USAGE* usage = trust_data->usage; + CTL_USAGE* usage = tdata->usage; DWORD i; /* No usages, means anything goes */ @@ -62,14 +62,14 @@ has_usage(TrustData* trust_data, const char* oid) } static CK_RV -trust_bool_attribute(void* obj, CK_ATTRIBUTE_TYPE type, - CK_VOID_PTR data, CK_ULONG_PTR len) +trust_bool_attribute(CkCapiObjectData* objdata, CK_ATTRIBUTE_PTR attr) { CK_BBOOL val; - ASSERT(obj); + ASSERT(objdata); + ASSERT(attr); - switch(type) + switch(attr->type) { /* * Resides on the token @@ -106,19 +106,19 @@ trust_bool_attribute(void* obj, CK_ATTRIBUTE_TYPE type, return CKR_ATTRIBUTE_TYPE_INVALID; }; - return ckcapi_return_data(data, len, &val, sizeof(CK_BBOOL)); + return ckcapi_return_data(attr->pValue, &attr->ulValueLen, &val, sizeof(CK_BBOOL)); } static CK_RV -trust_ulong_attribute(void* obj, CK_ATTRIBUTE_TYPE type, - CK_VOID_PTR data, CK_ULONG_PTR len) +trust_ulong_attribute(CkCapiObjectData* objdata, CK_ATTRIBUTE_PTR attr) { - TrustData* trust_data = (TrustData*)obj; + TrustObjectData* tdata = (TrustObjectData*)objdata; CK_ULONG val; - ASSERT(obj); + ASSERT(tdata); + ASSERT(attr); - switch(type) + switch(attr->type) { /* @@ -133,51 +133,46 @@ trust_ulong_attribute(void* obj, CK_ATTRIBUTE_TYPE type, * Various trust flags */ case CKA_TRUST_SERVER_AUTH: - val = has_usage(trust_data, X509_USAGE_SERVER_AUTH); + val = has_usage(tdata, X509_USAGE_SERVER_AUTH); break; case CKA_TRUST_CLIENT_AUTH: - val = has_usage(trust_data, X509_USAGE_CLIENT_AUTH); + val = has_usage(tdata, X509_USAGE_CLIENT_AUTH); break; case CKA_TRUST_CODE_SIGNING: - val = has_usage(trust_data, X509_USAGE_CODE_SIGNING); + val = has_usage(tdata, X509_USAGE_CODE_SIGNING); break; case CKA_TRUST_EMAIL_PROTECTION: - val = has_usage(trust_data, X509_USAGE_EMAIL); + val = has_usage(tdata, X509_USAGE_EMAIL); break; case CKA_TRUST_IPSEC_END_SYSTEM: - val = has_usage(trust_data, X509_USAGE_IPSEC_ENDPOINT); + val = has_usage(tdata, X509_USAGE_IPSEC_ENDPOINT); break; case CKA_TRUST_IPSEC_TUNNEL: - val = has_usage(trust_data, X509_USAGE_IPSEC_TUNNEL); + val = has_usage(tdata, X509_USAGE_IPSEC_TUNNEL); break; case CKA_TRUST_IPSEC_USER: - val = has_usage(trust_data, X509_USAGE_IPSEC_USER); + val = has_usage(tdata, X509_USAGE_IPSEC_USER); break; case CKA_TRUST_TIME_STAMPING: - val = has_usage(trust_data, X509_USAGE_TIME_STAMPING); + val = has_usage(tdata, X509_USAGE_TIME_STAMPING); break; default: return CKR_ATTRIBUTE_TYPE_INVALID; }; - if(*len < sizeof(CK_ULONG)) - { - *len = sizeof(CK_ULONG); - return CKR_BUFFER_TOO_SMALL; - } - - return ckcapi_return_data(data, len, &val, sizeof(CK_ULONG)); + return ckcapi_return_data(attr->pValue, &attr->ulValueLen, &val, sizeof(CK_ULONG)); } static CK_RV -trust_bytes_attribute(void* obj, CK_ATTRIBUTE_TYPE type, - CK_VOID_PTR data, CK_ULONG_PTR len) +trust_bytes_attribute(CkCapiObjectData* objdata, CK_ATTRIBUTE_PTR attr) { - TrustData* trust_data = (TrustData*)obj; - ASSERT(obj); + TrustObjectData* tdata = (TrustObjectData*)objdata; + + ASSERT(tdata); + ASSERT(attr); - switch(type) + switch(attr->type) { /* * Forward these through to the certificate itself. @@ -186,18 +181,18 @@ trust_bytes_attribute(void* obj, CK_ATTRIBUTE_TYPE type, case CKA_ISSUER: case CKA_SERIAL_NUMBER: case CKA_LABEL: - ASSERT(trust_data->cert); - return ckcapi_cert_get_bytes_attribute((void*)(trust_data->cert), - type, data, len); + ASSERT(tdata->cert); + return ckcapi_cert_certificate_get_bytes(tdata->cert, attr); /* * The hash of the DER encoded certificate. */ case CKA_CERT_MD5_HASH: case CKA_CERT_SHA1_HASH: - if(!CryptHashCertificate(0, type == CKA_CERT_MD5_HASH ? CALG_MD5 : CALG_SHA1, - 0, trust_data->cert->pbCertEncoded, - trust_data->cert->cbCertEncoded, data, (DWORD*)len)) + if(!CryptHashCertificate(0, attr->type == CKA_CERT_MD5_HASH ? CALG_MD5 : CALG_SHA1, + 0, tdata->cert->pbCertEncoded, + tdata->cert->cbCertEncoded, attr->pValue, + (DWORD*)(&attr->ulValueLen))) return ckcapi_winerr_to_ckr(GetLastError()); return CKR_OK; }; @@ -206,8 +201,7 @@ trust_bytes_attribute(void* obj, CK_ATTRIBUTE_TYPE type, } static CK_RV -trust_date_attribute(void* obj, CK_ATTRIBUTE_TYPE type, - CK_VOID_PTR data, CK_ULONG_PTR len) +trust_date_attribute(CkCapiObjectData* objdata, CK_ATTRIBUTE_PTR attr) { return CKR_ATTRIBUTE_TYPE_INVALID; } @@ -215,16 +209,16 @@ trust_date_attribute(void* obj, CK_ATTRIBUTE_TYPE type, static void trust_release(void* data) { - TrustData* trust_data = (TrustData*)data; - ASSERT(trust_data); + TrustObjectData* tdata = (TrustObjectData*)data; + ASSERT(tdata); - ASSERT(trust_data->cert); - CertFreeCertificateContext(trust_data->cert); + ASSERT(tdata->cert); + CertFreeCertificateContext(tdata->cert); - if(trust_data->usage) - free(trust_data->usage); + if(tdata->usage) + free(tdata->usage); - free(trust_data); + free(tdata); } static const CkCapiObjectDataVtable trust_objdata_vtable = { @@ -236,16 +230,16 @@ static const CkCapiObjectDataVtable trust_objdata_vtable = { }; static CK_RV -parse_usage(TrustData* trust_data) +parse_usage(TrustObjectData* tdata) { DWORD size, usize, err; CTL_USAGE* usage; void* buf; - ASSERT(!trust_data->usage); + ASSERT(!tdata->usage); /* Get the size of the usage property */ - if(!CertGetCertificateContextProperty(trust_data->cert, CERT_CTL_USAGE_PROP_ID, + if(!CertGetCertificateContextProperty(tdata->cert, CERT_CTL_USAGE_PROP_ID, NULL, &size)) { err = GetLastError(); @@ -258,7 +252,7 @@ parse_usage(TrustData* trust_data) /* Now get the actual usage property */ buf = _alloca(size); - if(!CertGetCertificateContextProperty(trust_data->cert, CERT_CTL_USAGE_PROP_ID, + if(!CertGetCertificateContextProperty(tdata->cert, CERT_CTL_USAGE_PROP_ID, buf, &size)) { err = GetLastError(); @@ -283,15 +277,15 @@ parse_usage(TrustData* trust_data) return ckcapi_winerr_to_ckr(GetLastError()); } - trust_data->usage = usage; + tdata->usage = usage; return CKR_OK; } static CK_RV -trust_load_data(CkCapiSession* sess, CkCapiObject* obj, CkCapiObjectData* objdata) +trust_load_data(CkCapiSession* sess, CkCapiObject* obj, CkCapiObjectData** objdata) { TrustObject* tobj = (TrustObject*)obj; - TrustData* trust_data; + TrustObjectData* tdata; CkCapiObjectData* certdata; CK_RV ret; @@ -303,27 +297,28 @@ trust_load_data(CkCapiSession* sess, CkCapiObject* obj, CkCapiObjectData* objdat if(ret != CKR_OK) return ret; - trust_data = (TrustData*)calloc(1, sizeof(TrustData)); - if(!trust_data) + tdata = (TrustObjectData*)calloc(1, sizeof(TrustObjectData)); + if(!tdata) return CKR_HOST_MEMORY; - ASSERT(certdata->data); - trust_data->cert = certdata->data; + tdata->cert = ckcapi_cert_object_data_get_certificate (certdata); + ASSERT(tdata->cert); /* Dig up the usage data */ - ret = parse_usage(trust_data); + ret = parse_usage(tdata); if(ret != CKR_OK) { - free(trust_data); + free(tdata); return ret; } /* And keep a reference to the certificate */ - trust_data->cert = CertDuplicateCertificateContext((PCCERT_CONTEXT)(certdata->data)); + tdata->cert = CertDuplicateCertificateContext(tdata->cert); - objdata->data = trust_data; - objdata->data_funcs = &trust_objdata_vtable; + tdata->base.object = obj->id; + tdata->base.data_funcs = &trust_objdata_vtable; + *objdata = &(tdata->base); return CKR_OK; } diff --git a/ckcapi.c b/ckcapi.c index f0c4629..d7f023b 100644 --- a/ckcapi.c +++ b/ckcapi.c @@ -116,10 +116,6 @@ CK_RV ckcapi_return_data(CK_VOID_PTR dst, CK_ULONG_PTR dlen, CK_VOID_PTR src, DWORD slen) { - // TODO: This check should probably go elsewhere - if(slen == 0) - return CKR_ATTRIBUTE_TYPE_INVALID; - /* Just asking for the length */ if(!dst) { diff --git a/ckcapi.h b/ckcapi.h index 4e69c0d..f166528 100644 --- a/ckcapi.h +++ b/ckcapi.h @@ -1,14 +1,29 @@ + #ifndef CKCAPI_H #define CKCAPI_H -#ifndef ASSERT -#include "assert.h" -#define ASSERT assert -#endif +/* -------------------------------------------------------------------- + * + * Session = CkCapiSession + * - A PKCS#11 Session + * + * Objects = CkCapiObject + * - There's a global list of objects in ckcapi-object.c indexed by + * object handle. + * - The object itself has no attributes or cached data, but knows how + * to load data when needed. + * - Each object has a unique key which guarantees we don't load the + * same object twice with two different object handles. + * + * Object Data = CkCapiObjectData + * - Object Data is owned by the Session + * - Loaded data and/or attributes for an object. + */ #define WIN32_LEAN_AND_MEAN #define _WIN32_WINNT 0x400 #include +#include #define CRYPTOKI_EXPORTS #include "pkcs11/cryptoki.h" @@ -16,36 +31,25 @@ #include "ckcapi-util.h" struct _CkCapiObject; +struct _CkCapiObjectData; struct _CkCapiSession; typedef struct _CkCapiObject CkCapiObject; +typedef struct _CkCapiObjectData CkCapiObjectData; typedef struct _CkCapiSession CkCapiSession; -/* Represents 'any' class in searches */ -#define CKO_ANY CK_INVALID_HANDLE -/* ------------------------------------------------------------------ - * cryptoki-capi.c +/* ------------------------------------------------------------------ + * ckcapi-object.c */ -#define DBG(args) \ - ckcapi_debug args - -void ckcapi_debug(const char* msg, ...); -void ckcapi_lock_global(void); -void ckcapi_unlock_global(void); -CK_RV ckcapi_winerr_to_ckr (DWORD werr); - -CK_RV ckcapi_return_data(CK_VOID_PTR dst, CK_ULONG_PTR dlen, - CK_VOID_PTR src, DWORD slen); - -/* object data ------------------- */ - -typedef CK_RV (*CkCapiGetAttribute)(void* obj, CK_ATTRIBUTE_TYPE type, - CK_VOID_PTR data, CK_ULONG_PTR len); +/* A function to get an attribute from ObjectData */ +typedef CK_RV (*CkCapiGetAttribute)(CkCapiObjectData* objdata, CK_ATTRIBUTE_PTR attr); -typedef void (*CkCapiRelease)(void* value); +/* A function to free some data */ +typedef void (*CkCapiRelease)(void* data); +/* Object data functions */ typedef struct _CkCapiObjectDataVtable { CkCapiGetAttribute get_bool; @@ -56,14 +60,88 @@ typedef struct _CkCapiObjectDataVtable } CkCapiObjectDataVtable; -typedef struct _CkCapiObjectData +/* + * Base class for object data. Different types of + * objects extend this with more detailed data + */ +struct _CkCapiObjectData { CK_OBJECT_HANDLE object; - void* data; const CkCapiObjectDataVtable* data_funcs; +}; + +/* A function to load data for an object */ +typedef CK_RV (*CkCapiLoadData)(CkCapiSession* sess, struct _CkCapiObject* obj, + CkCapiObjectData** objdata); + +/* Object functions */ +typedef struct _CkCapiObjectVtable +{ + CkCapiLoadData load_data; + CkCapiRelease release; } -CkCapiObjectData; +CkCapiObjectVtable; +/* Represents a object we've seen */ +struct _CkCapiObject +{ + CK_OBJECT_HANDLE id; + const CkCapiObjectVtable* obj_funcs; + void* unique_key; + size_t unique_len; +}; + +/* Debug print something about an object */ +#define DBGO(obj, msg) \ + ckcapi_debug("O%d: %s", (obj) ? (obj)->id : 0, (msg)) +#define DBGOD(objdata, msg) \ + ckcapi_debug("O%d: %s", (objdata) ? (objdata)->obj : 0, (msg)) + +CK_OBJECT_HANDLE ckcapi_object_get_max_handle (void); + +CkCapiObject* ckcapi_object_lookup (CkCapiSession* sess, CK_OBJECT_HANDLE obj); + +CK_RV ckcapi_object_register (CkCapiSession* sess, CkCapiObject* obj); + +void ckcapi_object_clear_all (void); + + +CK_BBOOL ckcapi_object_data_match (CkCapiObjectData* objdata, + CK_ATTRIBUTE_PTR matches, CK_ULONG count); + +CK_BBOOL ckcapi_object_data_match_attr (CkCapiObjectData* objdata, + CK_ATTRIBUTE_PTR match); + +CK_RV ckcapi_object_data_get_attrs (CkCapiObjectData* objdata, CK_ATTRIBUTE_PTR attrs, + CK_ULONG count); + +/* + * Each object has a unique key which guarantees that we're + * not loading the same objects over and over again. + * Usually these are contiguous members of a struct. These + * macros help calculate the address and length of such a + * unique key + */ + +/* The unique key starts at the address of the starting struct member */ +#define UNIQUE_KEY_AT(obj, mem) \ + (void*)(&((obj->mem))) + +/* Calculates key length between first and last struct members */ +#define UNIQUE_KEY_LEN(obj, first, last) \ + UNIQUE_KEY_VAR_LEN(obj, first, last, sizeof(obj->last)) + +/* Calcs key len between first and a certain num of bytes past last struct member */ +#define UNIQUE_KEY_VAR_LEN(obj, first, last, len) \ + ((((char*)&((obj->last))) - ((char*)&((obj->first)))) + (len)) + +/* Used internally to have a unique id for different object types */ +enum +{ + OBJECT_CERT = 1, + OBJECT_BUILTIN = 2, + OBJECT_TRUST = 3 +}; /* ------------------------------------------------------------------ * cryptoki-capi-session.c @@ -118,8 +196,8 @@ CK_RV ckcapi_session_get_object_data (CkCapiSession* sess, CkCapiObject* obj, CK_RV ckcapi_session_get_object_data_for (CkCapiSession* sess, CK_OBJECT_HANDLE hand, CkCapiObjectData** objdata); -CK_RV ckcapi_session_set_object_data (CkCapiSession* sess, CkCapiObject* obj, - const CkCapiObjectData* objdata); +void ckcapi_session_take_object_data (CkCapiSession* sess, CkCapiObject* obj, + CkCapiObjectData* objdata); void ckcapi_session_clear_object_data (CkCapiSession* sess, CkCapiObject* obj); @@ -128,80 +206,6 @@ typedef void (*CkCapiEnumObjectData)(CkCapiSession* sess, CkCapiObject* obj, CkC void ckcapi_session_enum_object_data (CkCapiSession* sess, CkCapiEnumObjectData enum_func, void* arg); -/* ------------------------------------------------------------------ - * ckcapi-object.c - */ - -/* Used internally to guarantee uniqueness between object types */ -enum -{ - OBJECT_CERT = 1, - OBJECT_BUILTIN = 2, - OBJECT_TRUST = 3 -}; - - -typedef CK_RV (*CkCapiPurge)(struct _CkCapiObject* obj); -typedef CK_RV (*CkCapiLoadData)(CkCapiSession* sess, struct _CkCapiObject* obj, - CkCapiObjectData* objdata); - -typedef struct _CkCapiObjectVtable -{ - CkCapiLoadData load_data; - CkCapiRelease release; -} -CkCapiObjectVtable; - -/* - * Each object has a unique key which guarantees that we're - * not loading the same objects over and over again. - * Usually these are contiguous members of a struct. These - * macros help calculate the address and length of such a - * unique key - */ - -/* The unique key starts at the address of the starting struct member */ -#define UNIQUE_KEY_AT(obj, mem) \ - (void*)(&((obj->mem))) - -/* Calculates key length between first and last struct members */ -#define UNIQUE_KEY_LEN(obj, first, last) \ - UNIQUE_KEY_VAR_LEN(obj, first, last, sizeof(obj->last)) - -/* Calcs key len between first and a certain num of bytes past last struct member */ -#define UNIQUE_KEY_VAR_LEN(obj, first, last, len) \ - ((((char*)&((obj->last))) - ((char*)&((obj->first)))) + (len)) - -struct _CkCapiObject -{ - CK_OBJECT_HANDLE id; - const CkCapiObjectVtable* obj_funcs; - void* unique_key; - size_t unique_len; -}; - -#define DBGO(obj, msg) \ - ckcapi_debug("O%d: %s", (obj) ? (obj)->id : 0, (msg)) -#define DBGOD(objdata, msg) \ - ckcapi_debug("O%d: %s", (objdata) ? (objdata)->obj : 0, (msg)) - -CK_OBJECT_HANDLE ckcapi_object_get_max_handle (void); - -CkCapiObject* ckcapi_object_lookup (CkCapiSession* sess, CK_OBJECT_HANDLE obj); - -CK_RV ckcapi_object_register (CkCapiSession* sess, CkCapiObject* obj); - -void ckcapi_object_clear_all (void); - - -CK_BBOOL ckcapi_object_data_match (CkCapiObjectData* objdata, - CK_ATTRIBUTE_PTR matches, CK_ULONG count); - -CK_BBOOL ckcapi_object_data_match_attr (CkCapiObjectData* objdata, - CK_ATTRIBUTE_PTR match); - -CK_RV ckcapi_object_data_get_attrs (CkCapiObjectData* objdata, CK_ATTRIBUTE_PTR attrs, - CK_ULONG count); /* ------------------------------------------------------------------- * ckcapi-cert.c @@ -216,8 +220,10 @@ CK_RV ckcapi_cert_find_specific (CkCapiSession* sess, CK_OBJECT_CLASS cls, CK_OBJECT_HANDLE_PTR obj); /* Called by trust stuff */ -CK_RV ckcapi_cert_get_bytes_attribute (void* cert, CK_ATTRIBUTE_TYPE type, - CK_VOID_PTR data, CK_ULONG_PTR len); +CK_RV +ckcapi_cert_certificate_get_bytes(PCCERT_CONTEXT cert, CK_ATTRIBUTE_PTR attr); + +PCCERT_CONTEXT ckcapi_cert_object_data_get_certificate (CkCapiObjectData* objdata); /* ------------------------------------------------------------------- * ckcapi-builtin.c @@ -239,6 +245,47 @@ CK_RV ckcapi_trust_find_specific (CkCapiSession* sess, CK_OBJECT_CLASS cls, CK_ATTRIBUTE_PTR issuer, CK_ATTRIBUTE_PTR serial, CK_OBJECT_HANDLE_PTR obj); +/* ------------------------------------------------------------------ + * cryptoki-capi.c + * + * Module helper and logging functions. + */ + +#define DBG(args) \ + ckcapi_debug args + +void ckcapi_debug (const char* msg, ...); + +/* + * Protect global data with these. + */ +void ckcapi_lock_global (void); +void ckcapi_unlock_global (void); + +/* + * Convert a GetLastError() windows error to a + * PKCS#11 return code. + */ +CK_RV ckcapi_winerr_to_ckr (DWORD werr); + +/* + * This stores data in the output buffer with appropriate + * PKCS#11 codes when the buffer is too short, or the caller + * just wants to know the length, etc. + */ +CK_RV ckcapi_return_data (CK_VOID_PTR dst, CK_ULONG_PTR dlen, + CK_VOID_PTR src, DWORD slen); -#endif /* CRYPTOKI_CAPI_H */ +/* ------------------------------------------------------------------ */ + +#ifndef ASSERT +#include "assert.h" +#define ASSERT assert +#endif + +/* Represents 'any' class in searches */ +#define CKO_ANY CK_INVALID_HANDLE + + +#endif /* CRYPTOKI_CAPI_H */ -- cgit v1.2.3