summaryrefslogtreecommitdiff
path: root/win/inspircd_win32wrapper.cpp
blob: 223fb648ebba170bf7c91a2891c7d32f15ed095e (plain)
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
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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
/*       +------------------------------------+
 *       | Inspire Internet Relay Chat Daemon |
 *       +------------------------------------+
 *
 *  InspIRCd: (C) 2002-2009 InspIRCd Development Team
 * See: http://wiki.inspircd.org/Credits
 *
 * This program is free but copyrighted software; see
 *            the file COPYING for details.
 *
 * ---------------------------------------------------
 */

#include "inspircd_win32wrapper.h"
#include "inspircd.h"
#include "configreader.h"
#include <string>
#include <errno.h>
#include <assert.h>
#define _WIN32_DCOM
#include <comdef.h>
#include <Wbemidl.h>

#pragma comment(lib, "wbemuuid.lib")
#pragma comment(lib, "comsuppwd.lib")
#pragma comment(lib, "winmm.lib")
using namespace std;

#ifndef INADDR_NONE
#define INADDR_NONE 0xffffffff
#endif

#include <mmsystem.h>

IWbemLocator *pLoc = NULL;
IWbemServices *pSvc = NULL;

/* This MUST remain static and delcared outside the class, so that WriteProcessMemory can reference it properly */
static DWORD owner_processid = 0;


int inet_aton(const char *cp, struct in_addr *addr)
{
	unsigned long ip = inet_addr(cp);
	addr->s_addr = ip;
	return (addr->s_addr == INADDR_NONE) ? 0 : 1;
}

const char *insp_inet_ntop(int af, const void *src, char *dst, socklen_t cnt)
{

	if (af == AF_INET)
	{
		struct sockaddr_in in;
		memset(&in, 0, sizeof(in));
		in.sin_family = AF_INET;
		memcpy(&in.sin_addr, src, sizeof(struct in_addr));
		getnameinfo((struct sockaddr *)&in, sizeof(struct sockaddr_in), dst, cnt, NULL, 0, NI_NUMERICHOST);
		return dst;
	}
	else if (af == AF_INET6)
	{
		struct sockaddr_in6 in;
		memset(&in, 0, sizeof(in));
		in.sin6_family = AF_INET6;
		memcpy(&in.sin6_addr, src, sizeof(struct in_addr6));
		getnameinfo((struct sockaddr *)&in, sizeof(struct sockaddr_in6), dst, cnt, NULL, 0, NI_NUMERICHOST);
		return dst;
	}
	return NULL;
}

int geteuid()
{
	return 1;
}

int insp_inet_pton(int af, const char *src, void *dst)
{
	sockaddr_in sa;
	int len = sizeof(SOCKADDR);
	int rv = WSAStringToAddress((LPSTR)src, af, NULL, (LPSOCKADDR)&sa, &len);
	if(rv >= 0)
	{
		if(WSAGetLastError() == 10022)			// Invalid Argument
			rv = 0;
		else
			rv = 1;
	}
	memcpy(dst, &sa.sin_addr, sizeof(struct in_addr));
	return rv;
}

void setcolor(int color_code)
{
	SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), color_code);
}

DIR * opendir(const char * path)
{
	std::string search_path = string(path) + "\\*.*";
	WIN32_FIND_DATA fd;
	HANDLE f = FindFirstFile(search_path.c_str(), &fd);
	if (f != INVALID_HANDLE_VALUE)
	{
		DIR * d = new DIR;
		memcpy(&d->find_data, &fd, sizeof(WIN32_FIND_DATA));
		d->find_handle = f;
		d->first = true;
		return d;
	}
	else
	{
		return 0;
	}
}

dirent * readdir(DIR * handle)
{
	if (handle->first)
		handle->first = false;
	else
	{
		if (!FindNextFile(handle->find_handle, &handle->find_data))
			return 0;
	}

	strncpy(handle->dirent_pointer.d_name, handle->find_data.cFileName, MAX_PATH);
	return &handle->dirent_pointer;
}

void closedir(DIR * handle)
{
	FindClose(handle->find_handle);
	delete handle;
}

