Recently we had a requirement to call a custom function module in SAP ECC system from a 3rd party non SAP application using SAP NW RFC SDK. Earlier it was pretty much simple process to call any function module in SAP via the startrfc kernel executable i.e. you can start any SAP function module, that is RFC-enabled. This program is part of SAP application server and of any SAP RFC SDK.
startrfc -3 -h <host> -s <instance number> -c <client> -l <language> -u <user> -p <password> -F <function module> -E <parameter=value>
RFC Clients calling Server FM
However in the last few years whenever you tried to call the startrfc to start a function module in backend system, you faced error like “Missing or invalid -F option” and the function module could not be started. This is due to new SAP Security limitations that now only allowed EDI_DATA_INCOMING and EDI_STATUS_INCOMING to be started by default. More information in the SAP Note 2010749 – Startrfc -F option restriction in NW RFC SDK
Our requirement was such that we had to call the custom FM so we decided on create a custom version of SAP Kernel file STARTRFC so that the original SAP Kernel file is not broken along with calls to standard SAP EDI FM EDI_DATA_INCOMING & EDI_STATUS_INCOMING. The standard STARTRFC program is designed to handle input parameter PATHNAME & PORT where as the custom FM was designed to accept 3 other parameters (routename, filename, uniqueid) so a copy of the STARTRFC C++ program was modified and recompiled and was placed into the kernel directory with name ZSTASRTRFC.
One challenge which arises from following this approach is that we need to ensure that in future when upgrades (Kernel upgrade, EHP upgrades, S/4 Migration etc.) happens, this custom Z file should not be deleted. Additionally customizing the original STARTRFC program itself was more complicated, error prone plus with every update (Kernel upgrade etc.), this file would be overwritten. So this approach was not taken.
Our custom Function module Z_*_UTILITY accepted 3 import parameters – IP_ROUTENAME, IP_FILENAME & IP_UNIQUEID. This FM was remote enabled
Custom FM
The standard startrfc c++ program was modified where the import parameters’ were added in place of the standard parameters. In addition to this the custom FM module (Z_*_UTILITY) was added to the program. One thing we noted is that the sequence of adding the import parameters in the c++ file should match the import parameter sequence in the function module.
#include "zstartrfc.h"
int mainU (int argc, SAP_UC ** argv)
{
OPTIONS options = {};
if(!parseCommand(argc, argv, &options))
return 0;
if(!checkOptions(&options))
{
showHelp();
return 1;
}
options->path = "/sapmnt/<SID>/exe/uc/linuxx86_64";
RFC_RC SAP_API RfcSetIniPath(options->path,errorInfo);
RFC_RC rc = startRfc(&options);
return rc;
}
bool parseCommand(int argc, SAP_UC ** argv, OPTIONS* options)
{
if( argc < 2 || !strcmpU(argv[1], cU("-help")) || !strcmpU(argv[1], cU("-?")))
{
showHelp();
return false;
}
else if(!strcmpU(argv[1], cU("-v")))
{
showVersion();
return false;
}
int i = 1;
const SAP_UC * const IP_FILENAME = cU("IP_FILENAME=");
const SAP_UC * const IP_ROUTENAME = cU("IP_ROUTENAME=");
const SAP_UC * const IP_UNIQUEID = cU("IP_UNIQUEID=");
const size_t FILENAME_LEN = 12;
const size_t ROUTENAME_LEN = 13;
const size_t UNIQUEID_LEN = 12;
while(i < argc)
{
const SAP_UC ch1 = argv[i][0];
const SAP_UC ch2 = argv[i++][1];
if(ch1 == cU('-') && ch2) // we found an option
{
if(ch2 == cU('i'))
{
options->showSysInfo = true;
continue;
}
if(i > argc - 1 || argv[i][0] == cU('-'))
{
continue;
}
switch (ch2)
{
case cU('h'):
options->ashost = argv[i++];
break;
case cU('s'):
options->sysnr = argv[i++];
break;
case cU('u'):
options->user = argv[i++];
break;
case cU('p'):
options->passwd = argv[i++];
break;
case cU('c'):
options->client = argv[i++];
break;
case cU('l'):
options->language = argv[i++];
break;
case cU('D'):
options->dest = argv[i++];
break;
case cU('F'):
options->function = argv[i++];
break;
case cU('E'):
{
const SAP_UC *param = argv[i++];
if(!strncmpU(param, IP_FILENAME, FILENAME_LEN))
{
options->filename = param + FILENAME_LEN;
}
else if(!strncmpU(param, IP_ROUTENAME, ROUTENAME_LEN))
{
options->routename = param + ROUTENAME_LEN;
}
else if(!strncmpU(param, IP_UNIQUEID, UNIQUEID_LEN))
{
options->uniqueid = param + UNIQUEID_LEN;
}
}
break;
case cU('t'):
options->trace = argv[i++];
break;
default:
i++;
break;
}
}
}
return true;
}
bool checkOptions(OPTIONS *options)
{
SAP_UC ch = cU('\0');
const SAP_UC * const EDI_DATA_INCOMING = cU("EDI_DATA_INCOMING");
const SAP_UC * const Z_*_UTILITY = cU("Z_*_UTILITY");
const SAP_UC * const EDI_STATUS_INCOMING = cU("EDI_STATUS_INCOMING");
const unsigned MAX_FILENAME_LEN = 255;
const unsigned MAX_ROUTENAME_LEN = 50;
const unsigned MAX_UNIQUEID_LEN = 50;
if(!options->dest)
{
if(!options->ashost )
ch = cU('h');
else if(!options->sysnr)
ch = cU('s');
else if(!options->user)
ch = cU('u');
else if(!options->passwd)
ch = cU('p');
else if(!options->client)
ch = cU('c');
if(ch)
{
printfU(cU("Missing or invalid -%c option.\n"), ch);
return false;
}
}
if(!options->showSysInfo)
{
if((!options->function) ||
(strcmpU(options->function,EDI_DATA_INCOMING) &&
strcmpU(options->function,EDI_STATUS_INCOMING) &&
strcmpU(options->function,Z_*_UTILITY)))
{
printfU(cU("Missing or invalid -F option.\n"));
return false;
}
if(!options->filename || !options->filename[0])
{
printfU(cU("Missing or invalid -E IP_FILENAME= option.\n"));
return false;
}
else if(strlenU(options->filename) > MAX_FILENAME_LEN)
{
printfU(cU("Filename specified by -E IP_FILENAME= excceeds the maximum length of 255. \n"));
return false;
}
if(!options->routename ||!options->routename[0] )
{
printfU(cU("Missing or invalid -E IP_ROUTENAME= option.\n"));
return false;
}
else if(strlenU(options->routename) > MAX_ROUTENAME_LEN)
{
printfU(cU("Route name specified by -E IP_ROUTENAME= excceeds the maximum length of 50. \n"));
return false;
}
if(!options->uniqueid ||!options->uniqueid[0] )
{
printfU(cU("Missing or invalid -E IP_UNIQUEID= option.\n"));
return false;
}
else if(strlenU(options->uniqueid) > MAX_UNIQUEID_LEN)
{
printfU(cU("UNIQUEID name specified by -E IP_UNIQUEID= excceeds the maximum length of 50. \n"));
return false;
}
}
return true;
}
RFC_RC startRfc(OPTIONS *options)
{
RFC_RC rc = RFC_OK;
RFC_ERROR_INFO error;
memsetR(&error, 0, sizeofR(RFC_ERROR_INFO));
RFC_CONNECTION_PARAMETER connParams[] = {
{cU("ashost"), options->ashost},
{cU("sysnr"), options->sysnr},
{cU("client"), options->client},
{cU("lang"), options->language ? options->language : cU("E")},
{cU("user"), options->user},
{cU("passwd"), options->passwd},
{cU("dest"), options->dest ? options->dest : cU("")},
{cU("trace"), options->trace}};
RFC_CONNECTION_HANDLE connHandle = RfcOpenConnection(connParams,
sizeofR(connParams) / sizeofR(RFC_CONNECTION_PARAMETER),
&error);
if(connHandle)
{
if(options->showSysInfo)
{
RFC_ATTRIBUTES attr;
rc = RfcGetConnectionAttributes(connHandle, &attr, &error);
showConnAttr(&attr);
}
else if(options->function)
{
RFC_FUNCTION_DESC_HANDLE funcDesc = getFunctionHandle(options->function);
RFC_FUNCTION_HANDLE funcHandle = RfcCreateFunction(funcDesc, 0);
RfcSetChars(funcHandle, cU("IP_ROUTENAME"), options->routename, (unsigned)strlenU(options->routename), 0);
RfcSetChars(funcHandle, cU("IP_FILENAME"), options->filename, (unsigned)strlenU(options->filename), 0);
RfcSetChars(funcHandle, cU("IP_UNIQUEID"), options->uniqueid, (unsigned)strlenU(options->uniqueid), 0);
rc = RfcInvoke(connHandle, funcHandle, &error);
}
if(RFC_OK == rc)
{
RfcCloseConnection(connHandle, &error);
return rc;
}
}
printfU(cU("Error: %s\n"), error.message);
return error.code;
}
RFC_FUNCTION_DESC_HANDLE getFunctionHandle(const SAP_UC* functionName)
{
RFC_PARAMETER_DESC parDescRoutename = { iU("IP_ROUTENAME"), RFCTYPE_CHAR, RFC_IMPORT, 50, 100, 0, 0, 0, 0, 0};
RFC_PARAMETER_DESC parDescFilename = { iU("IP_FILENAME"), RFCTYPE_CHAR, RFC_IMPORT, 255, 510, 0, 0, 0, 0, 0};
RFC_PARAMETER_DESC parDescUniqueid = { iU("IP_UNIQUEID"), RFCTYPE_CHAR, RFC_IMPORT, 50, 100, 0, 0, 0, 0, 0};
RFC_FUNCTION_DESC_HANDLE funcDesc = RfcCreateFunctionDesc(functionName, 0);
RfcAddParameter(funcDesc, &parDescRoutename, 0);
RfcAddParameter(funcDesc, &parDescFilename, 0);
RfcAddParameter(funcDesc, &parDescUniqueid, 0);
return funcDesc;
}
void showHelp( )
{
const SAP_UC * const programName = cU("startrfc");
printfU( cU("\nUsage: %s [options]\n"), programName );
printfU( cU("Options:\n") );
printfU( cU(" -h <ashost> SAP application server to connect to\n") );
printfU( cU(" -s <sysnr> system number of the target SAP system\n") );
printfU( cU(" -u <user> user\n") );
printfU( cU(" -p <passwd> password\n") );
printfU( cU(" -c <client> client \n") );
printfU( cU(" -l <language> logon language\n") );
printfU( cU(" -D <destination> destination defined in RFC config file sapnwrfc.ini\n") );
printfU( cU(" -F <function> function module to be called, only EDI_DATA_INCOMING\n") );
printfU( cU(" or EDI_STATUS_INCOMING or Z_MCA_MFT_UTILITY is supported\n") );
printfU( cU(" -E PATHNAME=<path> path, including file name, to EDI data file or status \n") );
printfU( cU(" file, with maximum length of 100 charachters\n") );
printfU( cU(" -E PORT=<port name> port name of the ALE/EDI interface with maximum \n") );
printfU( cU(" length of 10 charachters\n") );
printfU( cU(" -t <level> set RFC tracelevel 0(off), 1(brief), 2(verbose) or 3(full)\n") );
printfU( cU(" -help or -? display this help page\n") );
printfU( cU(" -v display the version of the NWRFC library, the version\n") );
printfU( cU(" of the compiler used by SAP to build this program and\n") );
printfU( cU(" the version of startrfc\n") );
printfU( cU(" -i connect to the target system and display the system info\n") );
}
void showConnAttr(RFC_ATTRIBUTES *attr)
{
if(!attr)
return;
printfU(cU("SAP System ID: %s\n"),attr->sysId);
printfU(cU("SAP System Number: %s\n"),attr->sysNumber);
printfU(cU("Partner Host: %s\n"),attr->partnerHost);
printfU(cU("Own Host: %s\n"),attr->host);
printfU(cU("Partner System Release: %s\n"),attr->partnerRel);
printfU(cU("Partner Kernel Release: %s\n"),attr->kernelRel);
printfU(cU("Own Release: %s\n"),attr->rel);
printfU(cU("Partner Codepage: %s\n"),attr->partnerCodepage);
printfU(cU("Own Codepage: %s\n"),attr->codepage);
printfU(cU("User: %s\n"),attr->user);
printfU(cU("Client: %s\n"),attr->client);
printfU(cU("Language: %s\n"),attr->language);
}
void showVersion()
{
printfU (cU("NW RFC Library Version: %s\n"), RfcGetVersion(NULL, NULL, NULL));
printfU (cU("Compiler Version:\n")
#if defined SAPonAIX
cU("%04X (VVRR)\n"), __xlC__
#elif defined SAPonHP_UX
cU("%06d (VVRRPP. %s Compiler)\n"), /*yes, decimal here!*/
#if defined __HP_cc
__HP_cc, cU("C")
#elif defined __HP_aCC
__HP_aCC, cU("C++")
#else
0, cU("Unknown Version")
#endif
#elif defined SAPonLINUX
cU("%s\n"), cU(__VERSION__)
#elif defined SAPonNT
cU("%09d (VVRRPPPPP. Microsoft (R) C/C++ Compiler)\n"), _MSC_FULL_VER /*decimal!*/
#elif defined SAPonSUN
cU("%03X (VRP. %s Compiler)\n"),
#if defined __SUNPRO_C
__SUNPRO_C, cU("C")
#elif defined __SUNPRO_CC
__SUNPRO_CC, cU("C++")
#else
0, cU("Unknown Version")
#endif
#elif defined SAPonOS390
cU("%08X (PVRRMMMM)\n"), __COMPILER_VER__
#else
cU("%s\n"), cU("Version not available.")
#endif
);
printfU (cU("Startrfc Version: 2018-08-15\n"));
}
Before compiling the custom c++ program, ensure that NW RFC SDK is in kernel folder & GCC rpm is installed for recompiling a new version of the STARTRFC program. Once the pre-requisites were in place, we proceeded with compiling and binding the file with SDK & Kernel with the below commands.
Bind with NW RFC SDK
Bind with Kernel
The above commands created the zstartrfc program which can be now placed in the kernel directory. Finally updated the custom application Java file to call ZSTARTRFC instead of STARTRFC
This process of creating the custom kernel executable now allowed to call the custom function module and bypass the restriction with startrfc.
Reference SAP Notes