Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

953 lines
18 KiB

  1. /*++ BUILD Version: 0000 // Increment this if a change has global effects
  2. Copyright (c) 1996-1998 Microsoft Corporation
  3. Module Name:
  4. tsec.c
  5. Abstract:
  6. A sample administration DLL
  7. Author:
  8. Revision History:
  9. --*/
  10. #include <windows.h>
  11. #include <tapi.h>
  12. #include <tapclntp.h> // private\inc\tapclntp.h
  13. #include <tlnklist.h>
  14. #include "tsec.h"
  15. HINSTANCE ghInst;
  16. LIST_ENTRY gClientListHead;
  17. CRITICAL_SECTION gCritSec;
  18. DEVICECHANGECALLBACK glpfnLineChangeCallback = NULL;
  19. DEVICECHANGECALLBACK glpfnPhoneChangeCallback = NULL;
  20. void
  21. FreeClient(
  22. PMYCLIENT pClient
  23. );
  24. BOOL
  25. WINAPI
  26. DllMain(
  27. HANDLE hDLL,
  28. DWORD dwReason,
  29. LPVOID lpReserved
  30. )
  31. {
  32. switch (dwReason)
  33. {
  34. case DLL_PROCESS_ATTACH:
  35. {
  36. #if DBG
  37. gdwDebugLevel = 0;
  38. #endif
  39. DBGOUT((2, "DLL_PROCESS_ATTACH"));
  40. ghInst = hDLL;
  41. InitializeCriticalSection (&gCritSec);
  42. InitializeListHead (&gClientListHead);
  43. break;
  44. }
  45. case DLL_PROCESS_DETACH:
  46. {
  47. PMYCLIENT pClient;
  48. //
  49. // Clean up client list (no need to enter crit sec since
  50. // process detaching)
  51. //
  52. while (!IsListEmpty (&gClientListHead))
  53. {
  54. LIST_ENTRY *pEntry = RemoveHeadList (&gClientListHead);
  55. pClient = CONTAINING_RECORD (pEntry, MYCLIENT, ListEntry);
  56. FreeClient(pClient);
  57. }
  58. DeleteCriticalSection (&gCritSec);
  59. break;
  60. }
  61. case DLL_THREAD_ATTACH:
  62. case DLL_THREAD_DETACH:
  63. break;
  64. } // switch
  65. return TRUE;
  66. }
  67. void
  68. FreeClient(
  69. PMYCLIENT pClient
  70. )
  71. {
  72. GlobalFree (pClient->pszUserName);
  73. GlobalFree (pClient->pszDomainName);
  74. GlobalFree (pClient->pLineDeviceMap);
  75. GlobalFree (pClient->pPhoneDeviceMap);
  76. GlobalFree (pClient);
  77. }
  78. LONG
  79. GetAndParseAMapping(
  80. LPCWSTR pszFullName,
  81. LPCWSTR pszType,
  82. LPTAPIPERMANENTID *ppDevices,
  83. LPDWORD pdwDevices
  84. )
  85. {
  86. LPWSTR pszDevices = NULL, pszHold1, pszHold2;
  87. DWORD dwSize, dwReturn, dwDevices;
  88. DWORD dwPermanentDeviceID;
  89. BOOL bBreak = FALSE;
  90. dwSize = MAXDEVICESTRINGLEN;
  91. // get the string
  92. do
  93. {
  94. if (pszDevices != NULL)
  95. {
  96. dwSize *= 2;
  97. GlobalFree (pszDevices);
  98. }
  99. pszDevices = (LPWSTR) GlobalAlloc (GPTR, dwSize * sizeof(WCHAR));
  100. if (!pszDevices)
  101. {
  102. return LINEERR_NOMEM;
  103. }
  104. dwReturn = GetPrivateProfileString(
  105. pszFullName,
  106. pszType,
  107. SZEMPTYSTRING,
  108. pszDevices,
  109. dwSize,
  110. SZINIFILE
  111. );
  112. if (dwReturn == 0)
  113. {
  114. // valid case. the user has no
  115. // devices, so just return 0
  116. GlobalFree(pszDevices);
  117. *pdwDevices = 0;
  118. *ppDevices = NULL;
  119. return 0;
  120. }
  121. } while (dwReturn == (dwSize - 1));
  122. // parse the string
  123. //
  124. // the string looks line px, x, py, y,pz, z where x,y and z are
  125. // tapi permanent device IDs, and px, py, and pz are the
  126. // permanent provider IDs for the corresponding devices.
  127. pszHold1 = pszDevices;
  128. dwDevices = 0;
  129. // first, count the ,s so we know how many devices there are
  130. while (*pszHold1 != L'\0')
  131. {
  132. if (*pszHold1 == L',')
  133. {
  134. dwDevices++;
  135. }
  136. pszHold1++;
  137. }
  138. dwDevices++;
  139. dwDevices /= 2;
  140. // alloc line mapping, this is freed later
  141. *ppDevices = (LPTAPIPERMANENTID) GlobalAlloc(
  142. GPTR,
  143. dwDevices * sizeof ( TAPIPERMANENTID )
  144. );
  145. if (!*ppDevices)
  146. {
  147. GlobalFree (pszDevices);
  148. return LINEERR_NOMEM;
  149. }
  150. pszHold1 = pszHold2 = pszDevices;
  151. dwDevices = 0;
  152. // go through string
  153. while (TRUE)
  154. {
  155. // wait for ,
  156. while ((*pszHold2 != L'\0') && *pszHold2 != L',')
  157. {
  158. pszHold2++;
  159. }
  160. if (*pszHold2 == L',')
  161. *pszHold2 = L'\0';
  162. else
  163. {
  164. bBreak = TRUE;
  165. }
  166. // save the id
  167. (*ppDevices)[dwDevices].dwProviderID = _wtol(pszHold1);
  168. // if we hit the end, break out
  169. // note here that this is an unmatched provider id
  170. // but we have inc'ed the dwdevices, so this element will be ignored
  171. if (bBreak)
  172. {
  173. break;
  174. }
  175. pszHold2++;
  176. pszHold1 = pszHold2;
  177. // wait for ,
  178. while ((*pszHold2 != L'\0') && *pszHold2 != L',')
  179. {
  180. pszHold2++;
  181. }
  182. if (*pszHold2 == L',')
  183. {
  184. *pszHold2 = L'\0';
  185. }
  186. else
  187. {
  188. bBreak = TRUE;
  189. }
  190. // save the id
  191. (*ppDevices)[dwDevices].dwDeviceID = _wtol(pszHold1);
  192. dwDevices++;
  193. // if we hit the end, break out
  194. if (bBreak)
  195. {
  196. break;
  197. }
  198. pszHold2++;
  199. pszHold1 = pszHold2;
  200. }
  201. *pdwDevices = dwDevices;
  202. GlobalFree(pszDevices);
  203. return 0; // success
  204. }
  205. LONG
  206. GetMappings(
  207. LPCWSTR pszDomainName,
  208. LPCWSTR pszUserName,
  209. LPTAPIPERMANENTID *ppLineMapping,
  210. LPDWORD pdwLines,
  211. LPTAPIPERMANENTID *ppPhoneMapping,
  212. LPDWORD pdwPhones
  213. )
  214. {
  215. LPWSTR pszFullName;
  216. DWORD dwSize;
  217. LONG lResult;
  218. // put the username and domain name together
  219. // for a full name: domain\user
  220. // in the " + 2" 1 is for \ and 1 is for null terminator
  221. pszFullName = (LPWSTR)GlobalAlloc(
  222. GPTR,
  223. ( lstrlen(pszDomainName) + lstrlen(pszUserName) + 2 ) * sizeof(WCHAR)
  224. );
  225. if (!pszFullName)
  226. {
  227. return LINEERR_NOMEM;
  228. }
  229. // put them together
  230. wsprintf(
  231. pszFullName,
  232. L"%s\\%s",
  233. pszDomainName,
  234. pszUserName
  235. );
  236. if (lResult = GetAndParseAMapping(
  237. pszFullName,
  238. SZLINES,
  239. ppLineMapping,
  240. pdwLines
  241. ))
  242. {
  243. GlobalFree(pszFullName);
  244. return lResult;
  245. }
  246. if (lResult = GetAndParseAMapping(
  247. pszFullName,
  248. SZPHONES,
  249. ppPhoneMapping,
  250. pdwPhones
  251. ))
  252. {
  253. if (NULL != ppLineMapping)
  254. {
  255. GlobalFree (*ppLineMapping);
  256. }
  257. GlobalFree (pszFullName);
  258. return lResult;
  259. }
  260. GlobalFree(pszFullName);
  261. return 0;
  262. }
  263. LONG
  264. CLIENTAPI
  265. TAPICLIENT_Load(
  266. LPDWORD pdwAPIVersion,
  267. DEVICECHANGECALLBACK lpfnLineChangeCallback,
  268. DEVICECHANGECALLBACK lpfnPhoneChangeCallback,
  269. DWORD Reserved
  270. )
  271. {
  272. if (*pdwAPIVersion > TAPI_CURRENT_VERSION)
  273. {
  274. *pdwAPIVersion = TAPI_CURRENT_VERSION;
  275. }
  276. glpfnLineChangeCallback = lpfnLineChangeCallback;
  277. glpfnPhoneChangeCallback = lpfnPhoneChangeCallback;
  278. return 0;
  279. }
  280. void
  281. CLIENTAPI
  282. TAPICLIENT_Free(
  283. void
  284. )
  285. {
  286. return;
  287. }
  288. LONG
  289. CLIENTAPI
  290. TAPICLIENT_ClientInitialize(
  291. LPCWSTR pszDomainName,
  292. LPCWSTR pszUserName,
  293. LPCWSTR pszMachineName,
  294. LPHMANAGEMENTCLIENT phmClient
  295. )
  296. {
  297. PMYCLIENT pNewClient;
  298. LPTAPIPERMANENTID pLineMapping, pPhoneMapping;
  299. DWORD dwNumLines, dwNumPhones;
  300. LONG lResult;
  301. // first, get the device mappings
  302. // if this fails, most likely the user
  303. // has access to no lines
  304. if (lResult = GetMappings(
  305. pszDomainName,
  306. pszUserName,
  307. &pLineMapping,
  308. &dwNumLines,
  309. &pPhoneMapping,
  310. &dwNumPhones
  311. ))
  312. {
  313. return lResult;
  314. }
  315. // alloc a client structure
  316. pNewClient = (PMYCLIENT) GlobalAlloc (GPTR, sizeof(MYCLIENT));
  317. if (!pNewClient)
  318. {
  319. return LINEERR_NOMEM;
  320. }
  321. // alloc space for the name
  322. pNewClient->pszUserName = (LPWSTR) GlobalAlloc(
  323. GPTR,
  324. (lstrlen(pszUserName) + 1) * sizeof(WCHAR)
  325. );
  326. if (!pNewClient->pszUserName)
  327. {
  328. GlobalFree(pNewClient);
  329. return LINEERR_NOMEM;
  330. }
  331. pNewClient->pszDomainName = (LPWSTR) GlobalAlloc(
  332. GPTR,
  333. (lstrlen(pszDomainName) +1) * sizeof(WCHAR)
  334. );
  335. if (!pNewClient->pszDomainName)
  336. {
  337. GlobalFree(pNewClient->pszUserName);
  338. GlobalFree(pNewClient);
  339. return LINEERR_NOMEM;
  340. }
  341. // initialize stuff
  342. lstrcpy (pNewClient->pszUserName, pszUserName);
  343. lstrcpy (pNewClient->pszDomainName, pszDomainName);
  344. pNewClient->pLineDeviceMap = pLineMapping;
  345. pNewClient->pPhoneDeviceMap = pPhoneMapping;
  346. pNewClient->dwNumLines = dwNumLines;
  347. pNewClient->dwNumPhones = dwNumPhones;
  348. pNewClient->dwKey = TSECCLIENT_KEY;
  349. // insert into list of clients
  350. EnterCriticalSection (&gCritSec);
  351. InsertHeadList (&gClientListHead, &pNewClient->ListEntry);
  352. LeaveCriticalSection (&gCritSec);
  353. // give TAPI the hmClient
  354. *phmClient = (HMANAGEMENTCLIENT)pNewClient;
  355. return 0;
  356. }
  357. LONG
  358. CLIENTAPI
  359. TAPICLIENT_ClientShutdown(
  360. HMANAGEMENTCLIENT hmClient
  361. )
  362. {
  363. PMYCLIENT pClient;
  364. pClient = (PMYCLIENT) hmClient;
  365. EnterCriticalSection (&gCritSec);
  366. try
  367. {
  368. if (pClient->dwKey == TSECCLIENT_KEY)
  369. {
  370. pClient->dwKey = 0;
  371. RemoveEntryList (&pClient->ListEntry);
  372. }
  373. else
  374. {
  375. pClient = NULL;
  376. }
  377. }
  378. except (EXCEPTION_EXECUTE_HANDLER)
  379. {
  380. pClient = NULL;
  381. }
  382. LeaveCriticalSection (&gCritSec);
  383. if (pClient)
  384. {
  385. FreeClient (pClient);
  386. }
  387. return 0;
  388. }
  389. LONG
  390. CLIENTAPI
  391. TAPICLIENT_GetDeviceAccess(
  392. HMANAGEMENTCLIENT hmClient,
  393. HTAPICLIENT htClient,
  394. LPTAPIPERMANENTID pLineDeviceMap,
  395. PDWORD pdwLineDevices,
  396. LPTAPIPERMANENTID pPhoneDeviceMap,
  397. PDWORD pdwPhoneDevices
  398. )
  399. {
  400. LONG lResult;
  401. PMYCLIENT pClient = (PMYCLIENT) hmClient;
  402. EnterCriticalSection (&gCritSec);
  403. try
  404. {
  405. if (pClient->dwKey != TSECCLIENT_KEY)
  406. {
  407. pClient = NULL;
  408. }
  409. }
  410. except (EXCEPTION_EXECUTE_HANDLER)
  411. {
  412. pClient = NULL;
  413. }
  414. if (pClient)
  415. {
  416. // would need to critical section this stuff
  417. // if we added devices dynamically
  418. if (*pdwLineDevices < pClient->dwNumLines)
  419. {
  420. *pdwLineDevices = pClient->dwNumLines;
  421. lResult = LINEERR_STRUCTURETOOSMALL;
  422. goto LeaveCritSec;
  423. }
  424. CopyMemory(
  425. pLineDeviceMap,
  426. pClient->pLineDeviceMap,
  427. pClient->dwNumLines * sizeof( TAPIPERMANENTID )
  428. );
  429. *pdwLineDevices = pClient->dwNumLines;
  430. if (*pdwPhoneDevices < pClient->dwNumPhones)
  431. {
  432. *pdwPhoneDevices = pClient->dwNumPhones;
  433. lResult = LINEERR_STRUCTURETOOSMALL;
  434. goto LeaveCritSec;
  435. }
  436. CopyMemory(
  437. pPhoneDeviceMap,
  438. pClient->pPhoneDeviceMap,
  439. pClient->dwNumPhones * sizeof( TAPIPERMANENTID )
  440. );
  441. *pdwPhoneDevices = pClient->dwNumPhones;
  442. pClient->htClient = htClient;
  443. lResult = 0;
  444. }
  445. else
  446. {
  447. lResult = LINEERR_INVALPOINTER;
  448. }
  449. LeaveCritSec:
  450. LeaveCriticalSection (&gCritSec);
  451. return lResult;
  452. }
  453. LONG
  454. CLIENTAPI
  455. TAPICLIENT_LineAddToConference(
  456. HMANAGEMENTCLIENT hmClient,
  457. LPTAPIPERMANENTID pID,
  458. LPLINECALLINFO lpConsultCallCallInfo
  459. )
  460. {
  461. return 0;
  462. }
  463. LONG
  464. CLIENTAPI
  465. TAPICLIENT_LineBlindTransfer(
  466. HMANAGEMENTCLIENT hmClient,
  467. LPTAPIPERMANENTID pID,
  468. LPWSTR lpszDestAddress,
  469. LPDWORD lpdwSize,
  470. LPDWORD pdwCountryCodeOut
  471. )
  472. {
  473. return 0;
  474. }
  475. LONG
  476. CLIENTAPI
  477. TAPICLIENT_LineConfigDialog(
  478. HMANAGEMENTCLIENT hmClient,
  479. LPTAPIPERMANENTID pID,
  480. LPCWSTR lpszDeviceClass
  481. )
  482. {
  483. return 0;
  484. }
  485. LONG
  486. CLIENTAPI
  487. TAPICLIENT_LineDial(
  488. HMANAGEMENTCLIENT hmClient,
  489. LPTAPIPERMANENTID pID,
  490. DWORD Reserved,
  491. LPWSTR lpszDestAddressIn,
  492. LPDWORD pdwSize,
  493. LPDWORD pdwCountyCode
  494. )
  495. {
  496. return 0;
  497. }
  498. LONG
  499. CLIENTAPI
  500. TAPICLIENT_LineForward(
  501. HMANAGEMENTCLIENT hmClient,
  502. LPTAPIPERMANENTID pID,
  503. LPLINEFORWARDLIST lpFowardListIn,
  504. LPDWORD pdwSize,
  505. LPLINECALLPARAMS lpCallParamsIn,
  506. LPDWORD pdwParamsSize
  507. )
  508. {
  509. return 0;
  510. }
  511. LONG
  512. CLIENTAPI
  513. TAPICLIENT_LineGenerateDigits(
  514. HMANAGEMENTCLIENT hmClient,
  515. LPTAPIPERMANENTID pID,
  516. DWORD Reserved,
  517. LPCWSTR lpszDigits
  518. )
  519. {
  520. return 0;
  521. }
  522. LONG
  523. CLIENTAPI
  524. TAPICLIENT_LineMakeCall(
  525. HMANAGEMENTCLIENT hmClient,
  526. LPTAPIPERMANENTID pID,
  527. DWORD dwReserved,
  528. LPWSTR lpszDestAddress,
  529. LPDWORD pdwSize,
  530. LPDWORD pdwCountryCode,
  531. LPLINECALLPARAMS lpCallParams,
  532. LPDWORD pdwCallParamsSize
  533. )
  534. {
  535. return 0;
  536. }
  537. LONG
  538. CLIENTAPI
  539. TAPICLIENT_LineOpen(
  540. HMANAGEMENTCLIENT hmClient,
  541. LPTAPIPERMANENTID pID,
  542. DWORD dwAPIVersion,
  543. DWORD dwExtVersion,
  544. DWORD dwPrivileges,
  545. DWORD dwMediaModes,
  546. LPLINECALLPARAMS lpCallParamsIn,
  547. LPDWORD pdwParamsSize
  548. )
  549. {
  550. return 0;
  551. }
  552. LONG
  553. CLIENTAPI
  554. TAPICLIENT_LineRedirect(
  555. HMANAGEMENTCLIENT hmClient,
  556. LPTAPIPERMANENTID pID,
  557. LPWSTR lpszDestAddress,
  558. LPDWORD pdwSize,
  559. LPDWORD pdwCountryCode
  560. )
  561. {
  562. return 0;
  563. }
  564. LONG
  565. CLIENTAPI
  566. TAPICLIENT_LineSetCallData(
  567. HMANAGEMENTCLIENT hmClient,
  568. LPTAPIPERMANENTID pID,
  569. LPVOID lpCallData,
  570. LPDWORD pdwSize
  571. )
  572. {
  573. return 0;
  574. }
  575. LONG
  576. CLIENTAPI
  577. TAPICLIENT_LineSetCallParams(
  578. HMANAGEMENTCLIENT hmClient,
  579. LPTAPIPERMANENTID pID,
  580. DWORD dwBearerMode,
  581. DWORD dwMinRate,
  582. DWORD dwMaxRate,
  583. LPLINEDIALPARAMS lpDialParams
  584. )
  585. {
  586. return 0;
  587. }
  588. LONG
  589. CLIENTAPI
  590. TAPICLIENT_LineSetCallPrivilege(
  591. HMANAGEMENTCLIENT hmClient,
  592. LPTAPIPERMANENTID pID,
  593. DWORD dwCallPrivilege
  594. )
  595. {
  596. return 0;
  597. }
  598. LONG
  599. CLIENTAPI
  600. TAPICLIENT_LineSetCallTreatment(
  601. HMANAGEMENTCLIENT hmClient,
  602. LPTAPIPERMANENTID pID,
  603. DWORD dwCallTreatment
  604. )
  605. {
  606. return 0;
  607. }
  608. LONG
  609. CLIENTAPI
  610. TAPICLIENT_LineSetCurrentLocation(
  611. HMANAGEMENTCLIENT hmClient,
  612. LPTAPIPERMANENTID pID,
  613. LPDWORD dwLocation
  614. )
  615. {
  616. return 0;
  617. }
  618. LONG
  619. CLIENTAPI
  620. TAPICLIENT_LineSetDevConfig(
  621. HMANAGEMENTCLIENT hmClient,
  622. LPTAPIPERMANENTID pID,
  623. LPVOID lpDevConfig,
  624. LPDWORD pdwSize,
  625. LPCWSTR lpszDeviceClass
  626. )
  627. {
  628. return 0;
  629. }
  630. LONG
  631. CLIENTAPI
  632. TAPICLIENT_LineSetLineDevStatus(
  633. HMANAGEMENTCLIENT hmClient,
  634. LPTAPIPERMANENTID pID,
  635. DWORD dwStatusToChange,
  636. DWORD fStatus
  637. )
  638. {
  639. return 0;
  640. }
  641. LONG
  642. CLIENTAPI
  643. TAPICLIENT_LineSetMediaControl(
  644. HMANAGEMENTCLIENT hmClient,
  645. LPTAPIPERMANENTID pID,
  646. LPLINEMEDIACONTROLDIGIT const lpDigitList,
  647. DWORD dwDigitNumEntries,
  648. LPLINEMEDIACONTROLMEDIA const lpMediaList,
  649. DWORD dwMediaNumEntries,
  650. LPLINEMEDIACONTROLTONE const lpToneList,
  651. DWORD dwToneNumEntries,
  652. LPLINEMEDIACONTROLCALLSTATE const lpCallstateList,
  653. DWORD dwCallstateNumEntries
  654. )
  655. {
  656. return 0;
  657. }
  658. LONG
  659. CLIENTAPI
  660. TAPICLIENT_LineSetMediaMode(
  661. HMANAGEMENTCLIENT hmClient,
  662. LPTAPIPERMANENTID pID,
  663. DWORD dwMediaModes
  664. )
  665. {
  666. return 0;
  667. }
  668. LONG
  669. CLIENTAPI
  670. TAPICLIENT_LineSetTerminal(
  671. HMANAGEMENTCLIENT hmClient,
  672. LPTAPIPERMANENTID pID,
  673. DWORD dwTerminalModes,
  674. DWORD dwTerminalID,
  675. BOOL bEnable
  676. )
  677. {
  678. return 0;
  679. }
  680. LONG
  681. CLIENTAPI
  682. TAPICLIENT_LineSetTollList(
  683. HMANAGEMENTCLIENT hmClient,
  684. LPTAPIPERMANENTID pID,
  685. LPCWSTR lpszAddressIn,
  686. DWORD dwTollListOption
  687. )
  688. {
  689. return 0;
  690. }
  691. LONG
  692. CLIENTAPI
  693. TAPICLIENT_PhoneConfigDialog(
  694. HMANAGEMENTCLIENT hmClient,
  695. LPTAPIPERMANENTID pID,
  696. LPCWSTR lpszDeviceClass
  697. )
  698. {
  699. return 0;
  700. }
  701. LONG
  702. CLIENTAPI
  703. TAPICLIENT_PhoneOpen(
  704. HMANAGEMENTCLIENT hmClient,
  705. LPTAPIPERMANENTID pID,
  706. DWORD dwAPIVersion,
  707. DWORD dwExtVersion,
  708. DWORD dwPrivilege
  709. )
  710. {
  711. return LINEERR_OPERATIONFAILED;
  712. }
  713. #if DBG
  714. VOID
  715. DbgPrt(
  716. IN DWORD dwDbgLevel,
  717. IN PUCHAR lpszFormat,
  718. IN ...
  719. )
  720. /*++
  721. Routine Description:
  722. Formats the incoming debug message & calls DbgPrint
  723. Arguments:
  724. DbgLevel - level of message verboseness
  725. DbgMessage - printf-style format string, followed by appropriate
  726. list of arguments
  727. Return Value:
  728. --*/
  729. {
  730. if (dwDbgLevel <= gdwDebugLevel)
  731. {
  732. char buf[128] = "TSEC: ";
  733. va_list ap;
  734. va_start(ap, lpszFormat);
  735. wvsprintfA (&buf[6], lpszFormat, ap);
  736. lstrcatA (buf, "\n");
  737. OutputDebugStringA (buf);
  738. va_end(ap);
  739. }
  740. }
  741. #endif