const char * dlerror()
{
	static char errormessage[500];
	DWORD error = GetLastError();
	SetLastError(0);
	if (error == 0)
		return 0;

	FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, 0, error, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)errormessage, 500, 0);
	return errormessage;
}

#define TRED FOREGROUND_RED | FOREGROUND_INTENSITY
#define TGREEN FOREGROUND_GREEN | FOREGROUND_INTENSITY
#define TYELLOW FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_INTENSITY
#define TNORMAL FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE
#define TWHITE TNORMAL | FOREGROUND_INTENSITY
#define TBLUE FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_INTENSITY

/* Handles colors in printf */
int printf_c(const char * format, ...)
{
	// Better hope we're not multithreaded, otherwise we'll have chickens crossing the road other side to get the to :P
	static char message[MAXBUF];
	static char temp[MAXBUF];
	int color1, color2;

	/* parse arguments */
	va_list ap;
	va_start(ap, format);
	vsnprintf(message, 500, format, ap);
	va_end(ap);

	/* search for unix-style escape sequences */
	int t;
	int c = 0;
	const char * p = message;
	while (*p != 0)
	{
		if (*p == '\033')
		{
			// Escape sequence -> copy into the temp buffer, and parse the color.
			p++;
			t = 0;
			while ((*p) && (*p != 'm'))
			{
				temp[t++] = *p;
				++p;
			}

			temp[t] = 0;
			p++;

			if (*temp == '[')
			{
				if (sscanf(temp, "[%u;%u", &color1, &color2) == 2)
				{
					switch(color2)
					{
					case 32:		// Green
						SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_GREEN | FOREGROUND_INTENSITY);		// Yellow
						break;

					default:		// Unknown
						// White
						SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY);
						break;
					}
				}
				else
				{
					switch (*(temp+1))
					{
						case '0':
							// Returning to normal colour.
							SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);
							break;

						case '1':
							// White
							SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), TWHITE);
							break;

						default:
							char message[50];
							sprintf(message, "Unknown color code: %s", temp);
							MessageBox(0, message, message, MB_OK);
							break;
					}
				}
			}
		}

		putchar(*p);
		++c;
		++p;
	}

	return c;
}

int optind = 1;
char optarg[514];
int getopt_long_only(int ___argc, char *const *___argv, const char *__shortopts, const struct option *__longopts, int *__longind)
{
	// burlex todo: handle the shortops, at the moment it only works with longopts.

	if (___argc == 1 || optind == ___argc)			// No arguments (apart from filename)
		return -1;

	const char * opt = ___argv[optind];
	optind++;

	// if we're not an option, return an error.
	if (strnicmp(opt, "--", 2) != 0)
		return 1;
	else
		opt += 2;


	// parse argument list
	int i = 0;
	for (; __longopts[i].name != 0; ++i)
	{
		if (!strnicmp(__longopts[i].name, opt, strlen(__longopts[i].name)))
		{
			// woot, found a valid argument =)
			char * par = 0;
			if ((optind) != ___argc)
			{
				// grab the parameter from the next argument (if its not another argument)
				if (strnicmp(___argv[optind], "--", 2) != 0)
				{
//					optind++;		// Trash this next argument, we won't be needing it.
					par = ___argv[optind-1];
				}
			}			

			// increment the argument for next time
//			optind++;

			// determine action based on type
			if (__longopts[i].has_arg == required_argument && !par)
			{
				// parameter missing and its a required parameter option
				return 1;
			}

			// store argument in optarg
			if (par)
				strncpy(optarg, par, 514);

			if (__longopts[i].flag != 0)
			{
				// this is a variable, we have to set it if this argument is found.
				*__longopts[i].flag = 1;
				return 0;
			}
			else
			{
				if (__longopts[i].val == -1 || par == 0)
					return 1;
				
				return __longopts[i].val;
			}			
			break;
		}
	}

	// return 1 (invalid argument)
	return 1;
}

/* These three functions were created from looking at how ares does it
 * (...and they look far tidier in C++)
 */

