windows-nt/Source/XPSP1/NT/printscan/print/spooler/localspl/net.c
2020-09-26 16:20:57 +08:00

936 lines
24 KiB
C

/*++
Copyright (c) 1990 - 1995 Microsoft Corporation
Module Name:
net.c
Abstract:
This module provides all the network stuuf for localspl
Author:
Dave Snipp (DaveSn) 15-Mar-1991
Notes:
We just need to get the winspool printer name associated with a given
queue name. The SHARE_INFO_2 structure has a shi2_path field that would
be nice to use, but NetShareGetInfo level 2 is privileged. So, by
DaveSn's arm twisting and agreement with windows/spooler/localspl/net.c,
we're going to use the shi1_remark field for this. This allows us to
do NetShareGetInfo level 1, which is not privileged.
This has been fixed by allowing OpenPrinter to succeed on share names.
If there is no comment, we put the printer name in as the remark
(for graceful upgrades from pre-PPC).
Revision History:
02-Sep-1992 JohnRo
RAID 3556: DosPrintQGetInfo(from downlevel) level 3, rc=124. (4&5 too.)
Jun 93 mattfe pIniSpooler
--*/
#define UNICODE 1
#define NOMINMAX
#include <precomp.h>
#pragma hdrstop
#include "clusspl.h"
#ifdef DBG_SHARE
#include <messages.h>
#endif
NET_API_STATUS (*pfnNetShareAdd)();
NET_API_STATUS (*pfnNetShareSetInfo)();
NET_API_STATUS (*pfnNetShareDel)();
NET_API_STATUS (*pfnNetServerEnum)();
NET_API_STATUS (*pfnNetWkstaUserGetInfo)();
NET_API_STATUS (*pfnNetApiBufferFree)();
NET_API_STATUS (*pfnNetAlertRaiseEx)();
NET_API_STATUS (*pfnNetShareGetInfo)(LPWSTR, LPWSTR, DWORD, LPBYTE *);
extern SHARE_INFO_2 PrintShareInfo;
extern SHARE_INFO_2 PrtProcsShareInfo;
BOOL
InitializeNet(
VOID
)
{
HANDLE hNetApi;
UINT uOldErrorMode = SetErrorMode(SEM_FAILCRITICALERRORS);
hNetApi = LoadLibrary(TEXT("netapi32.dll"));
SetErrorMode(uOldErrorMode);
if ( !hNetApi )
return FALSE;
pfnNetShareAdd = (NET_API_STATUS (*)()) GetProcAddress(hNetApi,"NetShareAdd");
pfnNetShareSetInfo = (NET_API_STATUS (*)()) GetProcAddress(hNetApi,"NetShareSetInfo");
pfnNetShareDel = (NET_API_STATUS (*)()) GetProcAddress(hNetApi,"NetShareDel");
pfnNetServerEnum = (NET_API_STATUS (*)()) GetProcAddress(hNetApi,"NetServerEnum");
pfnNetWkstaUserGetInfo = (NET_API_STATUS (*)()) GetProcAddress(hNetApi,"NetWkstaUserGetInfo");
pfnNetApiBufferFree = (NET_API_STATUS (*)()) GetProcAddress(hNetApi,"NetApiBufferFree");
pfnNetAlertRaiseEx = (NET_API_STATUS (*)()) GetProcAddress(hNetApi,"NetAlertRaiseEx");
pfnNetShareGetInfo = (NET_API_STATUS (*)(LPWSTR, LPWSTR, DWORD, LPBYTE *))GetProcAddress(hNetApi, "NetShareGetInfo");
if ( pfnNetShareAdd == NULL ||
pfnNetShareSetInfo == NULL ||
pfnNetShareDel == NULL ||
pfnNetServerEnum == NULL ||
pfnNetWkstaUserGetInfo == NULL ||
pfnNetApiBufferFree == NULL ||
pfnNetAlertRaiseEx == NULL ||
pfnNetShareGetInfo == NULL) {
return FALSE;
}
return TRUE;
}
BOOL
SetPrinterShareInfo(
PINIPRINTER pIniPrinter
)
/*++
Routine Description:
Sets the share information about a printer.
Note: This does not update the share path. We need to
delete and re-create the share in order to change the path.
Arguments:
pIniPrinter - Printer that needs to be updated.
Return Value:
TRUE - Success
FALSE - Failed
--*/
{
SHARE_INFO_502 ShareInfo;
HANDLE hToken;
PSECURITY_DESCRIPTOR pShareSecurityDescriptor = NULL;
DWORD ParmError, rc;
SplInSem();
pShareSecurityDescriptor = MapPrinterSDToShareSD(pIniPrinter->pSecurityDescriptor);
if ( !pShareSecurityDescriptor ) {
rc = ERROR_INVALID_SECURITY_DESCR;
goto Cleanup;
}
ZeroMemory((LPVOID)&ShareInfo, sizeof(ShareInfo));
ShareInfo.shi502_netname = pIniPrinter->pShareName;
ShareInfo.shi502_type = STYPE_PRINTQ;
ShareInfo.shi502_permissions = 0;
ShareInfo.shi502_max_uses = SHI_USES_UNLIMITED;
ShareInfo.shi502_current_uses = SHI_USES_UNLIMITED;
ShareInfo.shi502_passwd = NULL;
ShareInfo.shi502_security_descriptor = pShareSecurityDescriptor;
if ( pIniPrinter->pComment && pIniPrinter->pComment[0] ) {
ShareInfo.shi502_remark = pIniPrinter->pComment;
} else {
ShareInfo.shi502_remark = pIniPrinter->pName;
}
INCPRINTERREF(pIniPrinter);
LeaveSplSem();
SplOutSem(); // We *MUST* be out of our semaphore as the NetShareSet may
// come back and call spooler
hToken = RevertToPrinterSelf();
rc = (*pfnNetShareSetInfo)(NULL,
ShareInfo.shi502_netname,
502,
&ShareInfo,
&ParmError);
if ( rc ) {
if (rc == NERR_NetNameNotFound)
{
//
// This can happen deny all access to a shared printer, then
// restart the computer. The server service tries to validate
// it's share on startup, but since it has no access, it fails
// and deletes it (it also wants a handle to the printer). When
// you grant everyone access, we try to change the ACL on the
// SMB share, but since it was deleted, we fail. Recreate
// the share here.
//
EnterSplSem();
if (!ShareThisPrinter(pIniPrinter,
pIniPrinter->pShareName,
TRUE)) {
rc = GetLastError();
} else {
rc = ERROR_SUCCESS;
}
LeaveSplSem();
}
if (rc) {
DBGMSG(DBG_WARNING,
("NetShareSetInfo/ShareThisPrinter failed: Error %d, Parm %d\n",
rc, ParmError));
SetLastError(rc);
}
}
ImpersonatePrinterClient(hToken);
EnterSplSem();
DECPRINTERREF(pIniPrinter);
Cleanup:
SplInSem();
return rc == ERROR_SUCCESS;
}
BOOL
ShareThisPrinter(
PINIPRINTER pIniPrinter,
LPWSTR pShareName,
BOOL bShare
)
/*++
Routine Description:
Shares or UnShares a Printer.
Note: this really should be two functions, and the return value
is very confusing.
Note: no validation of sharename is done. This must be done by
callee, usually in ValidatePrinterInfo.
Arguments:
Return Value:
Returns whether the printer is shared after this call.
TRUE - Shared
FALSE - Not Shared
--*/
{
SHARE_INFO_502 ShareInfo = {0};
DWORD rc;
DWORD ParmError;
PSECURITY_DESCRIPTOR pShareSecurityDescriptor = NULL;
PINISPOOLER pIniSpooler = pIniPrinter->pIniSpooler;
PSHARE_INFO_2 pShareInfo = (PSHARE_INFO_2)pIniSpooler->pDriversShareInfo;
LPTSTR pszPrinterNameWithToken = NULL;
HANDLE hToken;
BOOL bReturn = FALSE;
BOOL bSame = FALSE;
LPWSTR pShareNameCopy = NULL;
SPLASSERT( pIniPrinter->pName );
SplInSem();
pShareNameCopy = AllocSplStr(pShareName);
if (!pShareNameCopy) {
goto Done;
}
if ( bShare ) {
if (!pfnNetShareAdd) {
SetLastError( ERROR_PROC_NOT_FOUND );
goto Done;
}
//
// Share name validation has been moved into ValidatePrinterInfo.
//
if ((pShareSecurityDescriptor =
MapPrinterSDToShareSD(pIniPrinter->pSecurityDescriptor)) == NULL) {
SetLastError(ERROR_INVALID_SECURITY_DESCR);
goto Done;
}
ShareInfo.shi502_netname = pShareNameCopy; // hplaser
//
// If there is a comment, use it; otherwise set the remark
// to be the printer name.
//
// Note: if the printer name changes and we don't have a comment,
// we will reshare the printer to update the remark.
//
if( pIniPrinter->pComment && pIniPrinter->pComment[0] ){
ShareInfo.shi502_remark = pIniPrinter->pComment;
} else {
ShareInfo.shi502_remark = pIniPrinter->pName;
}
//
// Use the fully qualifed name, and make sure it exists in
// localspl by using LocalsplOnlyToken.
//
pszPrinterNameWithToken = pszGetPrinterName(
pIniPrinter,
pIniSpooler != pLocalIniSpooler,
pszLocalsplOnlyToken );
if( !pszPrinterNameWithToken ){
goto Done;
}
ShareInfo.shi502_path = pszPrinterNameWithToken;
ShareInfo.shi502_type = STYPE_PRINTQ;
ShareInfo.shi502_permissions = 0;
ShareInfo.shi502_max_uses = SHI_USES_UNLIMITED;
ShareInfo.shi502_current_uses = SHI_USES_UNLIMITED;
ShareInfo.shi502_passwd = NULL;
ShareInfo.shi502_security_descriptor = pShareSecurityDescriptor;
INCPRINTERREF(pIniPrinter);
LeaveSplSem();
//
// We *MUST* be out of our semaphore as the NetShareAdd is
// going to come round and call OpenPrinter.
//
SplOutSem();
// Go add the Print Share
hToken = RevertToPrinterSelf();
// Add a share for the spool\drivers directory:
if( rc = AddPrintShare( pIniSpooler )){
EnterSplSem();
DECPRINTERREF(pIniPrinter);
ImpersonatePrinterClient(hToken);
SetLastError(rc);
goto Done;
}
#if DBG
{
WCHAR UserName[256];
DWORD cbUserName=256;
if (MODULE_DEBUG & DBG_SECURITY)
GetUserName(UserName, &cbUserName);
DBGMSG( DBG_SECURITY, ( "Calling NetShareAdd in context %ws\n", UserName ) );
}
#endif
// Add the printer share:
rc = (*pfnNetShareAdd)(NULL, 502, (LPBYTE)&ShareInfo, &ParmError);
// Now take care of Web sharing. i.e. make sure wwwroot\sharename is created if the
// printer is either local or masqurading. We cannot allow sharing of RPC connections.
if( fW3SvcInstalled && (pIniSpooler->SpoolerFlags & SPL_TYPE_LOCAL))
WebShare( pShareNameCopy );
//
// If the return code is that the share already exists, then check to
// see whether this share is to the same device, if it is we just
// update the info on the share and return success.
//
if (rc == NERR_DuplicateShare) {
if (ERROR_SUCCESS == CheckShareSame(pIniPrinter, &ShareInfo, &bSame) && bSame) {
rc = ERROR_SUCCESS;
}
}
ImpersonatePrinterClient(hToken);
EnterSplSem();
DECPRINTERREF(pIniPrinter);
if (ERROR_SUCCESS != rc) {
DBGMSG( DBG_WARNING,
( "NetShareAdd failed %lx, Parameter %d\n",
rc, ParmError ));
if ((rc == ERROR_INVALID_PARAMETER) &&
(ParmError == SHARE_NETNAME_PARMNUM)) {
rc = ERROR_INVALID_SHARENAME;
}
SetLastError(rc);
goto Done;
}
SPLASSERT( pIniPrinter != NULL);
SPLASSERT( pIniPrinter->signature == IP_SIGNATURE);
SPLASSERT( pIniPrinter->pIniSpooler != NULL);
SPLASSERT( pIniPrinter->pIniSpooler->signature == ISP_SIGNATURE );
CreateServerThread();
//
// The Printer is shared.
//
bReturn = TRUE;
} else {
if ( !pfnNetShareDel ) {
bReturn = TRUE;
goto Done;
}
INCPRINTERREF( pIniPrinter );
LeaveSplSem();
SplOutSem();
hToken = RevertToPrinterSelf();
rc = (*pfnNetShareDel)(NULL, pShareName, 0);
// Now take care of Web unsharing. i.e. make sure wwwroot\sharename is deleted.
if( fW3SvcInstalled && (pIniSpooler->SpoolerFlags & SPL_TYPE_LOCAL) && !(pIniSpooler->SpoolerFlags & SPL_TYPE_CLUSTER))
WebUnShare( pShareName );
ImpersonatePrinterClient(hToken);
EnterSplSem();
DECPRINTERREF(pIniPrinter);
// The share may have been deleted manually, so don't worry
// if we get NERR_NetNameNotFound:
if ( rc && ( rc != NERR_NetNameNotFound )){
DBGMSG(DBG_WARNING, ("NetShareDel failed %lx\n", rc));
SetLastError( rc );
bReturn = TRUE;
}
}
Done:
if( pShareSecurityDescriptor ){
LocalFree(pShareSecurityDescriptor);
}
FreeSplStr(pszPrinterNameWithToken);
FreeSplStr(pShareNameCopy);
return bReturn;
}
/*--
FillAlertStructWithJobStrings
Allocates memory which has to be freed by caller.
--*/
HRESULT
FillAlertStructWithJobStrings(
PINIJOB pIniJob,
PRINT_OTHER_INFO **pAlertInfo,
PDWORD size
)
{
HRESULT RetVal = E_FAIL;
DWORD cbSizetoAlloc = 0;
PBYTE pBuffer = NULL;
LPWSTR psz = NULL;
if (pIniJob && pAlertInfo && !(*pAlertInfo) && size)
{
//
// Do this in the splSem so that no one can jo a SetJob While we're not looking
//
EnterSplSem();
//
// We don't know how big these strings are going to be in future, so size
// them dynamically and alloc the structure to always be big enough.
//
cbSizetoAlloc = sizeof(PRINT_OTHER_INFO);
//
// We don't have to check some of these for existence, we know they exist.
//
cbSizetoAlloc += wcslen(pIniJob->pNotify) + 1 +
wcslen(pIniJob->pIniPrinter->pName) + 1 +
wcslen(pIniJob->pIniPrinter->pIniSpooler->pMachineName) + 1;
if ( pIniJob->pDocument )
{
cbSizetoAlloc += wcslen(pIniJob->pDocument) + 1;
}
else
{
cbSizetoAlloc += 2;
}
if (pIniJob->pIniPrinter->pIniSpooler->bEnableNetPopupToComputer &&
pIniJob->pMachineName)
{
cbSizetoAlloc += wcslen(pIniJob->pMachineName) + 1;
}
else
{
cbSizetoAlloc += wcslen(pIniJob->pNotify) + 1;
}
if ( pIniJob->pStatus )
{
cbSizetoAlloc += wcslen(pIniJob->pStatus) + 1;
}
else
{
cbSizetoAlloc += 2;
}
//
// Scale the buffer by the number of bytes per character.
//
cbSizetoAlloc = cbSizetoAlloc * sizeof(TCHAR);
//
// Alloc the memory
//
pBuffer = AllocSplMem(cbSizetoAlloc);
if ( pBuffer )
{
psz = (LPWSTR)ALERT_VAR_DATA((PRINT_OTHER_INFO *)pBuffer);
//
// Do the copying
//
//
// Computer Name
//
if(pIniJob->pIniPrinter->pIniSpooler->bEnableNetPopupToComputer &&
pIniJob->pMachineName ){
wcscpy( psz, pIniJob->pMachineName );
} else {
wcscpy( psz, pIniJob->pNotify );
}
psz+=wcslen(psz)+1;
// UserName
wcscpy(psz, pIniJob->pNotify);
psz+=wcslen(psz)+1;
// Document Name
if (pIniJob->pDocument)
wcscpy(psz, pIniJob->pDocument);
else
wcscpy(psz, L"");
psz += wcslen(psz) + 1;
// Printer Name
wcscpy(psz, pIniJob->pIniPrinter->pName);
psz += wcslen(psz)+1;
// Server Name
wcscpy(psz, pIniJob->pIniPrinter->pIniSpooler->pMachineName);
psz += wcslen(psz)+1;
// Status_string
// We should pass in other status strings for the other status errors, too.
if (pIniJob->pStatus && (pIniJob->Status & (JOB_ERROR | JOB_OFFLINE | JOB_PAPEROUT)))
wcscpy(psz, pIniJob->pStatus);
else
wcscpy(psz, L"");
psz += wcslen(psz) + 1;
//
// Pass back the size and struct
//
*size = (DWORD)((PBYTE)psz - pBuffer);
*pAlertInfo = (PRINT_OTHER_INFO *)pBuffer;
RetVal = NOERROR;
}
else
{
RetVal = E_OUTOFMEMORY;
}
//
// Leave the Spooler Semaphore
//
LeaveSplSem();
}
else
{
RetVal = E_INVALIDARG;
}
return RetVal;
}
VOID
SendJobAlert(
PINIJOB pIniJob
)
{
PRINT_OTHER_INFO *pinfo = NULL;
DWORD RetVal = ERROR_SUCCESS;
DWORD Status;
FILETIME FileTime;
DWORD AlertSize = 0;
#ifdef _HYDRA_
// Allow Hydra Sessions to be notified since they are remote
if( (USER_SHARED_DATA->SuiteMask & (1 << TerminalServer)) ) {
if ( !pIniJob->pNotify ||
!pIniJob->pNotify[0] ||
!pIniJob->pIniPrinter->pIniSpooler->bEnableNetPopups ) {
return;
}
}
else {
if ( !pIniJob->pNotify ||
!pIniJob->pNotify[0] ||
!(pIniJob->Status & JOB_REMOTE) ||
!pIniJob->pIniPrinter->pIniSpooler->bEnableNetPopups ) {
return;
}
}
#else
if ( !pIniJob->pNotify ||
!pIniJob->pNotify[0] ||
!(pIniJob->Status & JOB_REMOTE) ||
!pIniJob->pIniPrinter->pIniSpooler->bEnableNetPopups ) {
return;
}
#endif
if ( FAILED(RetVal = FillAlertStructWithJobStrings(pIniJob, &pinfo, &AlertSize)))
{
if ( pinfo )
FreeSplMem(pinfo);
return;
}
pinfo->alrtpr_jobid = pIniJob->JobId;
if (pIniJob->Status & (JOB_PRINTING | JOB_DESPOOLING | JOB_PRINTED | JOB_COMPLETE))
Status = PRJOB_QS_PRINTING;
else if (pIniJob->Status & JOB_PAUSED)
Status = PRJOB_QS_PAUSED;
else if (pIniJob->Status & JOB_SPOOLING)
Status = PRJOB_QS_SPOOLING;
else
Status = PRJOB_QS_QUEUED;
if (pIniJob->Status & (JOB_ERROR | JOB_OFFLINE | JOB_PAPEROUT)) {
Status |= PRJOB_ERROR;
if (pIniJob->Status & JOB_OFFLINE)
Status |= PRJOB_DESTOFFLINE;
if (pIniJob->Status & JOB_PAPEROUT)
Status |= PRJOB_DESTNOPAPER;
}
if (pIniJob->Status & JOB_PRINTED)
Status |= PRJOB_COMPLETE;
else if (pIniJob->Status & JOB_PENDING_DELETION)
Status |= PRJOB_DELETED;
pinfo->alrtpr_status = Status;
SystemTimeToFileTime( &pIniJob->Submitted, &FileTime );
// FileTimeToDosDateTime(&FileTime, &DosDate, &DosTime);
// pinfo->alrtpr_submitted = DosDate << 16 | DosTime;
RtlTimeToSecondsSince1970((PLARGE_INTEGER) &FileTime,
&pinfo->alrtpr_submitted);
pinfo->alrtpr_size = pIniJob->Size;
(*pfnNetAlertRaiseEx)(ALERT_PRINT_EVENT,
(PBYTE) pinfo,
AlertSize,
L"SPOOLER");
if ( pinfo )
FreeSplMem(pinfo);
}
DWORD
AddPrintShare(
PINISPOOLER pIniSpooler
)
/*++
Routine Description:
Adds the print$ share based on pIniSpooler.
Arguments:
pIniSpooler - Share path is based on this information. pDriversShareInfo
must be initialized before calling this routine.
Return Value:
TRUE - Success.
FALSE - Failed. LastError set.
--*/
{
DWORD rc;
DWORD ParmError;
SHARE_INFO_1501 ShareInfo1501 = {0};
PSHARE_INFO_2 pShareInfo = (PSHARE_INFO_2)pIniSpooler->pDriversShareInfo;
PSECURITY_DESCRIPTOR pSecurityDescriptor = NULL;
//
// Assert that the path is identical to the local one since there's
// only one print$ share. It should always be.
//
SPLASSERT( !lstrcmpi( pShareInfo->shi2_path,
((PSHARE_INFO_2)pLocalIniSpooler->pDriversShareInfo)->shi2_path ));
rc = (*pfnNetShareAdd)( NULL,
2,
(LPBYTE)pShareInfo,
&ParmError );
//
// If it already exists, assume it is set up correctly.
//
if( rc == NERR_DuplicateShare ){
return ERROR_SUCCESS;
}
//
// If we didn't create the share, fail.
//
if( rc != ERROR_SUCCESS ){
DBGMSG( DBG_WARN,
( "AddPrintShare: Error %d, Parm %d\n", rc, ParmError));
return rc;
}
//
// Set security on the newly created share.
//
// Bug 54844
// If this fails, we've created the share but haven't put security
// on it. Then since it exists, we'll never try and set it again.
//
pSecurityDescriptor = CreateDriversShareSecurityDescriptor();
if( !pSecurityDescriptor ){
return GetLastError();
}
ShareInfo1501.shi1501_security_descriptor = pSecurityDescriptor;
rc = (*pfnNetShareSetInfo)( NULL,
pShareInfo->shi2_netname,
1501,
&ShareInfo1501,
&ParmError );
if( rc != ERROR_SUCCESS ){
DBGMSG( DBG_WARN,
( "NetShareSetInfo failed: Error %d, Parm %d\n",
rc, ParmError));
}
LocalFree(pSecurityDescriptor);
return rc;
}
/*++
Routine Name:
CheckShareSame
Routine Description:
This checks to see whether the given share name is the same on both the
local and remote machines.
Arguments:
pIniPrinter - The iniprinter for which we are adding the share.
pShareInfo502 - The share info that we are attempting to add the share
with.
pbSame - The return parameter is TRUE if the shares were the same
If the rc is not ERROR_SUCCESS, then the info could not
be set.
Return Value:
An error code.
--*/
DWORD
CheckShareSame(
IN PINIPRINTER pIniPrinter,
IN SHARE_INFO_502 *pShareInfo502,
OUT BOOL *pbSame
)
{
DWORD rc = ERROR_SUCCESS;
SHARE_INFO_2 *pShareInfoCompare = NULL;
BOOL bPathEquivalent = FALSE;
BOOL bSame = FALSE;
DWORD ParmError;
SplOutSem();
//
// Get the share info for the share, we should already have determined
// that this share exists.
//
rc = pfnNetShareGetInfo(NULL, pShareInfo502->shi502_netname, 2, (LPBYTE *)&pShareInfoCompare);
if (ERROR_SUCCESS == rc)
{
if (STYPE_PRINTQ == pShareInfoCompare->shi2_type)
{
//
// Check to see whether the paths are the same, in the upgrade case, the
// LocalSplOnly will be taken off, so, compare this too.
//
bSame = !_wcsicmp(pShareInfoCompare->shi2_path, pShareInfo502->shi502_path);
//
// If they are not the same, compare it to the name of the printer.
//
if (!bSame)
{
EnterSplSem();
bSame = bPathEquivalent = !_wcsicmp(pIniPrinter->pName, pShareInfoCompare->shi2_path);
LeaveSplSem();
}
}
}
*pbSame = bSame;
if (ERROR_SUCCESS == rc && bSame)
{
//
// If the paths are identical, we can just set the share info, otherwise
// we have to delete and recreate the share.
//
if (!bPathEquivalent)
{
//
// OK, they are the same, set the share info instead.
//
rc = (*pfnNetShareSetInfo)(NULL, pShareInfo502->shi502_netname, 502, pShareInfo502, &ParmError);
}
else
{
rc = (*pfnNetShareDel)(NULL, pShareInfo502->shi502_netname, 0);
if (ERROR_SUCCESS == rc)
{
rc = (*pfnNetShareAdd)(NULL, 502, (LPBYTE)pShareInfo502, &ParmError);
}
}
}
if (pShareInfoCompare)
{
pfnNetApiBufferFree(pShareInfoCompare);
}
return rc;
}