Overview
The authorization engine is one of the core components of IBM Tivoli Access Manager for e-Business (TAMeb), specifically the entitlement service plug-in that provides a mechanism to retrieve access decision information (ADI) used by TAMeb to perform authorization rule evaluations.
Entitlement service plug-ins are share libraries that extend the TAMeb authorization engine with their own entitlement model, manipulating authorization structures such as user credentials and attribute lists. TAMeb version 6.0 and later includes a new class of entitlement service known as Dynamic Retrieval Service that is designed to retrieve ADI data that could exist in the register but also on external sources or even be calculated.
The following paragraphs describe how to create and invoke a dynamic entitlement service plug-in to extend the TAMeb authorization engine.
Some assumptions to consider
- This article assumes that you are working with TAMeb version 6.0 or later. Testing the plug-in with an early version may lead to unexpected results.
- Knowledge of C/C++ programming language and Microsoft Visual C++ 2005 development environment is required.
- Knowledge of XML/XSL format and processing.
- Knowledge of TAMeb’s authorization engine is also recommended to better understand this article, as it assumes that some concepts are familiar to the reader.
Introduction
To understand how a dynamic entitlement service plug-in works, I will describe a use case scenario where the customer is looking for ways to prevent URL tampering (i.e. based on URL redirects). The idea is to include a hash value or fingerprint in the URL string and have it verified with the plug-in to assure that no one have tampered with the parameters. Figure 1 shows the use case in detail.
Figure 1 – Using TAMeb’s entitlement service plug-in to prevent URL tampering