/* Get active nameserver */
bool GetNameServer(HKEY regkey, const char *key, char* &output)
{
	DWORD size = 0;
	DWORD result = RegQueryValueEx(regkey, key, 0, NULL, NULL, &size);
	if (((result != ERROR_SUCCESS) && (result != ERROR_MORE_DATA)) || (!size))
		return false;

	output = new char[size+1];

	if ((RegQueryValueEx(regkey, key, 0, NULL, (LPBYTE)output, &size) != ERROR_SUCCESS) || (!*output))
	{
		delete output;
		return false;
	}
	return true;
}

/* Check a network interface for its nameserver */
bool GetInterface(HKEY regkey, const char *key, char* &output)
{
	char buf[39];
	DWORD size = 39;
	int idx = 0;
	HKEY top;

	while (RegEnumKeyEx(regkey, idx++, buf, &size, 0, NULL, NULL, NULL) != ERROR_NO_MORE_ITEMS)
	{
		size = 39;
		if (RegOpenKeyEx(regkey, buf, 0, KEY_QUERY_VALUE, &top) != ERROR_SUCCESS)
			continue;
		int rc = GetNameServer(top, key, output);
		RegCloseKey(top);
		if (rc)
			return true;
	}
	return false;
}


std::string FindNameServerWin()
{
	std::string returnval = "127.0.0.1";
	HKEY top, key;
	char* dns = NULL;

	/* Lets see if the correct registry hive and tree exist */
	if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, "System\\CurrentControlSet\\Services\\Tcpip\\Parameters", 0, KEY_READ, &top) == ERROR_SUCCESS)
	{
		/* If they do, attempt to get the nameserver name */
		RegOpenKeyEx(top, "Interfaces", 0, KEY_QUERY_VALUE|KEY_ENUMERATE_SUB_KEYS, &key);
		if ((GetNameServer(top, "NameServer", dns)) || (GetNameServer(top, "DhcpNameServer", dns))
			|| (GetInterface(key, "NameServer", dns)) || (GetInterface(key, "DhcpNameServer", dns)))
		{
			if (dns)
			{
				returnval = dns;
				delete dns;
			}
		}
		RegCloseKey(key);
		RegCloseKey(top);
	}
	return returnval;
}


void ClearConsole()
{
	COORD coordScreen = { 0, 0 };    /* here's where we'll home the cursor */
	HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
	DWORD cCharsWritten;
	CONSOLE_SCREEN_BUFFER_INFO csbi; /* to get buffer info */ 
	DWORD dwConSize;                 /* number of character cells in the current buffer */ 

	/* get the number of character cells in the current buffer */ 

	if (GetConsoleScreenBufferInfo( hConsole, &csbi ))
	{
		dwConSize = csbi.dwSize.X * csbi.dwSize.Y;
		/* fill the entire screen with blanks */ 
		if (FillConsoleOutputCharacter( hConsole, (TCHAR) ' ', dwConSize, coordScreen, &cCharsWritten ))
		{
			/* get the current text attribute */ 
			if (GetConsoleScreenBufferInfo( hConsole, &csbi ))
			{
				/* now set the buffer's attributes accordingly */
				if (FillConsoleOutputAttribute( hConsole, csbi.wAttributes, dwConSize, coordScreen, &cCharsWritten ))
				{
					/* put the cursor at (0, 0) */
					SetConsoleCursorPosition( hConsole, coordScreen );
				}
			}
		}
	}
	return;
}

/* Many inspircd classes contain function pointers/functors which can be changed to point at platform specific implementations
 * of code. This function repoints these pointers and functors so that calls are windows specific.
 */
void ChangeWindowsSpecificPointers()
{
	Instance->Logs->Log("win32",DEBUG,"Changing to windows specific pointer and functor set");
}

