/* ** Name: PCexec_suid - Execute a command as the ingres user. ** ** Description: ** This procedure works with the Ingres service to run the given ** command as the ingres user. It mimicks the "setuid" bit in UNIX. ** ** Inputs: ** cmdbuf - command to execute as the ingres user ** ** Outputs: ** none ** ** Returns: ** OK ** FAIL ** ** Side Effects: ** none ** ** History: ** 08-jan-1998 (somsa01) ** Created. ** 19-feb-1998 (somsa01) ** We need to pass to the service the current working directory ** as well. (Bug #89006) ** 25-feb-1998 (somsa01) ** We now have an input file for the process' stdin which ** runs through the OpenIngres service. ** 19-jun-1998 (somsa01) ** Use SYSTEM_PRODUCT_NAME for the name of the service. ** 10-jul-1998 (kitch01) ** Bug 91362. If user is 'system' run through OpenIngres service ** despite having access to server shared memory 'system' does not ** have required privilege to access semaphores/mutexes. ** 11-jun-1999 (somsa01) ** If the command is a startup command, then it is always run through ** the Ingres service. ** 03-nov-1999 (somsa01) ** A failure from ControlService() should be treated as a severe ** error which should not let us continue. ** 22-jan-2000 (somsa01) ** Return the exit code of the spawned process. Also, if the ** files exist, truncate them. The service name is now keyed off ** of II_INSTALLATION. ** 05-jun-2000 (somsa01) ** The Ingres installation may be started as the SYSTEM account, ** in which the 'ingres' user will not automatically have access ** to the shared memory segments. Therefore, even if the real ** user is 'ingres', check to see if he has access. ** 24-oct-2000 (somsa01) ** Removed the check on shared memory access. Access to the shared ** memory segment does not necessarily mean that the user running ** the process does not need to run the specified process as the ** Ingres owner. Also, generalized the check of the user with ** IDname_service(). ** 18-dec-2000 (somsa01) ** Modified the cases to run the command "as is" without the Ingres ** service. ** 20-mar-2002 (somsa01) ** If all is well, return the exit code of the child process that ** was executed. ** 29-mar-2002 (somsa01) ** Properly return the child process exit code. ** 11-apr-2003 (somsa01) ** While waiting for "pending" to not be set, give some CPU back ** to the OS. ** 29-Jul-2005 (drivi01) ** Allow user to run the command if he/she owns a shared ** segment and ingres is not running as a service. ** 06-Dec-2006 (drivi01) ** Adding support for Vista, Vista requires "Global\" prefix for ** shared objects as well. Replacing calls to GVosvers with ** GVshobj which returns the prefix to shared objects. ** Added PCadjust_SeDebugPrivilege to allow quering of ** System processes. ** 25-Jul-2007 (drivi01) ** On Vista, PCexec_suid is unable to use SE_DEBUG Privilege ** to query process status and retireve its exit code. ** The routine for monitoring a process and retrieving ** its exit code has been moved to Ingres Service. ** 05-Nov-2009 (wanfr01) b122847 ** Don't do a PCsleep unless you are waiting for more input */ STATUS PCexec_suid(char *cmdbuf) { EX_CONTEXT context; SERVICE_STATUS ssServiceStatus; LPSERVICE_STATUS lpssServiceStatus = &ssServiceStatus; struct SETUID setuid; DWORD ProcID; HANDLE SaveStdout; SECURITY_ATTRIBUTES sa; CHAR szRealUserID[25] = ""; CHAR *pszRealUserID = szRealUserID; CHAR szServiceUserID[25] = ""; CHAR *pszServiceUserID = szServiceUserID; DWORD BytesWritten, BytesRead = 0; CHAR *inst_id; CHAR SetuidShmName[64]; CHAR *temp_loc; CHAR InBuf[256], OutBuf[256]; static CHAR SetuidPipeName[32]; CL_ERR_DESC err_code; CHAR ServiceName[255]; DWORD ExitCode = 0; CHAR tchII_INSTALLATION[3]; BOOL SetuidDbCmd = FALSE, ServiceCommand = FALSE; int i, cmdlen; char *ObjectPrefix; u_i4 drType; SC_HANDLE schSCManager, OpIngSvcHandle; BOOL bServiceStarted = FALSE; if (EXdeclare(ex_handler, &context) != OK) { EXdelete(); PCexit(FAIL); } NMgtAt("II_INSTALLATION", &inst_id); STcopy(inst_id, tchII_INSTALLATION); /* ** See if this is a command that MUST be run through the Ingres ** service. */ cmdlen = (i4)STlength(cmdbuf); for (i = 0; ServiceCommands[i] ; i++) { if (STbcompare( cmdbuf, cmdlen, ServiceCommands[i], (i4)STlength(ServiceCommands[i]), FALSE ) == 0) { ServiceCommand = TRUE; break; } } /* ** If the user is the same as the user who started the Ingres ** service, just spawn the command. */ if (!ServiceCommand) { IDname(&pszRealUserID); if (!IDname_service(&pszServiceUserID) && STcompare(pszServiceUserID, pszRealUserID) == 0 && PCisAdmin()) { /* ** Attempt to just execute the command. */ return( PCcmdline( (LOCATION *) NULL, cmdbuf, PC_WAIT, (LOCATION *) NULL, &err_code) ); } else { /* ** If current user is not the same as service user and ingres is not ** running as a service, check if shared memory segment is owned ** by current user, if user has access to shared segment allow him ** to run the command. */ PTR shmem; SIZE_TYPE allocated_pages=0; STATUS status; if((status = MEget_pages(ME_MSHARED_MASK, 1, "lglkdata.mem", &shmem, &allocated_pages, &err_code)) == OK) { STprintf(ServiceName, "%s_Database_%s", SYSTEM_SERVICE_NAME, tchII_INSTALLATION); if ((schSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT)) != NULL) { if ((OpIngSvcHandle = OpenService(schSCManager, ServiceName, SERVICE_QUERY_STATUS)) != NULL) { if (QueryServiceStatus(OpIngSvcHandle,lpssServiceStatus)) { if (ssServiceStatus.dwCurrentState != SERVICE_STOPPED) bServiceStarted = TRUE; } } } if (!bServiceStarted) return(PCcmdline( (LOCATION *) NULL, cmdbuf, PC_WAIT, (LOCATION *) NULL, &err_code) ); } } /* ** See if this command is an Ingres command which needs to interact ** with at least one database. */ for (i = 0; validSetuidDbCmds[i] ; i++) { if (STbcompare( cmdbuf, cmdlen, validSetuidDbCmds[i], (i4)STlength(validSetuidDbCmds[i]), FALSE ) == 0) { SetuidDbCmd = TRUE; break; } } /* ** If the user has access to the Ingres shared memory segment, ** just spawn the command provided that it is not in the ** validSetuidDbCmds list. */ if (!SetuidDbCmd) { PTR shmem; SIZE_TYPE allocated_pages=0; STATUS status; if (((status = MEget_pages(ME_MSHARED_MASK, 1, "lglkdata.mem", &shmem, &allocated_pages, &err_code)) == OK) || (status == ME_NO_SUCH_SEGMENT)) { if (status != ME_NO_SUCH_SEGMENT) MEfree_pages(shmem, allocated_pages, &err_code); return( PCcmdline( (LOCATION *) NULL, cmdbuf, PC_WAIT, (LOCATION *) NULL, &err_code) ); } } } /* ** We must run the command through the Ingres service. */ if ( STstrindex(cmdbuf, "-silent", 0, FALSE ) ) SilentMode = TRUE; iimksec(&sa); GVshobj(&ObjectPrefix); STprintf(SetuidShmName, "%s%sSetuidShm", ObjectPrefix, tchII_INSTALLATION); if ( (SetuidShmHandle = OpenFileMapping(FILE_MAP_READ | FILE_MAP_WRITE, FALSE, SetuidShmName)) == NULL ) { error_exit(GetLastError()); return(FAIL); } if ( (SetuidShmPtr = MapViewOfFile(SetuidShmHandle, FILE_MAP_WRITE | FILE_MAP_READ, 0, 0, sizeof(struct SETUID_SHM))) == NULL ) { error_exit(GetLastError()); return(FAIL); } /* Set up the information to send to the service. */ STcopy(cmdbuf, setuid.cmdline); GetCurrentDirectory(sizeof(setuid.WorkingDirectory), setuid.WorkingDirectory); NMgtAt("II_TEMPORARY", &temp_loc); drType = GetDriveType(NULL); if (drType == DRIVE_REMOTE) { STcopy(temp_loc, setuid.WorkingDirectory); } SaveStdout = GetStdHandle(STD_OUTPUT_HANDLE); CVla(GetCurrentProcessId(), setuid.ClientProcID); STprintf(SetuidPipeName, "\\\\.\\PIPE\\INGRES\\%s\\SETUID", inst_id); /* Set up the stdout file for the command. */ STprintf(OutfileName, "%s\\%sstdout.tmp", temp_loc, setuid.ClientProcID); if ( (OutFile = CreateFile(OutfileName, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, &sa, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL)) == INVALID_HANDLE_VALUE ) { error_exit(GetLastError()); return(FAIL); } /* Set up the stdin file for the command. */ STprintf(InfileName, "%s\\%sstdin.tmp", temp_loc, setuid.ClientProcID); if ( (InFile = CreateFile(InfileName, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, &sa, CREATE_ALWAYS, FILE_FLAG_WRITE_THROUGH, NULL)) == INVALID_HANDLE_VALUE ) { error_exit(GetLastError()); return(FAIL); } /* Wait until the service is ready to process our request. */ while (SetuidShmPtr->pending == TRUE) PCsleep(100); SetuidShmPtr->pending = TRUE; /* Trigger the "setuid" event of the service. */ if ( (schSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT)) == NULL) { error_exit(GetLastError()); return(FAIL); } STprintf(ServiceName, "%s_Database_%s", SYSTEM_SERVICE_NAME, tchII_INSTALLATION ); OpIngSvcHandle = OpenService(schSCManager, ServiceName, SERVICE_USER_DEFINED_CONTROL); if (OpIngSvcHandle == NULL) { STprintf(ServiceName, "%s_DBATools_%s", SYSTEM_SERVICE_NAME, tchII_INSTALLATION ); OpIngSvcHandle = OpenService(schSCManager, ServiceName, SERVICE_USER_DEFINED_CONTROL); } if ( OpIngSvcHandle == NULL) { error_exit(GetLastError()); return(FAIL); } if (!ControlService(OpIngSvcHandle, RUN_COMMAND_AS_INGRES, lpssServiceStatus)) { error_exit(GetLastError()); CloseServiceHandle(schSCManager); return(FAIL); } WaitNamedPipe(SetuidPipeName, NMPWAIT_WAIT_FOREVER); /* Send the information to the service. */ if ( (Setuid_Handle = CreateFile(SetuidPipeName, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE, &sa, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL)) == INVALID_HANDLE_VALUE ) { error_exit(GetLastError()); return(FAIL); } if (!WriteFile(Setuid_Handle, &setuid, sizeof(struct SETUID), &BytesWritten, NULL)) { error_exit(GetLastError()); return(FAIL); } /* ** Retrieve information back from the service, and then ** disconnect from the pipe. */ if (!ReadFile(Setuid_Handle, &setuid, sizeof(struct SETUID), &BytesRead, NULL)) { error_exit(GetLastError()); return(FAIL); } ProcID = setuid.CreatedProcID; SetuidShmPtr->pending = FALSE; UnmapViewOfFile(SetuidShmPtr); SetuidShmPtr = NULL; CloseHandle(SetuidShmHandle); if ( (ProcID != -1) && (ProcID != -2) ) { /* ** Wait for the "spawned" process to exit, reading its output ** from the stdout file. */ for (;;) { if ( ((!ReadFile(OutFile, OutBuf, sizeof(OutBuf), &BytesRead, NULL) || (BytesRead == 0)) && setuid.ExitCode != STILL_ACTIVE )) break; if ( BytesRead && (!WriteFile(SaveStdout, OutBuf, BytesRead, &BytesWritten, NULL)) && setuid.ExitCode != STILL_ACTIVE) break; else if (BytesRead < sizeof(OutBuf)) PCsleep(200); /* ** Currently, the only DBA program which can require ** user input is verifydb. Therefore, when it spits out ** the appropriate messages asking for user input, get ** it from the end user and pass it along to the spawned ** process. */ if ( (STrstrindex(OutBuf, "S_DU04FF_CONTINUE_PROMPT", 0, FALSE) != NULL) || (STrstrindex(OutBuf, "S_DU0300_PROMPT", 0, FALSE) != NULL) ) { SIflush(stdout); MEfill(sizeof(OutBuf), ' ', &OutBuf); MEfill(sizeof(InBuf), ' ', &InBuf); SIgetrec(InBuf, 255, 0); WriteFile(InFile, InBuf, sizeof(OutBuf), &BytesWritten, NULL); } } ExitCode = setuid.ExitCode; CloseHandle(Setuid_Handle); CloseHandle(InFile); DeleteFile(InfileName); CloseHandle(OutFile); DeleteFile(OutfileName); CloseServiceHandle(OpIngSvcHandle); CloseServiceHandle(schSCManager); return(ExitCode); } else { error_exit(GetLastError()); return(FAIL); } }
STATUS open_log(char *testPrefix,char *testSufix,char *username,char *errbuff) { SYSTIME atime ; char uname [20] ; char logFileName [MAX_LOC+1] ; char timestr [TEST_LINE] ; char buffer [MAX_LOC+1] ; char *dot = NULL ; char *cptr = NULL ; char year[5]; if (shellMode) STpolycat(3, testPrefix, ERx("."), testSufix, logFileName); else if (updateMode) STpolycat(2, testPrefix, ERx(".upd"), logFileName); else STpolycat(2, testPrefix, ERx(".log"), logFileName); if (outputDir) { if (outputDir_type == PATH) { STcopy(outputDir, buffer); LOfroms(PATH, buffer, &logloc); LOfstfile(logFileName, &logloc); LOtos(&logloc, &cptr); } else { LOtos(&outLoc,&cptr); } STcopy(cptr, logname); } else STcopy(logFileName, logname); if (LOfroms(FILENAME & PATH, logname, &logloc) != OK) { STprintf(errbuff,ERx("ERROR: could not get location for log file")); return(FAIL); } if (SIopen(&logloc,ERx("w"),&logptr) != OK) { STprintf(errbuff,ERx("ERROR: could not open log file")); return(FAIL); } if (!updateMode) { append_line(ERx("/*"),1); copyright_year(&year[0]); STprintf(buffer, ERx("Copyright (c) %s Ingres Corporation"), &year); append_line(buffer, 1); append_line(ERx(" "), 1); STprintf(buffer,ERx("\tTest Name: %s.%s"), testPrefix, testSufix); append_line(buffer,1); TMnow(&atime); TMstr(&atime,timestr); STprintf(buffer,ERx("\tTime: %s"),timestr); append_line(buffer,1); dot = uname; if (username == NULL || *username == '\0') IDname(&dot); else STcopy(username,uname); STprintf(buffer,ERx("\tUser Name: %s"),uname); append_line(buffer,1); STprintf(buffer, ERx("\tTerminal type: %s"), terminalType); append_line(buffer,1); append_line(ERx(" "),1); } return(OK); }
STATUS gca_ns_resolve( char *target, char *user, char *password, char *rem_user, char *rem_pass, GCA_RQCB *rqcb ) { #ifdef GCF_EMBEDDED_GCN GCA_RQNS *ns = &rqcb->ns; GCN_RESOLVE_CB *grcb; char uid[ GC_USERNAME_MAX + 1 ]; char *puid = uid; char empty[ 1 ] = { '\0' }; STATUS status; i4 i; /* ** Allocate the embedded Name Server interface control block ** and save it in the request control block to provide storage ** for the resolved values. */ if ( ! rqcb->grcb && ! (rqcb->grcb = gca_alloc(sizeof(GCN_RESOLVE_CB))) ) return( E_GC0013_ASSFL_MEM ); grcb = (GCN_RESOLVE_CB *)rqcb->grcb; /* ** Default local username and password if required. ** Only use the provided info if both a username and ** password are provided. */ IDname( &puid ); STzapblank( uid, uid ); if ( ! user ) user = empty; if ( ! password ) password = empty; if ( ! user[0] || ! password[ 0 ] ) { user = uid; password = empty; } /* ** Validate username and password if attempt is ** made to change the user's ID. This would ** normally be done in the Name Server GCA_LISTEN. */ if ( user != uid && STcompare( user, uid ) ) { CL_ERR_DESC sys_err; status = GCusrpwd( user, password, &sys_err ); if ( status != OK ) return( status ); } /* ** Setup input parameters to Name Database interface. */ STcopy( target, grcb->target ); STcopy( user, grcb->user ); if ( rem_user && rem_pass ) { /* ** Use the requested remote username and password. */ grcb->gca_message_type = GCN_NS_2_RESOLVE; STcopy( rem_user, grcb->user_in ); STcopy( rem_pass, grcb->passwd_in ); } else { /* ** Use the local username and password. */ grcb->gca_message_type = GCN_NS_RESOLVE; STcopy( user, grcb->user_in ); STcopy( password, grcb->passwd_in ); } /* ** Call embedded Name Server to resolve the target. */ if ( (status = gcn_rslv_parse( grcb )) == OK && (status = gcn_rslv_server( grcb )) == OK && (status = gcn_rslv_node( grcb )) == OK && (status = gcn_rslv_login( grcb )) == OK ) { #ifdef WIN16 gcpasswd_prompt(grcb->user_out, grcb->passwd_out, grcb->vnode); #endif /* WIN16 */ /* ** Return resolved info. The new partner ID should ** have the vnode removed, but still retain the server ** class. We use the grcb as static storage to rebuild ** the partner ID from the dbname and class components. */ ns->svr_class = grcb->class ? grcb->class : ""; ns->username = grcb->user_out ? grcb->user_out : ""; ns->password = grcb->passwd_out ? grcb->passwd_out : ""; ns->rmt_dbname = grcb->target; STprintf( grcb->target, "%s%s%s", grcb->dbname ? grcb->dbname : "", ns->svr_class[0] ? "/" : "", ns->svr_class ); for( i = 0, ns->lcl_count = grcb->catl.tupc; i < ns->lcl_count; i++ ) ns->lcl_addr[ i ] = grcb->catl.tupv[ i ]; for( i = 0, ns->rmt_count = grcb->catr.tupc; i < ns->rmt_count; i++ ) { char *pv[3]; gcu_words( grcb->catr.tupv[ i ], NULL, pv, ',', 3 ); ns->node[ i ] = pv[ 0 ]; ns->protocol[ i ] = pv[ 1 ]; ns->port[ i ] = pv[ 2 ]; } if ( ! ns->lcl_count ) status = E_GC0021_NO_PARTNER; }
II_EXTERN IIAPI_MSG_BUFF * IIapi_createGCNOper( IIAPI_STMTHNDL *stmtHndl ) { IIAPI_MSG_BUFF *msgBuff; u_i1 *msg; char *str, temp[ 512 ]; i4 val; IIAPI_CONNHNDL *connHndl = IIapi_getConnHndl((IIAPI_HNDL *)stmtHndl); API_PARSE *parse = (API_PARSE *)stmtHndl->sh_queryText; IIAPI_TRACE( IIAPI_TR_VERBOSE ) ( "IIapi_createGCNOper: creating GCN Oper message\n" ); if ( ! connHndl ) { IIAPI_TRACE( IIAPI_TR_FATAL ) ( "IIapi_createGCNOper: can't get connHndl from stmtHnd\n" ); return( NULL ); } if ( ! (msgBuff = IIapi_allocMsgBuffer( (IIAPI_HNDL *)connHndl )) ) return( NULL ); msgBuff->msgType = GCN_NS_OPER; msg = msgBuff->data + msgBuff->length; /* ** Set the operations flags. ** ** Netutil always sets the merge flag when adding ** nodes, so we will too. ** ** The user ID flag is set if a username was given ** for IIapi_connect() but no password. The Name ** Server will reject the request if current user ** not authorized. ** ** The public flag is set for global values. ** ** The network database flag is set for non-server ** requests. ** ** We currently don't support the sole server flag. ** The merge flag is only marginally supported. ** These flags are mostly used for adding servers, ** which is currently only supported as an ** undocumented feature. */ val = GCN_DEF_FLAG; /* gcn_flag */ if ( parse->opcode == API_KW_ADD && ( parse->object == API_KW_NODE || parse->object == API_KW_ATTR ) ) val |= GCN_MRG_FLAG; if ( connHndl->ch_username && ! connHndl->ch_password ) val |= GCN_UID_FLAG; if ( parse->type == API_KW_GLOBAL ) val |= GCN_PUB_FLAG; if ( parse->object != API_KW_SERVER ) val |= GCN_NET_FLAG; I4ASSIGN_MACRO( val, *msg ); msg += sizeof( i4 ); msgBuff->length += sizeof( i4 ); switch( parse->opcode ) /* gcn_opcode */ { case API_KW_ADD : val = GCN_ADD; break; case API_KW_DEL : val = GCN_DEL; break; case API_KW_GET : val = GCN_RET; break; default : /* This should not happen! */ IIAPI_TRACE( IIAPI_TR_FATAL ) ( "IIapi_createGCNOper: invalid operations code.\n" ); val = GCN_RET; break; } I4ASSIGN_MACRO( val, *msg ); msg += sizeof( i4 ); msgBuff->length += sizeof( i4 ); /* ** Installation ID is currently ignored. */ val = STlength( install_id ) + 1; /* gcn_install.gcn_l_item */ I4ASSIGN_MACRO( val, *msg ); msg += sizeof( i4 ); msgBuff->length += sizeof( i4 ); MEcopy( install_id, val, msg ); /* gcn_install.gcn_value */ msg += val; msgBuff->length += val; val = 1; /* gcn_tup_cnt */ I4ASSIGN_MACRO( val, *msg ); msg += sizeof( i4 ); msgBuff->length += sizeof( i4 ); /* ** Node and connection operations have fixed types ** defined by GCN. For server operations, the server ** class is used as provided by the application. */ switch( parse->object ) { case API_KW_NODE : str = GCN_NODE; break; case API_KW_LOGIN : str = GCN_LOGIN; break; case API_KW_ATTR : str = GCN_ATTR; break; case API_KW_SERVER : str = ns_resolve_param( parse, API_FIELD_OBJ, FALSE ); break; default : /* Should not happen! */ IIAPI_TRACE( IIAPI_TR_TRACE ) ( "IIapi_createGCNOper: invalid object.\n" ); str = empty; break; } /* ** Build the GCN tuple. */ val = STlength( str ) + 1; /* gcn_type.gcn_l_item */ I4ASSIGN_MACRO( val, *msg ); msg += sizeof( i4 ); msgBuff->length += sizeof( i4 ); MEcopy( str, val, msg ); /* gcn_type.gcn_value */ msg += val; msgBuff->length += val; /* ** Use username if specified. This will ** either be the username we are connected ** with (if password also provided) or it ** will be the username for the GCN_UID_FLAG ** which requires special privileges for the ** current user. Otherwise, we use the user ** ID of the current process. */ if ( connHndl->ch_username ) str = connHndl->ch_username; else { if ( ! uid[0] ) { IDname( &puid ); STzapblank( uid, uid ); } str = uid; } val = STlength( str ) + 1; /* gcn_uid.gcn_l_item */ I4ASSIGN_MACRO( val, *msg ); msg += sizeof( i4 ); msgBuff->length += sizeof( i4 ); MEcopy( str, val, msg ); /* gcn_uid.gcn_value */ msg += val; msgBuff->length += val; str = ns_resolve_param( parse, API_FIELD_VNODE, (parse->opcode != API_KW_ADD) ); val = STlength( str ) + 1; /* gcn_obj.gcn_l_item */ I4ASSIGN_MACRO( val, *msg ); msg += sizeof( i4 ); msgBuff->length += sizeof( i4 ); MEcopy( str, val, msg ); /* gcn_obj.gcn_value */ msg += val; msgBuff->length += val; /* ** The tuple value must be built up from various parameters ** and can be function and object dependent. Call appropriate ** function for the current request to process the parameters. ** ** Pass in our temp buffer. It will be used if large enough ** or a different buffer will be allocated and returned. Check ** for memory allocation failures and free the returned buffer ** if it is not the one passed in (after copying the value). */ if ( ! (str = (*parse->parms->parms)( parse, sizeof( temp ), temp )) ) { IIapi_freeMsgBuffer( msgBuff ); return( NULL ); } val = STlength( str ) + 1; /* gcn_val.gcn_l_item */ I4ASSIGN_MACRO( val, *msg ); msg += sizeof( i4 ); msgBuff->length += sizeof( i4 ); MEcopy( str, val, msg ); /* gcn_val.gcn_value */ msgBuff->length += val; if ( str != temp ) MEfree( (PTR)str ); msgBuff->flags = IIAPI_MSG_EOD; return( msgBuff ); }