Need help to retrieve a Windows Certificate by Template? - c++

I need to retrieve a Windows Certificate by Template Name (The Template is in the Extension Fields) using Windows API, C++.
My steps:
Open store: CertOpenStore(..) (Done; I can enumerate the certificates using CertEnumCertificatesInStore(..), but I see only their "Version 1 fields" and not the "Extensions". The Template is in the Extensions, so I cannot find it.)
I tried to find it using the CertFindCertificateInStore(), but did not succeed. Can anyone help me with the correct find type and parameters or use another function?
CertFreeCertificateContext(..), CertCloseStore(..) (Done).

I would like to post my code, hope it helps someone.
void GetCertificateByTemplate(char *certificateTemplate)
{
HCERTSTORE hCertStore;
PCCERT_CONTEXT pCertContext = NULL;
BYTE *pbDecoded;
DWORD cbDecoded;
_CERT_TEMPLATE_EXT *pbDecodedTemplate = NULL;
// 1). Open Local Machine certificate store
if (hCertStore = CertOpenStore(
CERT_STORE_PROV_SYSTEM,
0,
NULL,
CERT_SYSTEM_STORE_LOCAL_MACHINE,
L"My"))
{
fprintf(stderr, "The store has been opened. \n");
}
// 2). Enumerate certificates
while (pCertContext = CertEnumCertificatesInStore(
hCertStore,
pCertContext))
{
// 3). Check certificate extended data
for (int i = 0; i < pCertContext->pCertInfo->cExtension; i++)
{
// 4). Decode certificate extended data
if (CryptDecodeObject(
X509_ASN_ENCODING,
pCertContext->pCertInfo->rgExtension[i].pszObjId,
pCertContext->pCertInfo->rgExtension[i].Value.pbData,
pCertContext->pCertInfo->rgExtension[i].Value.cbData,
0,
NULL,
&cbDecoded))
{
; // error !!!
}
if (!(pbDecoded = (BYTE*)malloc(cbDecoded)))
{
; // error !!!
}
if (CryptDecodeObject(
X509_ASN_ENCODING,
pCertContext->pCertInfo->rgExtension[i].pszObjId,
pCertContext->pCertInfo->rgExtension[i].Value.pbData,
pCertContext->pCertInfo->rgExtension[i].Value.cbData,
0,
pbDecoded,
&cbDecoded))
{
pbDecodedTemplate = (_CERT_TEMPLATE_EXT*)pbDecoded;
char* objectId = pbDecodedTemplate->pszObjId;
// todo: check pDecodeTemplate->pszObjId
// 5). Compare the template string with the search one
if (strcmp(pbDecodedTemplate->pszObjId, certificateTemplate) == 0)
{
// todo: return certificate
printf("\nCertificate template found: %s \n", pbDecodedTemplate->pszObjId);
break;
}
}
}
}
// 6). Free certificate, close store
if (pCertContext)
{
CertFreeCertificateContext(pCertContext);
}
CertCloseStore(hCertStore, 0);
}

Related

CertGetCertificateChain with a supporting memory store and Certificate Trust List

