/* Editor Settings: expandtabs and use 4 spaces for indentation
* ex: set softtabstop=4 tabstop=8 expandtab shiftwidth=4: *
* -*- mode: c, c-basic-offset: 4 -*- */
/*
* Copyright Likewise Software 2004-2008
* All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details. You should have received a copy of the GNU General
* Public License along with this program. If not, see
* .
*
* LIKEWISE SOFTWARE MAKES THIS SOFTWARE AVAILABLE UNDER OTHER LICENSING
* TERMS AS WELL. IF YOU HAVE ENTERED INTO A SEPARATE LICENSE AGREEMENT
* WITH LIKEWISE SOFTWARE, THEN YOU MAY ELECT TO USE THE SOFTWARE UNDER THE
* TERMS OF THAT SOFTWARE LICENSE AGREEMENT INSTEAD OF THE TERMS OF THE GNU
* GENERAL PUBLIC LICENSE, NOTWITHSTANDING THE ABOVE NOTICE. IF YOU
* HAVE QUESTIONS, OR WISH TO REQUEST A COPY OF THE ALTERNATE LICENSING
* TERMS OFFERED BY LIKEWISE SOFTWARE, PLEASE CONTACT LIKEWISE SOFTWARE AT
* license@likewisesoftware.com
*/
/*
* Copyright (C) Likewise Software. All rights reserved.
*
* Module Name:
*
* main.c
*
* Abstract:
*
* Likewise Site Manager
*
* Service Entry API
*
* Authors: Krishna Ganugapati (krishnag@likewisesoftware.com)
* Sriram Nambakam (snambakam@likewisesoftware.com)
* Danilo Alameida (dalmeida@likewisesoftware.com)
*/
#include "includes.h"
int
main(
int argc,
const char* argv[]
)
{
DWORD dwError = 0;
PCSTR pszSmNotify = NULL;
int notifyFd = -1;
char notifyCode = 0;
int ret = 0;
dwError = LWNetSrvSetDefaults();
BAIL_ON_LWNET_ERROR(dwError);
dwError = LWNetSrvParseArgs(argc, argv, &gServerInfo);
BAIL_ON_LWNET_ERROR(dwError);
dwError = LWNetSrvInitLogging(LWNetGetProgramName(argv[0]));
BAIL_ON_LWNET_ERROR(dwError);
LWNET_LOG_VERBOSE("Logging started");
if (LWNetSrvShouldStartAsDaemon())
{
dwError = LWNetSrvStartAsDaemon();
BAIL_ON_LWNET_ERROR(dwError);
}
if (atexit(LWNetSrvExitHandler) < 0)
{
dwError = LwMapErrnoToLwError(errno);
BAIL_ON_LWNET_ERROR(dwError);
}
// Test system to see if dependent configuration tasks are completed prior to starting our process.
dwError = LWNetStartupPreCheck();
BAIL_ON_LWNET_ERROR(dwError);
#ifdef ENABLE_PIDFILE
// ISSUE-2008/07/03-dalmeida -- Should return/check for errors
LWNetSrvCreatePIDFile();
#endif
dwError = LWNetBlockSelectedSignals();
BAIL_ON_LWNET_ERROR(dwError);
dwError = LwDsCacheAddPidException(getpid());
if (dwError == LW_ERROR_FAILED_STARTUP_PREREQUISITE_CHECK)
{
LWNET_LOG_ERROR("Could not register process pid (%d) with Mac DirectoryService Cache plugin", (int) getpid());
BAIL_ON_LWNET_ERROR(dwError);
}
dwError = LWNetSrvInitialize();
BAIL_ON_LWNET_ERROR(dwError);
dwError = LWNetSrvStartListenThread();
BAIL_ON_LWNET_ERROR(dwError);
if ((pszSmNotify = getenv("LIKEWISE_SM_NOTIFY")) != NULL)
{
notifyFd = atoi(pszSmNotify);
do
{
ret = write(notifyFd, ¬ifyCode, sizeof(notifyCode));
} while(ret != sizeof(notifyCode) && errno == EINTR);
if (ret < 0)
{
LWNET_LOG_ERROR("Could not notify service manager: %s (%i)", strerror(errno), errno);
dwError = LwMapErrnoToLwError(errno);
BAIL_ON_LWNET_ERROR(dwError);
}
close(notifyFd);
}
// Post service started event to eventlog
LWNetSrvLogProcessStartedEvent();
// Handle signals, blocking until we are supposed to exit.
dwError = LWNetSrvHandleSignals();
BAIL_ON_LWNET_ERROR(dwError);
cleanup:
LWNET_LOG_VERBOSE("LWNet main cleaning up");
// Post service stopped event to eventlog
LWNetSrvLogProcessStoppedEvent(dwError);
LWNetSrvStopListenThread();
LwDsCacheRemovePidException(getpid());
LWNetSrvApiShutdown();
LWNET_LOG_INFO("LWNET Service exiting...");
lwnet_close_log();
LWNetSrvSetProcessExitCode(dwError);
return dwError;
error:
LWNET_LOG_ERROR("LWNET Process exiting due to error [Code:%d]", dwError);
// Post service failed event to eventlog
LWNetSrvLogProcessFailureEvent(dwError);
goto cleanup;
}
DWORD
LWNetStartupPreCheck(
VOID
)
{
DWORD dwError = 0;
#ifdef __LWI_DARWIN__
PSTR pszHostname = NULL;
int iter = 0;
// Make sure that the local hostname has been setup by the system
for (iter = 0; iter < STARTUP_PRE_CHECK_WAIT; iter++)
{
LWNET_SAFE_FREE_STRING(pszHostname);
dwError = LWNetDnsGetHostInfo(&pszHostname, NULL);
BAIL_ON_LWNET_ERROR(dwError);
if (!strcasecmp(pszHostname, "localhost"))
{
sleep(10);
}
else
{
/* Hostname now looks correct */
LWNET_LOG_INFO("LWNet Process start up check completed [Hostname = %s]", pszHostname);
break;
}
}
if (iter >= STARTUP_PRE_CHECK_WAIT)
{
dwError = ERROR_SERVICE_DEPENDENCY_FAIL;
LWNET_LOG_ERROR("LWNet start up pre-check failed to get updated hostname after 2 minutes of waiting [Code:%d]", dwError);
BAIL_ON_LWNET_ERROR(dwError);
}
cleanup:
LWNET_SAFE_FREE_STRING(pszHostname);
return dwError;
error:
LWNET_LOG_ERROR("LWNet Process exiting due to error checking hostname at startup [Code:%d]", dwError);
goto cleanup;
#else
return dwError;
#endif
}
DWORD
LWNetSrvSetDefaults(
VOID
)
{
DWORD dwError = 0;
gpServerInfo->dwLogLevel = LWNET_LOG_LEVEL_ERROR;
*(gpServerInfo->szLogFilePath) = '\0';
strcpy(gpServerInfo->szCachePath, LWNET_CACHE_DIR);
strcpy(gpServerInfo->szPrefixPath, PREFIXDIR);
setlocale(LC_ALL, "");
return (dwError);
}
DWORD
LWNetSrvParseArgs(
int argc,
PCSTR argv[],
PLWNETSERVERINFO pLWNetServerInfo
)
{
int iArg = 0;
BOOLEAN bShowUsage = FALSE;
BOOLEAN bError = FALSE;
for (iArg = 1; iArg < argc; iArg++)
{
PCSTR pArg = argv[iArg];
if (!strcmp(pArg, "--help") ||
!strcmp(pArg, "-h"))
{
bShowUsage = TRUE;
break;
}
else if (!strcmp(pArg, "--start-as-daemon"))
{
pLWNetServerInfo->dwStartAsDaemon = 1;
}
else if (!strcmp(pArg, "--logfile"))
{
if (iArg + 1 >= argc)
{
fprintf(stderr, "Missing required argument for %s option.\n", pArg);
bError = TRUE;
break;
}
// ISSUE-2008/07/03-dalmeida -- not safe
pArg = argv[++iArg];
strcpy(pLWNetServerInfo->szLogFilePath, pArg);
}
else if (strcmp(pArg, "--syslog") == 0)
{
pLWNetServerInfo->bLogToSyslog = TRUE;
}
else if (strcmp(pArg, "--loglevel") == 0)
{
if (iArg + 1 >= argc)
{
fprintf(stderr, "Missing required argument for %s option.\n", pArg);
bError = TRUE;
break;
}
pArg = argv[++iArg];
if (!strcasecmp(pArg, "error"))
{
pLWNetServerInfo->dwLogLevel = LWNET_LOG_LEVEL_ERROR;
}
else if (!strcasecmp(pArg, "warning"))
{
pLWNetServerInfo->dwLogLevel = LWNET_LOG_LEVEL_WARNING;
}
else if (!strcasecmp(pArg, "info"))
{
pLWNetServerInfo->dwLogLevel = LWNET_LOG_LEVEL_INFO;
}
else if (!strcasecmp(pArg, "verbose"))
{
pLWNetServerInfo->dwLogLevel = LWNET_LOG_LEVEL_VERBOSE;
}
else if (!strcasecmp(pArg, "debug"))
{
pLWNetServerInfo->bEnableDebugLogs = TRUE;
pLWNetServerInfo->dwLogLevel = LWNET_LOG_LEVEL_DEBUG;
}
else
{
fprintf(stderr, "Invalid log level specified: '%s'.\n", pArg);
bError = TRUE;
break;
}
}
else
{
fprintf(stderr, "Unrecognized command line option: '%s'.\n", pArg);
bError = TRUE;
break;
}
}
if (bShowUsage || bError)
{
ShowUsage(LWNetGetProgramName(argv[0]));
exit(bError ? 1 : 0);
}
return 0;
}
PCSTR
LWNetGetProgramName(
PCSTR pszFullProgramPath
)
{
PCSTR pszNameStart = NULL;
if (IsNullOrEmptyString(pszFullProgramPath))
{
return "";
}
// start from end of the string
pszNameStart = pszFullProgramPath + strlen(pszFullProgramPath);
do {
if (*(pszNameStart - 1) == '/') {
break;
}
pszNameStart--;
} while (pszNameStart != pszFullProgramPath);
return pszNameStart;
}
VOID
ShowUsage(
PCSTR pszProgramName
)
{
printf("Usage: %s [options]\n"
"\n"
" Options:\n"
"\n"
" --start-as-daemon start in daemon mode\n"
" --syslog log to syslog\n"
" --logfile log to specified file\n"
" --loglevel log at the specified detail level, which\n"
" can be one of:\n"
" error, warning, info, verbose, debug.\n"
"", pszProgramName);
}
VOID
LWNetSrvExitHandler(
VOID
)
{
DWORD dwError = 0;
DWORD dwExitCode = 0;
CHAR szErrCodeFilePath[PATH_MAX+1];
PSTR pszCachePath = NULL;
BOOLEAN bFileExists = 0;
FILE* fp = NULL;
dwError = LWNetSrvGetCachePath(&pszCachePath);
BAIL_ON_LWNET_ERROR(dwError);
sprintf(szErrCodeFilePath, "%s/LWNetsd.err", pszCachePath);
dwError = LwCheckFileTypeExists(
szErrCodeFilePath,
LWFILE_REGULAR,
&bFileExists);
BAIL_ON_LWNET_ERROR(dwError);
if (bFileExists) {
dwError = LwRemoveFile(szErrCodeFilePath);
BAIL_ON_LWNET_ERROR(dwError);
}
dwError = LWNetSrvGetProcessExitCode(&dwExitCode);
BAIL_ON_LWNET_ERROR(dwError);
if (dwExitCode) {
fp = fopen(szErrCodeFilePath, "w");
if (fp == NULL) {
dwError = LwMapErrnoToLwError(errno);
BAIL_ON_LWNET_ERROR(dwError);
}
fprintf(fp, "%d\n", dwExitCode);
}
error:
LWNET_SAFE_FREE_STRING(pszCachePath);
if (fp != NULL) {
fclose(fp);
}
}
DWORD
LWNetSrvInitialize(
VOID
)
{
DWORD dwError = 0;
dwError = LWNetSrvApiInit();
BAIL_ON_LWNET_ERROR(dwError);
cleanup:
return dwError;
error:
goto cleanup;
}
BOOLEAN
LWNetSrvShouldStartAsDaemon(
VOID
)
{
BOOLEAN bResult = FALSE;
BOOLEAN bInLock = FALSE;
LWNET_LOCK_SERVERINFO(bInLock);
bResult = (gpServerInfo->dwStartAsDaemon != 0);
LWNET_UNLOCK_SERVERINFO(bInLock);
return bResult;
}
DWORD
LWNetSrvStartAsDaemon(
VOID
)
{
DWORD dwError = 0;
pid_t pid;
int fd = 0;
int iFd = 0;
if ((pid = fork()) != 0) {
// Parent terminates
exit (0);
}
// Let the first child be a session leader
setsid();
// Ignore SIGHUP, because when the first child terminates
// it would be a session leader, and thus all processes in
// its session would receive the SIGHUP signal. By ignoring
// this signal, we are ensuring that our second child will
// ignore this signal and will continue execution.
if (signal(SIGHUP, SIG_IGN) < 0) {
dwError = LwMapErrnoToLwError(errno);
BAIL_ON_LWNET_ERROR(dwError);
}
// Spawn a second child
if ((pid = fork()) != 0) {
// Let the first child terminate
// This will ensure that the second child cannot be a session leader
// Therefore, the second child cannot hold a controlling terminal
exit(0);
}
// This is the second child executing
dwError = chdir("/");
BAIL_ON_LWNET_ERROR(dwError);
// Clear our file mode creation mask
umask(0);
for (iFd = 0; iFd < 3; iFd++)
close(iFd);
for (iFd = 0; iFd < 3; iFd++) {
fd = open("/dev/null", O_RDWR, 0);
if (fd < 0) {
fd = open("/dev/null", O_WRONLY, 0);
}
if (fd < 0) {
exit(1);
}
if (fd != iFd) {
exit(1);
}
}
return (dwError);
error:
return (dwError);
}
DWORD
LWNetSrvGetProcessExitCode(
PDWORD pdwExitCode
)
{
DWORD dwError = 0;
BOOLEAN bInLock = FALSE;
LWNET_LOCK_SERVERINFO(bInLock);
*pdwExitCode = gpServerInfo->dwExitCode;
LWNET_UNLOCK_SERVERINFO(bInLock);
return dwError;
}
VOID
LWNetSrvSetProcessExitCode(
DWORD dwExitCode
)
{
BOOLEAN bInLock = FALSE;
LWNET_LOCK_SERVERINFO(bInLock);
gpServerInfo->dwExitCode = dwExitCode;
LWNET_UNLOCK_SERVERINFO(bInLock);
}
DWORD
LWNetSrvGetCachePath(
PSTR* ppszPath
)
{
DWORD dwError = 0;
PSTR pszPath = NULL;
BOOLEAN bInLock = FALSE;
LWNET_LOCK_SERVERINFO(bInLock);
if (IsNullOrEmptyString(gpServerInfo->szCachePath)) {
dwError = ERROR_PATH_NOT_FOUND;
BAIL_ON_LWNET_ERROR(dwError);
}
dwError = LWNetAllocateString(gpServerInfo->szCachePath, &pszPath);
BAIL_ON_LWNET_ERROR(dwError);
*ppszPath = pszPath;
cleanup:
LWNET_UNLOCK_SERVERINFO(bInLock);
return dwError;
error:
LWNET_SAFE_FREE_STRING(pszPath);
*ppszPath = NULL;
goto cleanup;
}
DWORD
LWNetSrvGetPrefixPath(
PSTR* ppszPath
)
{
DWORD dwError = 0;
PSTR pszPath = NULL;
BOOLEAN bInLock = FALSE;
LWNET_LOCK_SERVERINFO(bInLock);
if (IsNullOrEmptyString(gpServerInfo->szPrefixPath)) {
dwError = ERROR_PATH_NOT_FOUND;
BAIL_ON_LWNET_ERROR(dwError);
}
dwError = LWNetAllocateString(gpServerInfo->szPrefixPath, &pszPath);
BAIL_ON_LWNET_ERROR(dwError);
*ppszPath = pszPath;
cleanup:
LWNET_UNLOCK_SERVERINFO(bInLock);
return dwError;
error:
LWNET_SAFE_FREE_STRING(pszPath);
*ppszPath = NULL;
goto cleanup;
}
VOID
LWNetSrvCreatePIDFile(
VOID
)
{
int result = -1;
pid_t pid;
char contents[PID_FILE_CONTENTS_SIZE];
size_t len;
int fd = -1;
pid = LWNetSrvGetPidFromPidFile();
if (pid > 0) {
fprintf(stderr, "Daemon already running as %d\n", (int) pid);
result = -1;
goto error;
}
fd = open(PID_FILE, O_CREAT | O_WRONLY | O_EXCL, 0644);
if (fd < 0) {
fprintf(stderr, "Could not create pid file: %s\n", strerror(errno));
result = 1;
goto error;
}
pid = getpid();
snprintf(contents, sizeof(contents)-1, "%d\n", (int) pid);
contents[sizeof(contents)-1] = 0;
len = strlen(contents);
result = (int) write(fd, contents, len);
if ( result != (int) len ) {
fprintf(stderr, "Could not write to pid file: %s\n", strerror(errno));
result = -1;
goto error;
}
result = 0;
error:
if (fd != -1) {
close(fd);
}
if (result < 0) {
exit(1);
}
}
pid_t
LWNetSrvGetPidFromPidFile(
VOID
)
{
pid_t pid = 0;
int fd = -1;
int result;
char contents[PID_FILE_CONTENTS_SIZE];
fd = open(PID_FILE, O_RDONLY, 0644);
if (fd < 0) {
goto error;
}
result = read(fd, contents, sizeof(contents)-1);
if (result <= 0) {
goto error;
}
contents[result-1] = 0;
result = atoi(contents);
if (result <= 0) {
result = -1;
goto error;
}
pid = (pid_t) result;
result = kill(pid, 0);
if (result != 0 || errno == ESRCH) {
unlink(PID_FILE);
pid = 0;
}
error:
if (fd != -1) {
close(fd);
}
return pid;
}
DWORD
LWNetSrvInitLogging(
PCSTR pszProgramName
)
{
DWORD dwError = 0;
BOOLEAN bInLock = FALSE;
LWNET_LOCK_SERVERINFO(bInLock);
if ((gpServerInfo->dwStartAsDaemon &&
gpServerInfo->szLogFilePath[0] == '\0') ||
gpServerInfo->bLogToSyslog)
{
dwError = lwnet_init_logging_to_syslog(gpServerInfo->dwLogLevel,
gpServerInfo->bEnableDebugLogs,
pszProgramName,
LOG_PID,
LOG_DAEMON);
BAIL_ON_LWNET_ERROR(dwError);
} else {
dwError = lwnet_init_logging_to_file(gpServerInfo->dwLogLevel,
gpServerInfo->bEnableDebugLogs,
gpServerInfo->szLogFilePath);
BAIL_ON_LWNET_ERROR(dwError);
}
cleanup:
LWNET_UNLOCK_SERVERINFO(bInLock);
return dwError;
error:
goto cleanup;
}
VOID
LWNetClearAllSignals(
VOID
)
{
sigset_t default_signal_mask;
sigset_t old_signal_mask;
sigemptyset(&default_signal_mask);
pthread_sigmask(SIG_SETMASK, &default_signal_mask, &old_signal_mask);
}
DWORD
LWNetBlockSelectedSignals(
VOID
)
{
DWORD dwError = 0;
sigset_t default_signal_mask;
sigset_t old_signal_mask;
sigemptyset(&default_signal_mask);
sigaddset(&default_signal_mask, SIGINT);
sigaddset(&default_signal_mask, SIGTERM);
sigaddset(&default_signal_mask, SIGHUP);
sigaddset(&default_signal_mask, SIGQUIT);
sigaddset(&default_signal_mask, SIGPIPE);
dwError = pthread_sigmask(SIG_BLOCK, &default_signal_mask, &old_signal_mask);
BAIL_ON_LWNET_ERROR(dwError);
cleanup:
return dwError;
error:
goto cleanup;
}
BOOLEAN
LWNetSrvShouldProcessExit(
VOID
)
{
BOOLEAN bExit = FALSE;
BOOLEAN bInLock = FALSE;
LWNET_LOCK_SERVERINFO(bInLock);
bExit = gpServerInfo->bProcessShouldExit;
LWNET_UNLOCK_SERVERINFO(bInLock);
return bExit;
}
VOID
LWNetSrvSetProcessToExit(
BOOLEAN bExit
)
{
BOOLEAN bInLock = FALSE;
LWNET_LOCK_SERVERINFO(bInLock);
gpServerInfo->bProcessShouldExit = bExit;
LWNET_UNLOCK_SERVERINFO(bInLock);
}
VOID
LWNetSrvLogProcessStartedEvent(
VOID
)
{
DWORD dwError = 0;
PSTR pszDescription = NULL;
dwError = LwAllocateStringPrintf(
&pszDescription,
"The Likewise site manager service was started.");
BAIL_ON_LWNET_ERROR(dwError);
LWNetSrvLogInformationEvent(
LWNET_EVENT_INFO_SERVICE_STARTED,
SERVICE_EVENT_CATEGORY,
pszDescription,
NULL);
cleanup:
LWNET_SAFE_FREE_STRING(pszDescription);
return;
error:
goto cleanup;
}
VOID
LWNetSrvLogProcessStoppedEvent(
DWORD dwExitCode
)
{
DWORD dwError = 0;
PSTR pszDescription = NULL;
PSTR pszData = NULL;
dwError = LwAllocateStringPrintf(
&pszDescription,
"The Likewise site manager service was stopped");
BAIL_ON_LWNET_ERROR(dwError);
dwError = LWNetGetErrorMessageForLoggingEvent(
dwExitCode,
&pszData);
BAIL_ON_LWNET_ERROR(dwError);
if (dwExitCode)
{
LWNetSrvLogErrorEvent(
LWNET_EVENT_ERROR_SERVICE_STOPPED,
SERVICE_EVENT_CATEGORY,
pszDescription,
pszData);
}
else
{
LWNetSrvLogInformationEvent(
LWNET_EVENT_INFO_SERVICE_STOPPED,
SERVICE_EVENT_CATEGORY,
pszDescription,
pszData);
}
cleanup:
LWNET_SAFE_FREE_STRING(pszDescription);
LWNET_SAFE_FREE_STRING(pszData);
return;
error:
goto cleanup;
}
VOID
LWNetSrvLogProcessFailureEvent(
DWORD dwErrCode
)
{
DWORD dwError = 0;
PSTR pszDescription = NULL;
PSTR pszData = NULL;
dwError = LwAllocateStringPrintf(
&pszDescription,
"The Likewise site manager service stopped running due to an error");
BAIL_ON_LWNET_ERROR(dwError);
dwError = LWNetGetErrorMessageForLoggingEvent(
dwErrCode,
&pszData);
BAIL_ON_LWNET_ERROR(dwError);
LWNetSrvLogErrorEvent(
LWNET_EVENT_ERROR_SERVICE_START_FAILURE,
SERVICE_EVENT_CATEGORY,
pszDescription,
pszData);
cleanup:
LWNET_SAFE_FREE_STRING(pszDescription);
LWNET_SAFE_FREE_STRING(pszData);
return;
error:
goto cleanup;
}
DWORD
LWNetGetErrorMessageForLoggingEvent(
DWORD dwErrCode,
PSTR* ppszErrorMsg
)
{
DWORD dwErrorBufferSize = 0;
DWORD dwError = 0;
DWORD dwLen = 0;
PSTR pszErrorMsg = NULL;
PSTR pszErrorBuffer = NULL;
dwErrorBufferSize = LwGetErrorString(dwErrCode, NULL, 0);
if (!dwErrorBufferSize)
goto cleanup;
dwError = LWNetAllocateMemory(
dwErrorBufferSize,
(PVOID*)&pszErrorBuffer);
BAIL_ON_LWNET_ERROR(dwError);
dwLen = LwGetErrorString(dwErrCode, pszErrorBuffer, dwErrorBufferSize);
if ((dwLen == dwErrorBufferSize) && !IsNullOrEmptyString(pszErrorBuffer))
{
dwError = LwAllocateStringPrintf(
&pszErrorMsg,
"Error: %s [error code: %d]",
pszErrorBuffer,
dwErrCode);
BAIL_ON_LWNET_ERROR(dwError);
}
*ppszErrorMsg = pszErrorMsg;
cleanup:
LWNET_SAFE_FREE_STRING(pszErrorBuffer);
return dwError;
error:
LWNET_SAFE_FREE_STRING(pszErrorMsg);
*ppszErrorMsg = NULL;
goto cleanup;
}