DWORD WindowsForkStart()
{
        /* Windows implementation of fork() :P */
	if (owner_processid)
		return 0;

        char module[MAX_PATH];
        if(!GetModuleFileName(NULL, module, MAX_PATH))
        {
                printf("GetModuleFileName() failed.\n");
                return false;
        }

        STARTUPINFO startupinfo;
        PROCESS_INFORMATION procinfo;
        ZeroMemory(&startupinfo, sizeof(STARTUPINFO));
        ZeroMemory(&procinfo, sizeof(PROCESS_INFORMATION));

        // Fill in the startup info struct
        GetStartupInfo(&startupinfo);

        /* Default creation flags create the processes suspended */
        DWORD startupflags = CREATE_SUSPENDED;

        /* On windows 2003/XP and above, we can use the value
         * CREATE_PRESERVE_CODE_AUTHZ_LEVEL which gives more access
         * to the process which we may require on these operating systems.
         */
        OSVERSIONINFO vi;
        vi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
        GetVersionEx(&vi);
        if ((vi.dwMajorVersion >= 5) && (vi.dwMinorVersion > 0))
                startupflags |= CREATE_PRESERVE_CODE_AUTHZ_LEVEL;

        // Launch our "forked" process.
        BOOL bSuccess = CreateProcess ( module, // Module (exe) filename
                strdup(GetCommandLine()),       // Command line (exe plus parameters from the OS)
                                                // NOTE: We cannot return the direct value of the
                                                // GetCommandLine function here, as the pointer is
                                                // passed straight to the child process, and will be
                                                // invalid once we exit as it goes out of context.
                                                // strdup() seems ok, though.
                0,                              // PROCESS_SECURITY_ATTRIBUTES
                0,                              // THREAD_SECURITY_ATTRIBUTES
                TRUE,                           // We went to inherit handles.
                startupflags,                   // Allow us full access to the process and suspend it.
                0,                              // ENVIRONMENT
                0,                              // CURRENT_DIRECTORY
                &startupinfo,                   // startup info
                &procinfo);                     // process info

        if(!bSuccess)
        {
                printf("CreateProcess() error: %s\n", dlerror());
                return false;
        }

        // Set the owner process id in the target process.
        SIZE_T written = 0;
        DWORD pid = GetCurrentProcessId();
        if(!WriteProcessMemory(procinfo.hProcess, &owner_processid, &pid, sizeof(DWORD), &written) || written != sizeof(DWORD))
        {
                printf("WriteProcessMemory() failed: %s\n", dlerror());
                return false;
        }

        // Resume the other thread (let it start)
        ResumeThread(procinfo.hThread);

        // Wait for the new process to kill us. If there is some error, the new process will end and we will end up at the next line.
        WaitForSingleObject(procinfo.hProcess, INFINITE);

        // If we hit this it means startup failed, default to 14 if this fails.
        DWORD ExitCode = 14;
        GetExitCodeProcess(procinfo.hProcess, &ExitCode);
        CloseHandle(procinfo.hThread);
        CloseHandle(procinfo.hProcess);
        return ExitCode;
}

void WindowsForkKillOwner()
{
        HANDLE hProcess = OpenProcess(PROCESS_TERMINATE, FALSE, owner_processid);
        if(!hProcess || !owner_processid)
        {
                printf("Could not open process id %u: %s.\n", owner_processid, dlerror());
                Instance->Exit(14);
        }

        // die die die
        if(!TerminateProcess(hProcess, 0))
        {
                printf("Could not TerminateProcess(): %s\n", dlerror());
                Instance->Exit(14);
        }

        CloseHandle(hProcess);
}

bool ValidateDnsServer(ServerConfig* conf, const char* tag, const char* value, ValueItem &data)
{
	if (!*(data.GetString()))
	{
		std::string nameserver;
		conf->GetInstance()->Logs->Log("win32",DEFAULT,"WARNING: <dns:server> not defined, attempting to find working server in the registry...");
		nameserver = FindNameServerWin();
		/* Windows stacks multiple nameservers in one registry key, seperated by commas.
		 * Spotted by Cataclysm.
		 */
		if (nameserver.find(',') != std::string::npos)
			nameserver = nameserver.substr(0, nameserver.find(','));
		/* Just to be FUCKING AKWARD, windows fister... err i mean vista...
		 * seperates the nameservers with spaces instead.
		 */
		if (nameserver.find(' ') != std::string::npos)
			nameserver = nameserver.substr(0, nameserver.find(' '));
		data.Set(nameserver.c_str());
		conf->GetInstance()->Logs->Log("win32",DEFAULT,"<dns:server> set to '%s' as first active resolver in registry.", nameserver.c_str());
	}
	return true;
}