I need to mark a custom self-signed root certificate as trusted during certificate chain validation and, overall, I want to rely on the system API as much as possible.
I create a temporary memory store.
HCERTSTORE certStore = CertOpenStore(CERT_STORE_PROV_MEMORY, 0, 0, 0, 0);
Then I place the custom root certificate into the store.
CertAddCertificateContextToStore(certStore, rootCertContext, CERT_STORE_ADD_REPLACE_EXISTING, 0);
The CertGetCertificateChain MSDN documentation says
hAdditionalStore A handle to any additional store to search for supporting certificates and certificate trust lists (CTLs).
As far as I understand if I create a CTL with the root certificate and place it to the store, CertGetCertificateChain will trust it. So, I create the root certificate CTL entry in an allocated buffer and then copy it to std::vector ctlEntries
CertCreateCTLEntryFromCertificateContextProperties(rootCertContext, 0, nullptr, CTL_ENTRY_FROM_PROP_CHAIN_FLAG, nullptr, ctlEntry, &size);
Then I create the CTL itself.
const std::wstring ctlID(L"TrustedRoots");
// I do not know what OIDs to use here. I tried different options.
std::vector<LPSTR> usageList;
usageList.push_back(szOID_SORTED_CTL);
usageList.push_back(szOID_PKIX_KP_CLIENT_AUTH);
usageList.push_back(szOID_PKIX_KP_SERVER_AUTH);
CTL_INFO ctlInfo;
ZeroMemory(&ctlInfo, sizeof(ctlInfo));
ctlInfo.dwVersion = CTL_V1;
ctlInfo.SubjectUsage.cUsageIdentifier = static_cast<DWORD>(usageList.size());
ctlInfo.SubjectUsage.rgpszUsageIdentifier = usageList.data();
ctlInfo.ListIdentifier.cbData = static_cast<DWORD>((ctlID.size() + 1) * sizeof(wchar_t));
ctlInfo.ListIdentifier.pbData = static_cast<BYTE*>(static_cast<void*>(const_cast<wchar_t*>(ctlID.data())));
ctlInfo.SubjectAlgorithm.pszObjId = szOID_OIWSEC_sha1;
ctlInfo.cCTLEntry = static_cast<DWORD>(ctlEntries.size());
ctlInfo.rgCTLEntry = const_cast<PCTL_ENTRY>(ctlEntries.data());
// From MSDN:
// The message can be encoded without signers if the cbSize member of the structure is set to the
// size of the structure and all of the other members are set to zero.
CMSG_SIGNED_ENCODE_INFO encode;
ZeroMemory(&encode, sizeof(encode));
encode.cbSize = sizeof(encode);
DWORD size = 0, flags = CMSG_ENCODE_SORTED_CTL_FLAG | CMSG_ENCODE_HASHED_SUBJECT_IDENTIFIER_FLAG;
if (CryptMsgEncodeAndSignCTL(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, &ctlInfo, &encode, flags, nullptr, &size) == TRUE)
{
std::string data;
data.resize(size);
BYTE* p = static_cast<BYTE*>(static_cast<void*>(&data.front()));
if (CryptMsgEncodeAndSignCTL(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, &ctlInfo, &encode, flags, p, &size) == TRUE)
{
PCCTL_CONTEXT ctlContext = CertCreateCTLContext(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, p, size);
if (ctlContext)
{
if (CertAddCTLContextToStore(certStore, ctlContext, CERT_STORE_ADD_REPLACE_EXISTING, nullptr) == TRUE)
{
// success
}
}
}
}
All API calls above finish successfully but when I call CertGetCertificateChain, it still returns CERT_TRUST_IS_UNTRUSTED_ROOT in TrustStatus.dwErrorStatus.
Potential Workaround
If I get the CERT_TRUST_IS_UNTRUSTED_ROOT error, I just extract the CTL from the certificate store and check if the root from the result chain (returned by CertGetCertificateChain) is in the CTL. It works but is still not fully acceptable for me. I would like to rely on CertGetCertificateChain.
What is wrong with the solution?
What specific Certificate Trust List OIDs must I use?
Is any requirement (like specific extensions) for the root certificate to be trusted in this case?
p.s. The test certificates are created using this instruction https://gist.github.com/fntlnz/cf14feb5a46b2eda428e000157447309
UPD: 2020-01-31
CertModifyCertificatesToTrust did not help. It finishes successfully but the chain is still reported as having an untrusted root. Probably, the issue is in different area.
PCCERT_CONTEXT copiedCert = nullptr;
BOOL result = CertAddCertificateContextToStore(certStore,
cert, CERT_STORE_ADD_REPLACE_EXISTING, &copiedCert);
CertFreeCertificateContext(cert);
if (result)
{
// Save the certificate to create a CTL entry later
trustedRoots.push_back(copiedCert);
}
...
// Creating the CTL entries
...
std::vector<LPSTR> usageList;
usageList.push_back(szOID_CTL); // I really do not know what IDs I must use here
...
CTL_INFO ctlInfo;
ZeroMemory(&ctlInfo, sizeof(ctlInfo));
ctlInfo.dwVersion = CTL_V1;
ctlInfo.SubjectUsage.cUsageIdentifier = static_cast<DWORD>(usageList.size());
ctlInfo.SubjectUsage.rgpszUsageIdentifier = usageList.data();
...
// Should I use any of the flags?
DWORD size = 0, flags = 0; /*CMSG_ENCODE_SORTED_CTL_FLAG | CMSG_ENCODE_HASHED_SUBJECT_IDENTIFIER_FLAG;*/
if (CryptMsgEncodeAndSignCTL(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, &ctlInfo, &encode, flags, nullptr, &size) == TRUE)
...
if (CertAddCTLContextToStore(certStore, ctlContext, CERT_STORE_ADD_REPLACE_EXISTING, nullptr) == TRUE)
{
// Check that the CTL is in the store and the root certificate is in the CTL
CTL_FIND_USAGE_PARA usagePara;
ZeroMemory(&usagePara, sizeof(usagePara));
usagePara.cbSize = sizeof(usagePara);
usagePara.SubjectUsage.cUsageIdentifier = 0;
usagePara.ListIdentifier.cbData = static_cast<DWORD>((ctlID.size() + 1) * sizeof(wchar_t));
usagePara.ListIdentifier.pbData = static_cast<BYTE*>(static_cast<void*>(const_cast<wchar_t*>(ctlID.data())));
PCCTL_CONTEXT foundCTLContext = CertFindCTLInStore(certStore,
X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, 0, CTL_FIND_USAGE,
static_cast<void*>(&usagePara), nullptr);
if (foundCTLContext != nullptr)
{
PCTL_ENTRY ctlEntry = CertFindSubjectInCTL(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
CTL_CERT_SUBJECT_TYPE, const_cast<void*>(*trustedRoots.begin()), foundCTLContext, 0);
if (ctlEntry != nullptr)
{
// It means the root certificate has been correctly added to the CTL and the CTL is in the store.
std::cout << "Found the certificate in the CTL" << std::endl;
}
}
// Make the certificate trusted via CertModifyCertificatesToTrust
HMODULE module = LoadLibrary(L"CryptDlg.dll");
if (module)
{
CertModifyCertificatesToTrustPfn pfn =
(CertModifyCertificatesToTrustPfn)GetProcAddress(hModule, "CertModifyCertificatesToTrust");
if (pfn != nullptr)
{
CTL_MODIFY_REQUEST req;
// Only one certificate is in the trustedRoots store curretly
req.pccert = static_cast<PCCERT_CONTEXT>(*trustedRoots.begin());
req.dwOperation = CTL_MODIFY_REQUEST_ADD_TRUSTED;
req.dwError = 0;
HRESULT hr = pfn(1, &req, szOID_CTL, NULL, certStore, nullptr);
if (hr == S_OK)
{
// Success
std::cout << "Modified" << std::endl;
}
}
}
}
You can try to use the following api: CertModifyCertificatesToTrust
And note that
This function has no associated import library. You must use the
LoadLibrary and GetProcAddress functions to dynamically link to
CryptDlg.dll.
Set the CTL_MODIFY_REQUEST.dwOperation to the flagCTL_MODIFY_REQUEST_ADD_TRUSTED to add the certificate to the CTL. The certificate is explicitly trusted.

