/***************************************************************************** * * 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 * * + */ 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); }