int gettimeofday(struct timeval * tv, void * tz)
{
	if(tv == NULL)
		return -1;

	DWORD mstime = timeGetTime();
	tv->tv_sec   = time(NULL);
	tv->tv_usec  = (mstime - (tv->tv_sec * 1000)) * 1000;
	return 0;	
}

/* Initialise WMI. Microsoft have the silliest ideas about easy ways to
 * obtain the CPU percentage of a running process!
 * The whole API for this uses evil DCOM and is entirely unicode, giving
 * all results and accepting queries as wide strings.
 */
bool initwmi()
{
	HRESULT hres;

	/* Initialise COM. This can kill babies. */
	hres =  CoInitializeEx(0, COINIT_MULTITHREADED); 
	if (FAILED(hres))
		return false;

	/* COM security. This stuff kills kittens */
	hres =  CoInitializeSecurity(NULL, -1, NULL, NULL, RPC_C_AUTHN_LEVEL_DEFAULT,
		RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE, NULL);

	if (FAILED(hres))
	{
		CoUninitialize();
		return false;
	}
    
	/* Instance to COM object */
	pLoc = NULL;
	hres = CoCreateInstance(CLSID_WbemLocator, 0, CLSCTX_INPROC_SERVER, IID_IWbemLocator, (LPVOID*)&pLoc);
 
	if (FAILED(hres))
	{
		CoUninitialize();
		return false;
	}

	pSvc = NULL;

	/* Connect to DCOM server */
	hres = pLoc->ConnectServer(_bstr_t(L"ROOT\\CIMV2"), NULL, NULL, 0, NULL, 0, 0, &pSvc);
    
	/* That didn't work, maybe no kittens found to kill? */
	if (FAILED(hres))
	{
		pLoc->Release();
		CoUninitialize();
		return false;
	}

	/* Don't even ASK what this does. I'm still not too sure myself. */
	hres = CoSetProxyBlanket(pSvc, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, NULL, RPC_C_AUTHN_LEVEL_CALL,
		RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE);

	if (FAILED(hres))
	{
		pSvc->Release();
		pLoc->Release();     
		CoUninitialize();
		return false;
	}
	return true;
}

void donewmi()
{
	pSvc->Release();
	pLoc->Release();
	CoUninitialize();
}

/* Return the CPU usage in percent of this process */
int getcpu()
{
	HRESULT hres;
	int cpu = -1;

	/* Use WQL, similar to SQL, to construct a query that lists the cpu usage and pid of all processes */
	IEnumWbemClassObject* pEnumerator = NULL;

	BSTR Language = SysAllocString(L"WQL");
	BSTR Query    = SysAllocString(L"Select PercentProcessorTime,IDProcess from Win32_PerfFormattedData_PerfProc_Process");

	hres = pSvc->ExecQuery(Language, Query, WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY, NULL, &pEnumerator);

	/* Query didn't work */
	if (!FAILED(hres))
	{
		IWbemClassObject *pclsObj = NULL;
		ULONG uReturn = 0;

		/* Iterate the query results */
		while (pEnumerator)
		{
			VARIANT vtProp;
			/* Next item */
			HRESULT hr = pEnumerator->Next(WBEM_INFINITE, 1, &pclsObj, &uReturn);

			/* No more items left */
			if (uReturn == 0)
				break;

			/* Find process ID */
			hr = pclsObj->Get(L"IDProcess", 0, &vtProp, 0, 0);
			if (!FAILED(hr))
			{
				/* Matches our process ID? */
				if (vtProp.uintVal == GetCurrentProcessId())
				{
					VariantClear(&vtProp);
					/* Get CPU percentage for this process */
					hr = pclsObj->Get(L"PercentProcessorTime", 0, &vtProp, 0, 0);
					if (!FAILED(hr))
					{
						/* Deal with wide string ickyness. Who in their right
						 * mind puts a number in a bstrVal wide string item?!
						 */
						VariantClear(&vtProp);
						cpu = 0;
						std::wstringstream out(vtProp.bstrVal);
						out >> cpu;
						break;
					}
				}
			}
		}

		pEnumerator->Release();
		pclsObj->Release();
	}

	SysFreeString(Language);
	SysFreeString(Query);

	return cpu;
}