Add Subject Alternate Name to self signed certificate using wincrypt

How to add Subject Alternative Name in the certificate. Using wincrypt I have created and added the certificate to "MY" and "root" path where CN can be machine fully qualified domain name or hostname or IP. As Subject alternate name I want to add the following:DNS name=fully qualified domain name, DNS name=hostname and
DNS name=IP
How to do this?
I do not want to use openssl.
LPCTSTR cnName= fqdn;
DWORD cbEncoded = 0;
if (!CertStrToName(X509_ASN_ENCODING, cnName, CERT_X500_NAME_STR, NULL,
pbdata, &cbData, NULL))
{
_tprintf(_T("CertStrToName failed 0x%x\n"), GetLastError());
return 0;
}
if (!(pbdata = (BYTE *)malloc(cbData)))
{
_tprintf(_T("malloc Error 0x%x\n"), GetLastError());
return 0;
}
if (!CertStrToName(X509_ASN_ENCODING, cnName, CERT_X500_NAME_STR, NULL, pbdata, &cbData, NULL))
{
_tprintf(_T("CertStrToName failed 0x%x\n"), GetLastError());
return 0;
}
CERT_NAME_BLOB IssuerBlob;
IssuerBlob.cbData = cbEncoded;
IssuerBlob.pbData = pbEncoded;
CertCreateSelfSignCertificate(NULL, &IssuerBlob, 0, &KeyProvInfo, &Alg, 0, &EndTime, 0);
OpenandAddCertificateToStore(pCertContext, L"MY");
OpenandAddCertificateToStore(pCertContext, L"Root");
This creates and adds certificate to the store without SAN in the certificate
I tried passing extension list like below:
CertCreateSelfSignCertificate(NULL, &IssuerBlob, 0, &KeyProvInfo, &Alg, 0, &EndTime, &myExtns_list);
CERT_EXTENSION myExtn;
myExtn.fCritical = TRUE;
myExtn.pszObjId = szOID_SUBJECT_ALT_NAME;
myExtn.Value = myBlobdata;
CERT_EXTENSIONS myExtns_list;
myExtns_list.cExtension = 1;
myExtns_list.rgExtension = &myExtn;
char cb[20] = { "DNS Names=abc.com" };
BYTE *pbData = (BYTE*)cb;
CERT_NAME_BLOB myBlobdata;
myBlobdata.cbData = 20;
myBlobdata.pbData = pbData;
with this I could get the SAN as byte format in the left pane and the right pane shows my string "DNS Names=abc.com".
But my requirement is to show only the DNS Names in the SAN.
I'm assuming that your building of the extension data actually happened prior to your call to CertCreateSelfSignedCertificate (and, likewise, that your building of myBlobdata happened before using it).
The main problem is that you used one SAN entry (sort of) as the entirety of the SAN extension; which meant you lost some encoding wrappers. A secondary problem is that you used szOID_SUBJECT_ALT_NAME, which is the incorrect OID for Subject Alternative Name... you actually want szOID_SUBJECT_ALT_NAME2.
I want to add the following:DNS name=fully qualified domain name, DNS name=hostname and DNS name=IP
Adding an IP address as a DNS name is non-standard and is supposed to result in a match failure. You'd actually want to add the IP address as an IP Address SAN entry.
CERT_ALT_NAME_ENTRY entries[3];
entries[0] = { CERT_ALT_NAME_DNS_NAME };
entries[0].pwszDNSName = L"example.org";
// IPv4 Address 10.12.1.130
BYTE ip4Bytes[] = { 10, 12, 1, 130 };
entries[1] = { CERT_ALT_NAME_IP_ADDRESS };
entries[1].IPAddress = { sizeof(ip4Bytes), ip4Bytes };
// ::1, big-endian
BYTE ip6Bytes[16] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 };
entries[2] = { CERT_ALT_NAME_IP_ADDRESS };
entries[2].IPAddress = { sizeof(ip6Bytes), ip6Bytes };
CERT_NAME_BLOB name = { cbEncoded, buf };
BYTE extBuf[1024] = { 0 };
cbEncoded = sizeof(extBuf);
CERT_ALT_NAME_INFO info = { sizeof(entries) / sizeof(CERT_ALT_NAME_ENTRY), entries };
if (!CryptEncodeObjectEx(
X509_ASN_ENCODING,
X509_ALTERNATE_NAME,
&info,
0,
nullptr,
extBuf,
&cbEncoded))
{
// Whatever your error handling story is.
//
// Note I didn't do a 0 buffer or CRYPT_ENCODE_ALLOC; I just knew
// that my buffer would be big enough.
}
CERT_EXTENSION extension = { 0 };
extension.fCritical = 0;
extension.pszObjId = szOID_SUBJECT_ALT_NAME2;
extension.Value = { cbEncoded, extBuf };
CERT_EXTENSIONS extensions = { 1, &extension };
...
PCCERT_CONTEXT cert = CertCreateSelfSignCertificate(
0,
&name,
0,
&keyProvInfo,
&sigAlg,
0,
&certExpirationDate,
&extensions);
In CertUI that gives me the Subject Alternative Name value I expect:
DNS Name=example.org
IP Address=10.12.1.130
IP Address=0000:0000:0000:0000:0000:0000:0000:0001