The use case is as follows:
- A URL request to access a protected resource is submitted. The URL string contains a hash value to verify the integrity of the parameters.
- TAMeb’s reverse proxy intercepts the URL request, authenticates user credentials and passes its control to the authorization engine for rule evaluation.
- If an authorization rule has been defined, the appropriated entitlement service plug-in is called to retrieve additional information (ADI data).
- The plug-in calculates a hash value using the URL’s parameters and a shared key known by TAMeb and the requestor only. The value is returned to the authorization rule engine.
- The authorization rule engine uses the returned value to evaluate the rule criterion, which states that the returned value must match the URL request’s value.
- If the rule criterion is met, the request is redirected to the protected resource for further processing.
- If failure to met the rule criterion, an error message is returned to the requestor.
Step 1: Creating a Dynamic Entitlement Service Plug-in
Entitlements service plug-ins are created using TAMeb’s C application programming interface (API) which requires a C compiler and TAMeb’s Application Development Kit (ADK). Each plug-in is a standalone module that is dynamically loaded in memory by the authorization engine. The TAMeb’s authorization engine recognizes and registers an entitlement service plug-in with the service dispatcher by reading entries in the aznapi-configuration stanza or in the aznapi.conf file.
The API model provides a standard interface to the service dispatcher to initialize and shut down all types of service plug-ins. For service plug-ins at least three functions must be implemented:
- azn_svc_initialize()
Initialize the entitlement service
- azn_svc_entitlement_get_entitlements()
Main function called when the authorization engine evaluates authorization rules
- azn_svc_shutdown()
Shutdown the entitlement service
The following listing shows the source code for the plug-in. I have included log information that can be useful to debug the results but the code is self explanatory, however one thing to consider is the use of application context attributes that will give you information about the URL request. You will need these attributes to calculate the hash value.
/***************************************************************************************
* File Name: azn_ent_srv_hash.c
* Dynamic ADI Entitlement Service Plug-in that extend TAMeb authorization engine.
* Function: Validates integrity of URL string, can be used to prevent URL tampering.
* To be called an authorization rule (Authzrule)) must be defined.
*
* Note: Based on sample code provided by TAMeb authorization development kit.
***************************************************************************************/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
//#include <strings.h>
#include <ctype.h>
#include <ogauthzn.h>
#include <azn_svc_protos.h>
#include <aznutils.h>
// this version
#define ENT_SVC_DEMO_VER "Sample Dynamic ADI Entitlement Service Plug-In v1.0"
#define MAX_ATTR_LENGTH (2048)
#define LINELEN (128)
#ifdef WIN32
#define STRCASECMP _stricmp
#define STRNCASECMP _strnicmp
#else
#define STRCASECMP strcasecmp
#define STRNCASECMP strncasecmp
#endif /* WIN32 */
#ifndef FALSE
#define FALSE 0
#endif
#ifndef TRUE
#define TRUE 1
#endif
#ifdef __cplusplus
extern "C" {
#endif
void attrlist_dump(azn_attrlist_h_t);
void check_status(char*, unsigned);
int readVar(const char*, char*, int);
static const char * HASH_ATTRIBUTE = "fingerprint";
/*
* Ignore_unauth will be set to true if we should not attempt to update
* the credentials of unauthenticated users.
*/
int ignore_unauth = FALSE;
/*
* Dump_cred will be set to true if we should print out the contents of
* the credential before asking for new attributes.
*/
int dump_cred = FALSE;
FILE *stream;
/******************************************************************************
* Interface function definitions. *
******************************************************************************/
/**
* FUNCTION NAME
* azn_svc_initialize
*
* DESCRIPTION
* init the credattrs entitlements service
*
* ARGUMENTS
* [in] argc The count of arguments to the service.
* [in] argv The array of argument strings.
* [in] svcInit List of initialization attributes for the service.
* [out] svcInfo attr list ptr for attributes returned by the service.
*
* RETURN VALUE
* AZN_S_COMPLETE on success, error code on failure
*/
AZN_DECLSPEC
azn_status_t
AZN_CALLTYPE
azn_svc_initialize(const int argc, /* in */
const char **argv, /* in */
const azn_attrlist_h_t svcInit, /* in */
azn_attrlist_h_t *svcInfo /* out */
)
{
azn_status_t status = AZN_S_COMPLETE;
azn_string_t svcid = NULL;
azn_string_t azn_code_set = NULL;
azn_string_t conf_file = NULL;
azn_string_t domain = NULL;
azn_boolean_t freeAttrlist = FALSE;
int i=0;
//Log file
fopen_s(&stream, "azn_ent_svc_hash.log", "a+" );
fprintf_s(stream,"Entitlement Service init...\n");
//If not arguments return error
if (svcInfo == NULL)
{
return(azn_util_errcode(AZN_S_SVC_ENT_INVALID_SVCINFO_HDL, 0));
}
//If invalid arguments return error
if ((argc < 0) || (argc == 0 && argv != NULL))
{
return(azn_util_errcode(AZN_S_SVC_ENT_INVALID_ARG_COUNT, 0));
}
//If inconsistent arguments return error
if ((argc > 0) && (argv == NULL))
{
return(azn_util_errcode(AZN_S_SVC_ENT_INVALID_ARG_COUNT, 0));
}
//Get service id - defined in TAM config file
status = azn_attrlist_get_entry_string_value(svcInit, azn_svc_init_my_service_id, AZN_C_INITIATOR_INDEX, &svcid);
if (status == AZN_S_COMPLETE || svcid != NULL)
{
fprintf_s(stream,"Entitlement Service ID: %s\n", svcid);
azn_release_string(&svcid);
}
else
{
return(azn_util_errcode(status, 0));
}
//Get conf file if there is one (you can define your own entitlement service config file)
status = azn_attrlist_get_entry_string_value(svcInit, azn_init_cfg_file, AZN_C_INITIATOR_INDEX, &conf_file);
if (status == AZN_S_COMPLETE || conf_file != NULL)
{
fprintf_s(stream,"Configuration File: %s\n", conf_file);
azn_release_string(&conf_file);
}
//Get codepage
status = azn_attrlist_get_entry_string_value(svcInit, azn_svc_init_code_set, AZN_C_INITIATOR_INDEX, &azn_code_set);
if (status == AZN_S_COMPLETE || azn_code_set != NULL)
{
fprintf_s(stream,"Entitlement Service Codepage: %s\n", azn_code_set);
azn_release_string(&azn_code_set);
}
//Get the local domain this app is configured into
//if the attribute is not set then assume NULL which means Default
status = azn_attrlist_get_entry_string_value(svcInit, azn_init_ssl_local_domain, AZN_C_INITIATOR_INDEX, &domain);
if (status == AZN_S_COMPLETE || domain != NULL)
{
fprintf_s(stream,"Entitlement Service Domain: %s\n", domain);
azn_release_string(&domain);
}
//Return the service version to the dispatcher
if (*svcInfo == AZN_C_INVALID_HANDLE)
{
azn_attrlist_create(svcInfo);
freeAttrlist = TRUE;
}
//Add this version info to svcInfo
status = azn_attrlist_add_entry(*svcInfo, (const azn_string_t)azn_svc_version, (const azn_string_t)ENT_SVC_DEMO_VER);
if (status != AZN_S_COMPLETE)
{
if (freeAttrlist)
{
azn_attrlist_delete(svcInfo);
}
fprintf_s(stream,"Entitlement Service version error\n");
}
fclose(stream);
return(azn_util_errcode(AZN_S_COMPLETE, 0));
}
/**
* FUNCTION NAME
* azn_svc_shutdown
*
* DESCRIPTION
* shutdown the ext_attr entitlements service.
* The initialization parameters are passed in
* again on shutdown but are ignored. No version info
* is returned.
*
* ARGUMENTS
* [in] argc The count of arguments to the service.
* [in] argv The array of argument strings.
* [in] svcInit List of initialization attributes for the service.
* [out] svcInfo attr list ptr for attributes returned by the service.
*
* RETURN VALUE
* AZN_S_COMPLETE on success, error code on failure
*/
AZN_DECLSPEC
azn_status_t
AZN_CALLTYPE
azn_svc_shutdown(const int argc, // in
const char **argv, // in
const azn_attrlist_h_t svcInit, // in
azn_attrlist_h_t *svcInfo // out
)
{
//Log file
fopen_s(&stream, "azn_ent_svc_hash.log", "a+" );
fprintf_s(stream,"Entitlement Service shutdown...\n");
fclose(stream);
return(azn_util_errcode(AZN_S_COMPLETE, 0));
}
/**
* FUNCTION NAME
* azn_svc_entitlement_get_entitlements
*
* DESCRIPTION
* Returns extended attribute information associated with
* the cred passed in
*
* ARGUMENTS
* [in] creds The credentials of the caller
* [in] svc_id The id of the entitlements service
* [in] app_context attribute list containing information
* on which extended attributes to get
* [out] entitlements attribute list containing the extended
* attributes
*
* RETURN VALUE
* AZN_S_COMPLETE on success, error code on failure
*
*
*/
AZN_DECLSPEC
azn_status_t
AZN_CALLTYPE
azn_svc_entitlement_get_entitlements(
const azn_creds_h_t creds, /* in */
const azn_string_t svc_id, /* in */
const azn_attrlist_h_t app_context, /* in */
azn_attrlist_h_t *entitlements) /* out */
{
azn_status_t status = AZN_S_COMPLETE;
unsigned num = 0;
unsigned numval = 0;
int i = 0;
int j = 0;
azn_attrlist_h_t cred_attrs = AZN_C_INVALID_HANDLE;
azn_string_t attr_name = NULL;
azn_string_t auth_type = NULL;
azn_boolean_t free_enthdl = FALSE;
azn_string_t attr_id = NULL;
azn_boolean_t done = FALSE;
char hash_value[MAX_ATTR_LENGTH];
//Log file
fopen_s(&stream, "azn_ent_svc_hash.log", "a+" );
fprintf_s(stream,"\n%s\n", ENT_SVC_DEMO_VER);
//Check that we dont have an invalid handle
if (entitlements == NULL)
{
fprintf_s(stream,"Error invalid handle.\n");
fclose(stream);
return(azn_util_errcode(AZN_S_INVALID_ENTITLEMENTS_HDL, 0));
}
fprintf_s(stream,"Service ID: %s\n\n", svc_id);
if (*entitlements == AZN_C_INVALID_HANDLE)
{
status = azn_attrlist_create(entitlements);
if (status != AZN_S_COMPLETE)
{
fprintf_s(stream,"Error reading entitlements.\n");
fclose(stream);
return(status);
}
free_enthdl = TRUE;
}
//Bypass if unauth users
if (ignore_unauth)
{
// Check to see whether the user is unauthenticated, and leave quickly if they are
status = azn_creds_get_attr_value_string(creds, AZN_C_INITIATOR_INDEX, azn_cred_mech_id, &auth_type);
check_status((char*)"azn_creds_get_attr_value_string", status);
if (status == AZN_S_COMPLETE && strcmp(auth_type, IV_UNAUTH) == 0)
{
azn_release_string(&auth_type);
fprintf_s(stream,"Unauthenticated user, skipping adding extended attributes.\n");
fclose(stream);
return(AZN_S_COMPLETE);
}
}
//Display credentials info
if (dump_cred)
{
/* Display the attrlist for the principal */
status = azn_creds_get_attrlist_for_subject(creds, AZN_C_INITIATOR_INDEX, &cred_attrs);
check_status((char*)"azn_creds_get_attrlist_for_subject", status);
if (status == AZN_S_COMPLETE)
{
fprintf_s(stream,"[User Credential Attributes]\n");
attrlist_dump(cred_attrs);
azn_attrlist_delete(&cred_attrs);
fclose(stream);
fopen_s(&stream, "azn_ent_svc_hash.log", "a+" );
}
}
//If the app_context contains stuff then prompt to enter the attribute value
//otherwise, prompt to enter attribute name and value.
if (app_context != AZN_C_INVALID_HANDLE)
{
//Look for azn_perminfo_rules_adi_request
attr_id = azn_perminfo_rules_adi_request;
status = azn_attrlist_name_get_num(app_context, attr_id, &num);
//App_context is valid and non-empty handle
if (status == AZN_S_COMPLETE && num > 0)
{
fprintf_s(stream,"[Application Context Attributes]\n");
attrlist_dump(app_context);
//Scan all attributes values and look for the attr-value _'myhash'
for (i = 0; i < num && !done ; i++)
{
status = azn_attrlist_get_entry_string_value(app_context, attr_id, i, &attr_name);
if (status != AZN_S_COMPLETE)
{
break;
}
else
{
//If known attribute, we call the hash function to generate the hash value. The
//function use the URL string parametrs and a shared key to calculate the value.
//For simplicity we hardcode the result instead of calling the funcion.
if (strcmp( attr_name, HASH_ATTRIBUTE ) == 0)
{
fprintf_s(stream,"\nCalling hash function...\n\n");
hash_value[0]=(char)NULL;
strcpy(hash_value, "hashvalue\0");
status = azn_attrlist_add_entry(*entitlements, (const azn_string_t) attr_name, (const azn_string_t) hash_value);
check_status((char *)"azn_attrlist_add_entry", status);
if (status != AZN_S_COMPLETE)
{
done = TRUE;
break;
}
}
}
azn_release_string(&attr_name);
}
if (status != AZN_S_COMPLETE)
{
if (free_enthdl)
{
azn_attrlist_delete(entitlements);
}
}
else
{
fprintf_s(stream,"[Entitlement Attributes]\n");
attrlist_dump(*entitlements);
}
fclose(stream);
return(status);
}
}
fclose(stream);
return(status);
}
/*
* check status and print a message.
*/
void check_status(char *info, unsigned status)
{
azn_string_t errstr = NULL;
unsigned majerr, minerr;
azn_status_t rc;
if (status == AZN_S_COMPLETE)
return;
majerr = azn_error_major(status);
minerr = azn_error_minor(status);
fprintf_s(stream,"\n%s: ", info);
rc = azn_error_get_string(status, &errstr);
if (rc == AZN_S_COMPLETE && errstr)
{
fprintf_s(stream,"%s (0x%08x/0x%08x)\n\n", errstr, majerr, minerr);
azn_release_string(&errstr);
}
else
fprintf_s(stream,"API failure (0x%08x/0x%08x)\n\n", majerr, minerr);
}
/*
* Prompt the user for a variable.
*/
int readVar(const char *prompt, char *var, int len)
{
char *startpos;
char *endpos;
char buffer[MAX_ATTR_LENGTH];
/* Display prompt */
fprintf_s(stream,"%s [%s]: ", prompt, var);
fflush(stdout);
/* Get user input */
if (fgets(buffer, len, stdin) == NULL)
return(FALSE);
buffer[len-1] = '\0';
/* Skip leading white space */
startpos = buffer;
while (*startpos && isspace(*startpos))
{
startpos++;
}
/* If empty, return the current value */
if (! *startpos)
{
return(TRUE);
}
/* Skip trailing white space */
endpos = startpos + strlen(startpos) - 1;
while (endpos != startpos && isspace(*endpos))
{
endpos--;
}
endpos[1] = '\0';
/* check for end of exit marker */
if (STRCASECMP(startpos, "exit") == 0)
return(FALSE);
/* Copy the user's input to return */
strcpy(var, startpos);
return(TRUE);
}
/*
* dump attributes to the standard output
*/
void attrlist_dump(azn_attrlist_h_t perminfo)
{
azn_string_t* attr_names;
int i ,j;
unsigned int count;
azn_status_t aznStatus;
azn_buffer_t buffer_value;
azn_string_t str_value;
azn_ulong_t ulong_value;
aznStatus = azn_attrlist_get_names(perminfo, &attr_names);
check_status((char *)"azn_attrlist_get_names", aznStatus);
if (attr_names)
{
for (i = 0; attr_names[i] != NULL ;i++)
{
aznStatus = azn_attrlist_name_get_num(perminfo,
attr_names[i], &count);
check_status((char *)"azn_attrlist_name_get_num", aznStatus);
fprintf_s(stream,"\n-----------------------------\n");
fprintf_s(stream,"Attr Name = %s\n" , attr_names[i]);
fprintf_s(stream,"Values are : \n");
for (j=0;j<count;j++)
{
aznStatus = azn_attrlist_get_entry_string_value(perminfo,attr_names[i],j,&str_value);
if (aznStatus == AZN_S_COMPLETE)
{
fprintf_s(stream,"%s\n", str_value);
azn_release_string(&str_value);
}
else
{
aznStatus = azn_attrlist_get_entry_buffer_value(perminfo,attr_names[i],j,&buffer_value);
if (aznStatus == AZN_S_COMPLETE)
{
fprintf_s(stream,"0x%08x\n" , buffer_value);
azn_release_buffer(&buffer_value);
}
else
{
aznStatus = azn_attrlist_get_entry_ulong_value(perminfo,attr_names[i],j,&ulong_value);
if (aznStatus == AZN_S_COMPLETE)
fprintf_s(stream,"%d\n" , ulong_value);
}
}
}
}
}
}
#ifdef __cplusplus
}
#endif
If using Microsoft Visual C++ 2005 Express Edition, you must follow these steps to compile the plug-in:
- Be sure you to have installed the following components:
Visual Studio Express 2005 (C++)
TAMeb’s Application Development Kit (ADK)
Microsoft Platform SDK 2003 SP1 or SDK 2005 Express
- The following libraries are required to compile the source code. For example if you have installed TAMeb’s ADK and Microsoft Visual C++ in your C drive:
C:\Tivoli\PolicyDirector\lib\pdauthzn.lib
C:\Program Files\Microsoft Platform SDK\Lib\kernel32.lib
C:\Program Files\Microsoft Visual Studio 8\VC\lib\msvcrt.lib
C:\Program Files\Microsoft Platform SDK\Include\crt
C:\Program Files\Microsoft Visual Studio 8\VC\lib\msvcmrt.lib
- Open a command prompt window by selecting Program Files -> Visual C++ 2005 Express Edition -> Visual Studio Tools -> Visual Studio 2005 Command Prompt
- Run the command SetEnvLaunchWinXP32Retail.cmd to set the variables used by the compiler
- Modify the sample makefile.in that comes with TAMeb ADK to compile you source file. Make sure to add the following value to parameter WINNT_4x_CFLAGS: /D_CRT_SECURE_NO_DEPRECATE
This will avoid warnings in depreciated functions like strcpy. Also replace msvcrt.lib with libcmt.lib to use runtime static library.
- If using strdup in your source code (.c file) replace it with _strdup.
- Run nmake makefile.in to compile and generate the plug-in (.dll file).
Step 2: Deploying the Plug-in
Once the plug-in is compiled, the resulting DLL file (azn_ent_svc_hash.dll) must be copied to folder C:TivoliPolicyDirectorbin (assuming that TAMeb is installed in drive C). For example:
C:\Tivoli\PolicyDirector\bin\azn_ent_svc_hash.dll
Add the following entries to ivmgrd.conf, ivacld.conf and webseald-default.conf files to register the entitlement service plug-in:
</pre>
[aznapi-configuration]
……
dynamic-adi-entitlement-services = AZN_ENT_SVC_HASH_ID
[aznapi-entitlement-services]
……
AZN_ENT_SVC_HASH_ID = azn_ent_svc_hash & -dump_cred
Step 3: Defining the Authorization Rule
Authorization rules are defined in XSLT format using TAMeb’s Web Portal Manager or the command line interface. Entitlement service plug-ins are automatically loaded when authorization rules are evaluated, so to invoke our plug-in we define a rule attached to the junction that points to the protected resource. For simplicity, the protected resource will be a local directory containing a sample text file.
1. Create a junction that points to the resource to be protected. Log in to TAMeb Web Portal Manager and select WebSEAL -> Create Junction, enter the following values:
WebSEAL Server Name: default-webseald-2gwin2k3
Junction Point: /testrules
Type: Local
Local Directory: C:/TestRules
2. Create an ACL to restrict access to the junction. Go to ACL -> Create ACL and enter the following values:
ACL Name: testrules-acl-ro
ACL Entries:
sec_master user Tc-mdbsvaB-R--l---[WP6]-
tuser user T------v----r-l---[WP6]-
tuser is a sample user that will be used to test the authorization rule and consequently the plug-in.
3. Create the authorization rule. Go to AuthzRule -> Create AuthzRule and enter the following values:
Name: validateURLstring
AuthxRule Text: <xsl:if test='AMWS_hd_user-agent != "" and AMWS_qs_param1 != "" and AMWS_qs_param2 != "" and AMWS_qs_param3 != "" and AMWS_qs_param4 = fingerprint'>!TRUE!</xsl:if>
Click on Create button to confirm the creation. AMWS_hd_user_agent and AMWS_qs_paramX are application context attributes (Browser) populated by TAMeb; fingerprint is the ADI attribute whose value will be resolved by the entitlement service plug-in.
4. Go to Object Space -> Browse Object Space and attach the authorization rule and ACL to the junction created in the first step. Log off from TAMeb Web Portal Manager.
Figure 2 – Attaching ACL and Auth-Rule to TAMeb junction using Web Portal Manager

Step 5: Testing the Plug-in
Testing the plug-in is as simple as submitting the URL requests to access the protected resource. In our case we access a text file that resides in a local folder protected by TAMeb. The URL string must contain among other parameters a hash value, calculated using the remaining parameters and a shared key known by the requestor and the plug-in only. The value is unique and can not be reproduced unless the key is compromised.
1. Submitting the URL request to access a text file:
https://2gwin2k3.iam.com/testrules/notes.txt?param1=data1¶m2=data2¶m3=data3¶m4=hashvalue
Figure 3 – URL request with valid signature succeeds in accessing protected resource

2. If an invalid hash value is provided TAMeb returns an error:
https://2gwin2k3.iam.com/testrules/notes.txt?param1=data1¶m2=data2¶m3=data3¶m4=othervalue
Figure 4 – URL request with invalid signature fails to access protected resource

Conclusion
The sample code in this article will give you a head start to develop plug-ins for the TAMeb’s authorization engine, however I strongly suggest to take a look to the following documents: