703 lines
18 KiB
C
703 lines
18 KiB
C
/*****************************************************************************
|
|
*
|
|
* MSNSPA.c
|
|
*
|
|
* Copyright (c) 1997 Microsoft Corporation. All Rights Reserved.
|
|
*
|
|
* Abstract:
|
|
*
|
|
* MSN SPA Proxy.
|
|
*
|
|
* Proxies POP and NNTP for clients that don't speak them natively.
|
|
*
|
|
* Runs as app that minimizes to nowhere. Get it back by Alt+Tab'ing
|
|
* to it.
|
|
*
|
|
*****************************************************************************/
|
|
|
|
#include "msnspa.h"
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* Globals
|
|
*
|
|
*****************************************************************************/
|
|
|
|
HINSTANCE g_hinst;
|
|
HINSTANCE g_hinstSecur;
|
|
PSecurityFunctionTable g_psft;
|
|
|
|
#ifdef DBG
|
|
/*****************************************************************************
|
|
*
|
|
* Squirt - Print a message
|
|
*
|
|
*****************************************************************************/
|
|
|
|
void __cdecl
|
|
Squirt(LPCTSTR ptszMsg, ...)
|
|
{
|
|
TCHAR tsz[1024 + PLENTY_BIG];
|
|
va_list ap;
|
|
va_start(ap, ptszMsg);
|
|
wvsprintf(tsz, ptszMsg, ap);
|
|
|
|
OutputDebugString(tsz);
|
|
}
|
|
#endif
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* Die - Death
|
|
*
|
|
*****************************************************************************/
|
|
|
|
void __cdecl
|
|
Die(LPCTSTR ptszMsg, ...)
|
|
{
|
|
TCHAR tsz[1024];
|
|
va_list ap;
|
|
va_start(ap, ptszMsg);
|
|
wvsprintf(tsz, ptszMsg, ap);
|
|
|
|
OutputDebugString(tsz);
|
|
OutputDebugString(TEXT("\r\n"));
|
|
ExitProcess(1);
|
|
}
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* IsNT
|
|
*
|
|
*****************************************************************************/
|
|
|
|
BOOL
|
|
IsNT(void)
|
|
{
|
|
return (int)GetVersion() >= 0;
|
|
}
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* RFC1113 translation tables
|
|
*
|
|
*****************************************************************************/
|
|
|
|
const char RFC1113_From[256]={
|
|
64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,
|
|
64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,62,64,64,64,63,
|
|
52,53,54,55,56,57,58,59,60,61,64,64,64,64,64,64,64,0,1,2,3,4,5,6,7,8,9,
|
|
10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,64,64,64,64,64,64,26,27,
|
|
28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,
|
|
64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,
|
|
64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,
|
|
64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,
|
|
64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,
|
|
64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,
|
|
64,64,64,64,64,64,64,64,64,64,64,64,64
|
|
};
|
|
|
|
const char RFC1113_To[64] = {
|
|
'A','B','C','D','E','F','G','H','I','J','K','L','M',
|
|
'N','O','P','Q','R','S','T','U','V','W','X','Y','Z',
|
|
'a','b','c','d','e','f','g','h','i','j','k','l','m',
|
|
'n','o','p','q','r','s','t','u','v','w','x','y','z',
|
|
'0','1','2','3','4','5','6','7','8','9','+','/'
|
|
};
|
|
|
|
#define chPad '='
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* RFC1113_Encode
|
|
*
|
|
* Convert a binary blob into an ASCII string using RFC1113 encoding.
|
|
* (I think it's RFC1113. Boy would it be embarrassing if it weren't.)
|
|
*
|
|
* szBuf - output buffer, will be null-terminated
|
|
* rgbIn - source buffer to be encoded
|
|
* cbIn - number of bytes in source buffer
|
|
*
|
|
*****************************************************************************/
|
|
|
|
void
|
|
RFC1113_Encode(LPSTR szBuf, const BYTE *rgbIn, UINT cbIn)
|
|
{
|
|
LPSTR psz = szBuf;
|
|
UINT ib;
|
|
|
|
unsigned char *outptr;
|
|
unsigned int i;
|
|
|
|
for (ib = 0; ib < cbIn; ib += 3) {
|
|
*psz++ = RFC1113_To[*rgbIn >> 2];
|
|
*psz++ = RFC1113_To[((*rgbIn << 4) & 060) | ((rgbIn[1] >> 4) & 017)];
|
|
*psz++ = RFC1113_To[((rgbIn[1] << 2) & 074) | ((rgbIn[2] >> 6) & 03)];
|
|
*psz++ = RFC1113_To[rgbIn[2] & 077];
|
|
|
|
rgbIn += 3;
|
|
}
|
|
|
|
/*
|
|
* If cbIn was not a multiple of 3, then we have encoded too
|
|
* many characters. Adjust appropriately.
|
|
*/
|
|
if (ib == cbIn + 1) {
|
|
/* There were only 2 bytes in that last group */
|
|
psz[-1] = chPad;
|
|
} else if (ib == cbIn + 2) {
|
|
/* There was only 1 byte in that last group */
|
|
psz[-1] = chPad;
|
|
psz[-2] = chPad;
|
|
}
|
|
|
|
*psz = '\0';
|
|
|
|
}
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* RFC1113_Decode
|
|
*
|
|
* Convert an ASCII string back into a binary blob.
|
|
*
|
|
* rgbOut - output buffer
|
|
* szIn - source buffer
|
|
*
|
|
* Returns number of bytes converted.
|
|
*
|
|
*****************************************************************************/
|
|
|
|
#define RFC1113x(ch) (RFC1113_From[(BYTE)(ch)] & 63)
|
|
|
|
int
|
|
RFC1113_Decode(LPBYTE rgbOut, LPSTR szIn)
|
|
{
|
|
int nbytesdecoded;
|
|
LPSTR psz;
|
|
BYTE *rgb = rgbOut;
|
|
int cchIn;
|
|
int cbRc;
|
|
|
|
/*
|
|
* Skip leading whitespace, just to be safe.
|
|
*/
|
|
|
|
while (szIn[0] == ' ' || szIn[0] == '\t') {
|
|
szIn++;
|
|
}
|
|
|
|
/*
|
|
* Figure out how many characters are in the input buffer.
|
|
*/
|
|
psz = szIn;
|
|
while (RFC1113_From[(BYTE)*psz] < 64) {
|
|
psz++;
|
|
}
|
|
|
|
/*
|
|
* The caller will pad the input string to a multiple of 4 in
|
|
* length with chPad's.
|
|
*
|
|
* Three bytes out for each four chars in.
|
|
*/
|
|
cchIn = (int)(psz - szIn);
|
|
|
|
cbRc = ((cchIn + 3) / 4) * 3;
|
|
|
|
/*
|
|
* Now decode it.
|
|
*/
|
|
psz = szIn;
|
|
|
|
while (cchIn > 0) {
|
|
*rgb++ =
|
|
(BYTE)(RFC1113x(psz[0]) << 2 | RFC1113x(psz[1]) >> 4);
|
|
*rgb++ =
|
|
(BYTE) (RFC1113x(psz[1]) << 4 | RFC1113x(psz[2]) >> 2);
|
|
*rgb++ =
|
|
(BYTE) (RFC1113x(psz[2]) << 6 | RFC1113x(psz[3]));
|
|
psz += 4;
|
|
cchIn -= 4;
|
|
}
|
|
|
|
/*
|
|
* Now adjust the number of output bytes based on the number of
|
|
* input equal-signs.
|
|
*/
|
|
if (cchIn & 3) {
|
|
if(RFC1113_From[psz[-2]] > 63) {
|
|
rgbOut[cbRc - 2] = 0;
|
|
cbRc -= 2;
|
|
} else {
|
|
rgbOut[cbRc - 1] = 0;
|
|
cbRc -= 1;
|
|
}
|
|
}
|
|
|
|
return cbRc;
|
|
}
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* LoadSecurityManager
|
|
*
|
|
* Obtain all the entry points into the security DLL.
|
|
*
|
|
*****************************************************************************/
|
|
|
|
BOOL
|
|
LoadSecurityManager(void)
|
|
{
|
|
INIT_SECURITY_INTERFACE InitSecurityInterface;
|
|
|
|
/*
|
|
* On NT, the security DLL is named SECURITY.DLL.
|
|
* On 95, the security DLL is named SECUR32.DLL.
|
|
*
|
|
* Go figure.
|
|
*/
|
|
g_hinstSecur = LoadLibrary(IsNT() ? TEXT("security.dll") :
|
|
TEXT("secur32.dll"));
|
|
if (!g_hinstSecur) {
|
|
Squirt(TEXT("Can't load security manager") EOL);
|
|
return FALSE;
|
|
}
|
|
|
|
InitSecurityInterface = (INIT_SECURITY_INTERFACE)
|
|
GetProcAddress(g_hinstSecur, SECURITY_ENTRYPOINT);
|
|
|
|
if (!InitSecurityInterface) {
|
|
Squirt(TEXT("Can't find entrypoint") EOL);
|
|
return FALSE;
|
|
}
|
|
|
|
g_psft = InitSecurityInterface();
|
|
if (!g_psft) {
|
|
Squirt(TEXT("Unable to init security interface") EOL);
|
|
return FALSE;
|
|
}
|
|
|
|
Squirt(TEXT("Security manager successfully loaded") EOL);
|
|
return TRUE;
|
|
}
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* Security_AcquireCredentials
|
|
*
|
|
* pwas -> WIN32AUTHSTATE to track the state of this session
|
|
* ptszPackage - name of security package (e.g., "MSN")
|
|
*
|
|
*****************************************************************************/
|
|
|
|
BOOL INTERNAL
|
|
Security_AcquireCredentials(PWIN32AUTHSTATE pwas, LPTSTR ptszPackage)
|
|
{
|
|
TimeStamp tsExpires;
|
|
SECURITY_STATUS ss;
|
|
char szToken[PLENTY_BIG];
|
|
|
|
/*
|
|
* Clean slate.
|
|
*/
|
|
ZeroMemory(pwas, sizeof(*pwas));
|
|
|
|
ss = g_psft->AcquireCredentialsHandle(
|
|
NULL, /* Use credentials of current user */
|
|
ptszPackage, /* Use this security package */
|
|
SECPKG_CRED_OUTBOUND, /* I am the untrusted one */
|
|
NULL, /* Not gonna access remote files */
|
|
NULL, /* Additional info */
|
|
NULL, /* No credential retriever */
|
|
NULL, /* No credential retriever */
|
|
&pwas->hCred, /* Receives credentials handle */
|
|
&tsExpires); /* Expiration time for hCred */
|
|
|
|
if (ss == SEC_E_OK) {
|
|
pwas->fHCredValid = TRUE;
|
|
}
|
|
|
|
return (ss == SEC_E_OK);
|
|
|
|
}
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* Security_BuildOutString
|
|
*
|
|
* Build a string that will be output.
|
|
*
|
|
*****************************************************************************/
|
|
|
|
BOOL
|
|
Security_BuildOutString(PWIN32AUTHSTATE pwas, PSecBufferDesc pdescIn,
|
|
PCtxtHandle pctxOld, PCtxtHandle pctxNew,
|
|
LPTSTR ptszTarget)
|
|
{
|
|
SecBuffer bufOut;
|
|
SecBufferDesc descOut;
|
|
TimeStamp tsExpire;
|
|
SECURITY_STATUS ss;
|
|
BYTE rgbToken[PLENTY_BIG];
|
|
ULONG fContextAttrib;
|
|
|
|
/*
|
|
* Set up the buffers...
|
|
*/
|
|
descOut.ulVersion = SECBUFFER_VERSION;
|
|
descOut.cBuffers = 1;
|
|
descOut.pBuffers = &bufOut;
|
|
|
|
bufOut.cbBuffer = PLENTY_BIG;
|
|
bufOut.BufferType = SECBUFFER_TOKEN;
|
|
bufOut.pvBuffer = rgbToken;
|
|
|
|
retry:;
|
|
ss = g_psft->InitializeSecurityContext(
|
|
&pwas->hCred, /* Remember me? */
|
|
pctxOld, /* Current context */
|
|
ptszTarget, /* Server name */
|
|
pwas->fContextReq, /* Context requiremnents */
|
|
0, /* (reserved) */
|
|
SECURITY_NATIVE_DREP, /* Target data representation */
|
|
pdescIn, /* Input buffer descriptor */
|
|
0, /* (reserved) */
|
|
pctxNew, /* New context */
|
|
&descOut, /* Output buffer descriptor */
|
|
&fContextAttrib, /* Receives context attributes */
|
|
&tsExpire); /* Expiration time */
|
|
|
|
/*
|
|
* If we failed to obtain credentials, and we haven't yet prompted
|
|
* the user, then try again with prompting.
|
|
*/
|
|
if (ss == SEC_E_NO_CREDENTIALS &&
|
|
!(pwas->fContextReq & ISC_REQ_PROMPT_FOR_CREDS)) {
|
|
pwas->fContextReq |= ISC_REQ_PROMPT_FOR_CREDS;
|
|
goto retry;
|
|
}
|
|
|
|
if (FAILED(ss)) {
|
|
Squirt(TEXT("Logon failed") EOL);
|
|
return FALSE;
|
|
}
|
|
|
|
/*
|
|
* Oh dear, a continuation record? Ack, I can't handle that
|
|
* because I'm lazy.
|
|
*/
|
|
if (ss == SEC_I_CONTINUE_NEEDED) {
|
|
/* Aigh! */
|
|
}
|
|
|
|
/*
|
|
* Since POP and NNTP are text-based protocols, we need to
|
|
* RFC1113-encode the binary data before transmitting.
|
|
*/
|
|
|
|
RFC1113_Encode(pwas->szBuffer, rgbToken, bufOut.cbBuffer);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* Security_GetNegotiation
|
|
*
|
|
* Begin the transaction by building a negotiation string
|
|
*
|
|
*****************************************************************************/
|
|
|
|
BOOL INTERNAL
|
|
Security_GetNegotiation(PWIN32AUTHSTATE pwas)
|
|
{
|
|
BOOL fRc;
|
|
|
|
/*
|
|
* We're starting over; throw away any leftover context.
|
|
*/
|
|
if (pwas->fHCtxtValid) {
|
|
g_psft->DeleteSecurityContext(&pwas->hCtxt);
|
|
pwas->fHCtxtValid = FALSE;
|
|
}
|
|
|
|
/*
|
|
* Use common worker function to generate an output string.
|
|
*/
|
|
|
|
fRc = Security_BuildOutString(
|
|
pwas, /* Authorization state */
|
|
NULL, /* No input buffer */
|
|
NULL, /* No source context */
|
|
&pwas->hCtxt, /* Destination context */
|
|
NULL); /* Server name */
|
|
|
|
/*
|
|
* If it worked, then the hCtxt is valid and needs to be
|
|
* deleted when we're done.
|
|
*/
|
|
if (fRc) {
|
|
pwas->fHCtxtValid = TRUE;
|
|
}
|
|
|
|
return fRc;
|
|
}
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* Security_GetResponse
|
|
*
|
|
* Build a reponse to the server's challenge.
|
|
*
|
|
*****************************************************************************/
|
|
|
|
BOOL INTERNAL
|
|
Security_GetResponse(PWIN32AUTHSTATE pwas, LPSTR szChallenge)
|
|
{
|
|
BOOL fRc;
|
|
BYTE rgbChallenge[PLENTY_BIG];
|
|
int cb;
|
|
SecBuffer bufIn;
|
|
SecBufferDesc descIn;
|
|
|
|
cb = RFC1113_Decode(rgbChallenge, szChallenge);
|
|
|
|
#ifdef CHATTY
|
|
Squirt("Decoded %d bytes" EOL, cb);
|
|
#endif
|
|
|
|
/*
|
|
* Set up the buffers...
|
|
*/
|
|
descIn.ulVersion = SECBUFFER_VERSION;
|
|
descIn.cBuffers = 1;
|
|
descIn.pBuffers = &bufIn;
|
|
|
|
bufIn.cbBuffer = cb;
|
|
bufIn.BufferType = SECBUFFER_TOKEN;
|
|
bufIn.pvBuffer = rgbChallenge;
|
|
|
|
/*
|
|
* Use common worker function to generate an output string.
|
|
*/
|
|
|
|
fRc = Security_BuildOutString(
|
|
pwas, /* Authorization state */
|
|
&descIn, /* No input buffer */
|
|
&pwas->hCtxt, /* No source context */
|
|
&pwas->hCtxt, /* Destination context */
|
|
NULL); /* Server name */
|
|
|
|
return fRc;
|
|
}
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* Security_ReleaseCredentials
|
|
*
|
|
* pwas -> WIN32AUTHSTATE to track the state of this session
|
|
* ptszPackage - name of security package (e.g., "MSN")
|
|
*
|
|
*****************************************************************************/
|
|
|
|
void INTERNAL
|
|
Security_ReleaseCredentials(PWIN32AUTHSTATE pwas)
|
|
{
|
|
if (pwas->fHCtxtValid) {
|
|
g_psft->DeleteSecurityContext(&pwas->hCtxt);
|
|
pwas->fHCtxtValid = FALSE;
|
|
}
|
|
|
|
if (pwas->fHCredValid) {
|
|
g_psft->FreeCredentialHandle(&pwas->hCred);
|
|
pwas->fHCredValid = FALSE;
|
|
}
|
|
}
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* sendsz
|
|
*
|
|
* Send an asciiz string.
|
|
*
|
|
*****************************************************************************/
|
|
|
|
int
|
|
sendsz(SOCKET s, LPCSTR psz)
|
|
{
|
|
return send(s, psz, lstrlen(psz), 0);
|
|
}
|
|
|
|
#if 0
|
|
/*****************************************************************************
|
|
*
|
|
* POP3_Negotiate
|
|
*
|
|
* Perform an authenticated MSN logon.
|
|
*
|
|
*****************************************************************************/
|
|
|
|
void
|
|
POP3_Negotiate(PCONNECTIONSTATE pcxs)
|
|
{
|
|
WIN32AUTHSTATE was;
|
|
int cb;
|
|
|
|
/*
|
|
* Tell the server to go into MSN mode.
|
|
*/
|
|
sendsz(pcxs->ssfd, "AUTH MSN\r\n");
|
|
|
|
/*
|
|
* Wait for the Proceed.
|
|
*/
|
|
cb = recv(pcxs->ssfd, pcxs->buf, BUFSIZE, 0); /* read a hunk */
|
|
if (cb <= 0 || pcxs->buf[0] != '+') {
|
|
sendsz(pcxs->scfd, "-ERR Server lost 1\r\n");
|
|
return;
|
|
}
|
|
|
|
pcxs->buf[cb] = 0;
|
|
#ifdef CHATTY
|
|
Squirt("<%s", pcxs->buf);
|
|
#endif
|
|
|
|
if (!Security_AcquireCredentials(&was, TEXT("MSN"), NULL)) {
|
|
Die(TEXT("Cannot acquire credentials handle"));
|
|
}
|
|
|
|
if (!Security_GetNegotiation(&was)) {
|
|
Die(TEXT("Cannot get negotiation string"));
|
|
}
|
|
|
|
/*
|
|
* Now send the initial cookie.
|
|
*/
|
|
wsprintf(pcxs->buf, "%s\r\n", was.szBuffer);
|
|
sendsz(pcxs->ssfd, pcxs->buf);
|
|
#ifdef CHATTY
|
|
Squirt(">%s", pcxs->buf);
|
|
#endif
|
|
|
|
/*
|
|
* Response should be
|
|
*
|
|
* + <challenge>
|
|
*/
|
|
cb = recv(pcxs->ssfd, pcxs->buf, BUFSIZE, 0);
|
|
|
|
if (cb <= 0 || pcxs->buf[0] != '+') {
|
|
if (cb > 0) {
|
|
pcxs->buf[cb] = 0;
|
|
sendsz(pcxs->scfd, pcxs->buf);
|
|
} else {
|
|
sendsz(pcxs->scfd, "-ERR Server lost 2\r\n");
|
|
}
|
|
return;
|
|
}
|
|
|
|
#ifdef CHATTY
|
|
pcxs->buf[cb] = 0;
|
|
Squirt("<%s", pcxs->buf);
|
|
#endif
|
|
|
|
if (!Security_GetResponse(&was, pcxs->buf + 2)) {
|
|
Die(TEXT("Cannot build response"));
|
|
}
|
|
|
|
/*
|
|
* Now send the response.
|
|
*/
|
|
wsprintf(pcxs->buf, "%s\r\n", was.szBuffer);
|
|
sendsz(pcxs->ssfd, pcxs->buf);
|
|
#ifdef CHATTY
|
|
Squirt(">%s", pcxs->buf);
|
|
#endif
|
|
|
|
Security_ReleaseCredentials(&was);
|
|
}
|
|
|
|
//
|
|
// nntp: authinfo user blah
|
|
// authinfo pass blah
|
|
//
|
|
#endif
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* Proxy_Main
|
|
*
|
|
*****************************************************************************/
|
|
|
|
void
|
|
Proxy_Main(void)
|
|
{
|
|
HANDLE hThread;
|
|
DWORD dwThid;
|
|
|
|
WSADATA wsad;
|
|
|
|
/* -- Lazy! I should check the return code */
|
|
if (WSAStartup(0x0101, &wsad)) return;
|
|
|
|
hThread = CreateThread(0, 0, ProxyThread, &g_proxyPop, 0, &dwThid);
|
|
|
|
if (hThread) {
|
|
|
|
CloseHandle(hThread);
|
|
hThread = CreateThread(0, 0, ProxyThread, &g_proxyNntp1, 0, &dwThid);
|
|
if (hThread) {
|
|
CloseHandle(hThread);
|
|
hThread = CreateThread(0, 0, ProxyThread, &g_proxyNntp2, 0, &dwThid);
|
|
if (hThread) {
|
|
|
|
HWND hdlg;
|
|
MSG msg;
|
|
|
|
CloseHandle(hThread);
|
|
|
|
/*
|
|
* Create our UI window.
|
|
*/
|
|
hdlg = UI_Init();
|
|
while (GetMessage(&msg, 0, 0, 0)) {
|
|
if (IsDialogMessage(hdlg, &msg)) {
|
|
} else {
|
|
TranslateMessage(&msg);
|
|
DispatchMessage(&msg);
|
|
}
|
|
}
|
|
} else {
|
|
Squirt("Can't spawn NNTP socket thread 2; bye-bye" EOL);
|
|
}
|
|
} else {
|
|
Squirt("Can't spawn NNTP socket thread 1; bye-bye" EOL);
|
|
}
|
|
} else {
|
|
Squirt("Can't spawn POP3 socket thread; bye-bye" EOL);
|
|
}
|
|
}
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* Entry
|
|
*
|
|
*****************************************************************************/
|
|
|
|
void __cdecl
|
|
Entry(void)
|
|
{
|
|
g_hinst = GetModuleHandle(0);
|
|
|
|
if (LoadSecurityManager()) {
|
|
Proxy_Main();
|
|
}
|
|
|
|
if (g_hinstSecur) {
|
|
FreeLibrary(g_hinstSecur);
|
|
}
|
|
|
|
ExitProcess(0);
|
|
}
|