Getting digital signature from mmc.exe at windows 8

I have an application that tries to verify the mmc.exe (services) signature. (the context of the application I think is irrelevant) I am trying with winapi function which both fails with
WinVerifyTrust. I get TRUST_E_BAD_DIGEST when I am trying with verification from catalog, and
TRUST_E_NOSIGNATURE when trying from file info. it is very important to mention that my function succeeds on win7, XP but fails on win8.
this is the code snippet for the function
CATALOG_INFO InfoStruct = {0};
InfoStruct.cbStruct = sizeof(CATALOG_INFO);
WINTRUST_CATALOG_INFO WintrustCatalogStructure = {0};
WintrustCatalogStructure.cbStruct = sizeof(WINTRUST_CATALOG_INFO);
WINTRUST_FILE_INFO WintrustFileStructure = {0};
WintrustFileStructure.cbStruct = sizeof(WINTRUST_FILE_INFO);
GUID ActionGuid = WINTRUST_ACTION_GENERIC_VERIFY_V2;
//Get a context for signature verification.
HCATADMIN Context = NULL;
if(!::CryptCATAdminAcquireContext(&Context, NULL, 0) ){
return false;
}
//Open file.
cx_handle hFile(::CreateFileW(filename_.c_str(), GENERIC_READ, 7, NULL, OPEN_EXISTING, 0, NULL));
if( INVALID_HANDLE_VALUE == (HANDLE)hFile )
{
CryptCATAdminReleaseContext(Context, 0);
return false;
}
//Get the size we need for our hash.
DWORD HashSize = 0;
::CryptCATAdminCalcHashFromFileHandle(hFile, &HashSize, NULL, 0);
if( HashSize == 0 )
{
//0-sized has means error!
::CryptCATAdminReleaseContext(Context, 0);
return false;
}
//Allocate memory.
buffer hashbuf(HashSize);
//Actually calculate the hash
if( !CryptCATAdminCalcHashFromFileHandle(hFile, &HashSize, hashbuf.data, 0) )
{
CryptCATAdminReleaseContext(Context, 0);
return false;
}
//Convert the hash to a string.
buffer MemberTag(((HashSize * 2) + 1) * sizeof(wchar_t));
for( unsigned int i = 0; i < HashSize; i++ ){
swprintf(&((PWCHAR)MemberTag.data)[i * 2], L"%02X", hashbuf.data[i ]);
}
//Get catalog for our context.
HCATINFO CatalogContext = CryptCATAdminEnumCatalogFromHash(Context, hashbuf, HashSize, 0, NULL);
if ( CatalogContext )
{
//If we couldn't get information
if ( !CryptCATCatalogInfoFromContext(CatalogContext, &InfoStruct, 0) )
{
//Release the context and set the context to null so it gets picked up below.
CryptCATAdminReleaseCatalogContext(Context, CatalogContext, 0);
CatalogContext = NULL;
}
}
//If we have a valid context, we got our info.
//Otherwise, we attempt to verify the internal signature.
WINTRUST_DATA WintrustStructure = {0};
WintrustStructure.cbStruct = sizeof(WINTRUST_DATA);
if( !CatalogContext )
{
load_signature_verification_from_file_info(WintrustFileStructure, WintrustStructure);
}
else
{
load_signature_verification_from_catalog(WintrustStructure, WintrustCatalogStructure, InfoStruct, MemberTag);
}
//Call our verification function.
long verification_res = ::WinVerifyTrust(0, &ActionGuid, &WintrustStructure);
//Check return.
bool is_success = SUCCEEDED(verification_res) ? true : false;
// if failed with CatalogContext, try with FILE_INFO
if(!is_success && CatalogContext && verification_res != TRUST_E_NOSIGNATURE)
{
//warning2(L"Failed verification with Catalog Context: 0x%x %s ; Retrying with FILE_INFO.", verification_res, (const wchar_t*)format_last_error(verification_res));
load_signature_verification_from_file_info(WintrustFileStructure, WintrustStructure);
verification_res = ::WinVerifyTrust(0, &ActionGuid, &WintrustStructure);
is_success = SUCCEEDED(verification_res) ? true : false;
}
if(perr && !is_success && verification_res != TRUST_E_NOSIGNATURE)
{
perr->code = verification_res;
perr->description = format_last_error(verification_res);
}
//Free context.
if( CatalogContext ){
::CryptCATAdminReleaseCatalogContext(Context, CatalogContext, 0);
}
//If we successfully verified, we need to free.
if( is_success )
{
WintrustStructure.dwStateAction = WTD_STATEACTION_CLOSE;
::WinVerifyTrust(0, &ActionGuid, &WintrustStructure);
}
::CryptCATAdminReleaseContext(Context, 0);
return is_success;
I don't think any thing had changed in this function from win7 to win 8 so what could possibly go wrong?
UPDATE
I did notice that my function does work for task manager at win 8.
but again for the mmc it does not work.
It appears that your general approach is correct and the functions themselves haven't changed. However there are subtle changes; namely the data on which they operate has changed. The hashes stored for files on Windows 8, according to comments on CryptCATAdminCalcHashFromFileHandle, are calculated using SHA-256 hashes.
The SHA-256 hashing algorithm is not supported by CryptCATAdminCalcHashFromFileHandle, so you must update the code to use CryptCATAdminAcquireContext2 and CryptCATAdminCalcHashFromFileHandle2 on Windows 8; the former allows you to acquire a HCATADMIN with a specified hash algorithm, and the latter allows using that HCATADMIN.
(Interestingly, WINTRUST_CATALOG_INFO also points this direction with its HCATADMIN hCatAdmin member, documented as "Windows 8 and Windows Server 2012: Support for this member begins.")

Windows WMI - Getting multiple MAC addresses

I am using the following function and query to retrieve the network adapter's MAC address :
QueryValue( pService, L"SELECT MACAddress FROM Win32_NetworkAdapter", L"MACAddress", NetMacAddress, bufferLength );
But i don't know how to specify which card i want to get the address of. Is there a way to get the MAC address of every card on the computer (assuming they are activated and all that) using WMI ? Or using something else ?
Thank you :)
Without WMI you can simply use something like this...
unsigned long ulLen = 0;
IP_ADAPTER_ADDRESSES* p_adapAddress = NULL;
DWORD dwRetValue = GetAdaptersAddresses(AF_INET, 0, NULL, p_adapAddress,&ulLen);
if(dwRetValue == ERROR_BUFFER_OVERFLOW)
{
p_adapAddress = (PIP_ADAPTER_ADDRESSES)malloc(ulLen);
if(p_adapAddress)
{
dwRetValue = GetAdaptersAddresses(AF_INET, 0, NULL, p_adapAddress,&ulLen);
if(dwRetValue == NO_ERROR)
{
IP_ADAPTER_ADDRESSES* p_adapAddressAux = p_adapAddress;
do
{
// Get the value of the p_adapAddressAux.PhysicalAddress
}
while(p_adapAddressAux != NULL);
}
free(p_adapAddress);
}
}

How to make upnp action?

I want to implement port-forwarding using intel-upnp.
I got XML data like:
Device found at location: http://192.168.10.1:49152/gatedesc.xml
service urn:schemas-upnp-org:service:WANIPConnection:1
controlurl /upnp/control/WANIPConn1
eventsuburl : /upnp/control/WANIPConn1
scpdurl : /gateconnSCPD.xml
And now, I want to make upnp-action. But, I don't know how to make it.
If you know some code snippet or helpful URL in C, please tell me.
char actionxml[250];
IXML_Document *action = NULL;
strcpy(actionxml, "<u:GetConnectionTypeInfo xmlns:u=\"urn:schemas-upnp- org:service:WANCommonInterfaceConfig:1\">");
action = ixmlParseBuffer(actionxml);
int ret = UpnpSendActionAsync( g_handle,
"http:192.168.10.1:49152/upnp/control/WANCommonIFC1",
"urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1",
NULL,
action,
upnp_callback,
NULL);
I know this is an old question, but it can be kept for reference. You can take a look at the sample code in the libupnp library here: https://github.com/mrjimenez/pupnp/blob/master/upnp/sample/common/tv_ctrlpt.c
The relevant code is in the function TvCtrlPointSendAction():
int TvCtrlPointSendAction(
int service,
int devnum,
const char *actionname,
const char **param_name,
char **param_val,
int param_count)
{
struct TvDeviceNode *devnode;
IXML_Document *actionNode = NULL;
int rc = TV_SUCCESS;
int param;
ithread_mutex_lock(&DeviceListMutex);
rc = TvCtrlPointGetDevice(devnum, &devnode);
if (TV_SUCCESS == rc) {
if (0 == param_count) {
actionNode =
UpnpMakeAction(actionname, TvServiceType[service],
0, NULL);
} else {
for (param = 0; param < param_count; param++) {
if (UpnpAddToAction
(&actionNode, actionname,
TvServiceType[service], param_name[param],
param_val[param]) != UPNP_E_SUCCESS) {
SampleUtil_Print
("ERROR: TvCtrlPointSendAction: Trying to add action param\n");
/*return -1; // TBD - BAD! leaves mutex locked */
}
}
}
rc = UpnpSendActionAsync(ctrlpt_handle,
devnode->device.
TvService[service].ControlURL,
TvServiceType[service], NULL,
actionNode,
TvCtrlPointCallbackEventHandler, NULL);
if (rc != UPNP_E_SUCCESS) {
SampleUtil_Print("Error in UpnpSendActionAsync -- %d\n",
rc);
rc = TV_ERROR;
}
}
ithread_mutex_unlock(&DeviceListMutex);
if (actionNode)
ixmlDocument_free(actionNode);
return rc;
}
The explanation is that you should create the action with UpnpMakeAction() if you have no parameters or UpnpAddToAction() if you have parameters to create your action, and then send it either synchronously or asynchronously.