2003-06-07 Casper S. Hornstrup <chorns@users.sourceforge.net>
[reactos.git] / reactos / ntoskrnl / lpc / connect.c
1 /* $Id: connect.c,v 1.16 2003/06/07 12:23:14 chorns Exp $
2 *
3 * COPYRIGHT: See COPYING in the top level directory
4 * PROJECT: ReactOS kernel
5 * FILE: ntoskrnl/lpc/connect.c
6 * PURPOSE: Communication mechanism
7 * PROGRAMMER: David Welch (welch@cwcom.net)
8 * UPDATE HISTORY:
9 * Created 22/05/98
10 */
11
12 /* INCLUDES *****************************************************************/
13
14 #define NTOS_MODE_KERNEL
15 #include <ntos.h>
16 #include <internal/ob.h>
17 #include <internal/port.h>
18 #include <internal/dbg.h>
19 #include <internal/pool.h>
20 #include <internal/safe.h>
21 #include <internal/mm.h>
22
23 #define NDEBUG
24 #include <internal/debug.h>
25
26 /* GLOBALS *******************************************************************/
27
28 #define TAG_LPC_CONNECT_MESSAGE TAG('L', 'P', 'C', 'C')
29
30 /* FUNCTIONS *****************************************************************/
31
32 NTSTATUS STDCALL
33 EiConnectPort(IN PEPORT* ConnectedPort,
34 IN PEPORT NamedPort,
35 IN PSECTION_OBJECT Section,
36 IN LARGE_INTEGER SectionOffset,
37 IN ULONG ViewSize,
38 OUT PVOID* ClientSendViewBase,
39 OUT PVOID* ServerSendViewBase,
40 OUT PULONG ReceiveViewSize,
41 OUT PVOID* ReceiveViewBase,
42 OUT PULONG MaximumMessageSize,
43 IN OUT PVOID ConnectData,
44 IN OUT PULONG ConnectDataLength)
45 {
46 PEPORT_CONNECT_REQUEST_MESSAGE RequestMessage;
47 ULONG RequestConnectDataLength;
48 PEPORT OurPort;
49 PQUEUEDMESSAGE Reply;
50 PEPORT_CONNECT_REPLY_MESSAGE CReply;
51 NTSTATUS Status;
52 KIRQL oldIrql;
53
54 if (ConnectDataLength == NULL)
55 {
56 RequestConnectDataLength = 0;
57 }
58 else
59 {
60 RequestConnectDataLength = *ConnectDataLength;
61 }
62
63 /*
64 * Create a port to represent our side of the connection
65 */
66 Status = ObRosCreateObject (NULL,
67 PORT_ALL_ACCESS,
68 NULL,
69 ExPortType,
70 (PVOID*)&OurPort);
71 if (!NT_SUCCESS(Status))
72 {
73 return (Status);
74 }
75 NiInitializePort(OurPort);
76
77 /*
78 * Allocate a request message.
79 */
80 RequestMessage = ExAllocatePool(NonPagedPool,
81 sizeof(EPORT_CONNECT_REQUEST_MESSAGE) +
82 RequestConnectDataLength);
83 if (RequestMessage == NULL)
84 {
85 ObDereferenceObject(OurPort);
86 return(STATUS_NO_MEMORY);
87 }
88
89 /*
90 * Initialize the request message.
91 */
92 RequestMessage->MessageHeader.DataSize =
93 sizeof(EPORT_CONNECT_REQUEST_MESSAGE) + RequestConnectDataLength -
94 sizeof(LPC_MESSAGE);
95 RequestMessage->MessageHeader.MessageSize =
96 sizeof(EPORT_CONNECT_REQUEST_MESSAGE) + RequestConnectDataLength;
97 DPRINT("RequestMessageSize %d\n",
98 RequestMessage->MessageHeader.MessageSize);
99 RequestMessage->MessageHeader.SharedSectionSize = 0;
100 RequestMessage->ConnectingProcess = PsGetCurrentProcess();
101 ObReferenceObjectByPointer(RequestMessage->ConnectingProcess,
102 PROCESS_VM_OPERATION,
103 NULL,
104 KernelMode);
105 RequestMessage->SendSectionObject = (struct _SECTION_OBJECT*)Section;
106 RequestMessage->SendSectionOffset = SectionOffset;
107 RequestMessage->SendViewSize = ViewSize;
108 RequestMessage->ConnectDataLength = RequestConnectDataLength;
109 if (RequestConnectDataLength > 0)
110 {
111 memcpy(RequestMessage->ConnectData, ConnectData,
112 RequestConnectDataLength);
113 }
114
115 /*
116 * Queue the message to the named port
117 */
118 EiReplyOrRequestPort(NamedPort,
119 &RequestMessage->MessageHeader,
120 LPC_CONNECTION_REQUEST,
121 OurPort);
122 KeReleaseSemaphore(&NamedPort->Semaphore, IO_NO_INCREMENT, 1, FALSE);
123 ExFreePool(RequestMessage);
124
125 /*
126 * Wait for them to accept our connection
127 */
128 KeWaitForSingleObject(&OurPort->Semaphore,
129 UserRequest,
130 UserMode,
131 FALSE,
132 NULL);
133
134 /*
135 * Dequeue the response
136 */
137 KeAcquireSpinLock (&OurPort->Lock, &oldIrql);
138 Reply = EiDequeueMessagePort (OurPort);
139 KeReleaseSpinLock (&OurPort->Lock, oldIrql);
140 CReply = (PEPORT_CONNECT_REPLY_MESSAGE)&Reply->Message;
141
142 /*
143 * Do some initial cleanup.
144 */
145 ObDereferenceObject(PsGetCurrentProcess());
146
147 /*
148 * Check for connection refusal.
149 */
150 if (CReply->MessageHeader.MessageType == LPC_CONNECTION_REFUSED)
151 {
152 ObDereferenceObject(OurPort);
153 ExFreePool(Reply);
154 /*
155 * FIXME: Check what NT does here. Giving the user data back on
156 * connect failure sounds reasonable; it probably wouldn't break
157 * anything anyway.
158 */
159 if (ConnectDataLength != NULL)
160 {
161 *ConnectDataLength = CReply->ConnectDataLength;
162 memcpy(ConnectData, CReply->ConnectData, CReply->ConnectDataLength);
163 }
164 return(STATUS_PORT_CONNECTION_REFUSED);
165 }
166
167 /*
168 * Otherwise we are connected. Copy data back to the client.
169 */
170 *ServerSendViewBase = CReply->SendServerViewBase;
171 *ReceiveViewSize = CReply->ReceiveClientViewSize;
172 *ReceiveViewBase = CReply->ReceiveClientViewBase;
173 *MaximumMessageSize = CReply->MaximumMessageSize;
174 if (ConnectDataLength != NULL)
175 {
176 *ConnectDataLength = CReply->ConnectDataLength;
177 memcpy(ConnectData, CReply->ConnectData, CReply->ConnectDataLength);
178 }
179
180 /*
181 * Create our view of the send section object.
182 */
183 if (Section != NULL)
184 {
185 *ClientSendViewBase = 0;
186 Status = MmMapViewOfSection(Section,
187 PsGetCurrentProcess(),
188 ClientSendViewBase,
189 0,
190 ViewSize,
191 &SectionOffset,
192 &ViewSize,
193 ViewUnmap,
194 0 /* MEM_TOP_DOWN? */,
195 PAGE_READWRITE);
196 if (!NT_SUCCESS(Status))
197 {
198 /* FIXME: Cleanup here. */
199 return(Status);
200 }
201 }
202
203 /*
204 * Do the final initialization of our port.
205 */
206 OurPort->State = EPORT_CONNECTED_CLIENT;
207
208 /*
209 * Cleanup.
210 */
211 ExFreePool(Reply);
212 *ConnectedPort = OurPort;
213 return(STATUS_SUCCESS);
214 }
215
216 /**********************************************************************
217 * NAME EXPORTED
218 * NtConnectPort@32
219 *
220 * DESCRIPTION
221 * Connect to a named port and wait for the other side to
222 * accept the connection.
223 *
224 * ARGUMENTS
225 * ConnectedPort
226 * PortName
227 * Qos
228 * WriteMap
229 * ReadMap
230 * MaxMessageSize
231 * ConnectInfo
232 * UserConnectInfoLength
233 *
234 * RETURN VALUE
235 *
236 */
237 NTSTATUS STDCALL
238 NtConnectPort (PHANDLE UnsafeConnectedPortHandle,
239 PUNICODE_STRING PortName,
240 PSECURITY_QUALITY_OF_SERVICE Qos,
241 PLPC_SECTION_WRITE UnsafeWriteMap,
242 PLPC_SECTION_READ UnsafeReadMap,
243 PULONG UnsafeMaximumMessageSize,
244 PVOID UnsafeConnectData,
245 PULONG UnsafeConnectDataLength)
246 {
247 HANDLE ConnectedPortHandle;
248 LPC_SECTION_WRITE WriteMap;
249 LPC_SECTION_READ ReadMap;
250 ULONG MaximumMessageSize;
251 PVOID ConnectData;
252 ULONG ConnectDataLength;
253 PSECTION_OBJECT SectionObject;
254 LARGE_INTEGER SectionOffset;
255 PEPORT ConnectedPort;
256 NTSTATUS Status;
257 PEPORT NamedPort;
258
259 /*
260 * Copy in write map and partially validate.
261 */
262 if (UnsafeWriteMap != NULL)
263 {
264 Status = MmCopyFromCaller(&WriteMap, UnsafeWriteMap,
265 sizeof(LPC_SECTION_WRITE));
266 if (!NT_SUCCESS(Status))
267 {
268 return(Status);
269 }
270 if (WriteMap.Length != sizeof(LPC_SECTION_WRITE))
271 {
272 return(STATUS_INVALID_PARAMETER_4);
273 }
274 SectionOffset.QuadPart = WriteMap.SectionOffset;
275 }
276 else
277 {
278 WriteMap.SectionHandle = INVALID_HANDLE_VALUE;
279 }
280
281 /*
282 * Handle connection data.
283 */
284 if (UnsafeConnectData == NULL)
285 {
286 ConnectDataLength = 0;
287 ConnectData = NULL;
288 }
289 else
290 {
291 if (ExGetPreviousMode() == KernelMode)
292 {
293 ConnectDataLength = *UnsafeConnectDataLength;
294 ConnectData = UnsafeConnectData;
295 }
296 else
297 {
298 Status = MmCopyFromCaller(&ConnectDataLength,
299 UnsafeConnectDataLength,
300 sizeof(ULONG));
301 if (!NT_SUCCESS(Status))
302 {
303 return(Status);
304 }
305 ConnectData = ExAllocatePool(NonPagedPool, ConnectDataLength);
306 if (ConnectData == NULL && ConnectDataLength != 0)
307 {
308 return(STATUS_NO_MEMORY);
309 }
310 Status = MmCopyFromCaller(ConnectData,
311 UnsafeConnectData,
312 ConnectDataLength);
313 if (!NT_SUCCESS(Status))
314 {
315 ExFreePool(ConnectData);
316 return(Status);
317 }
318 }
319 }
320
321 /*
322 * Reference the named port.
323 */
324 Status = ObReferenceObjectByName (PortName,
325 0,
326 NULL,
327 PORT_ALL_ACCESS, /* DesiredAccess */
328 ExPortType,
329 UserMode,
330 NULL,
331 (PVOID*)&NamedPort);
332 if (!NT_SUCCESS(Status))
333 {
334 if (KeGetPreviousMode() != KernelMode)
335 {
336 ExFreePool(ConnectData);
337 }
338 return(Status);
339 }
340
341 /*
342 * Reference the send section object.
343 */
344 if (WriteMap.SectionHandle != INVALID_HANDLE_VALUE)
345 {
346 Status = ObReferenceObjectByHandle(WriteMap.SectionHandle,
347 SECTION_MAP_READ | SECTION_MAP_WRITE,
348 MmSectionObjectType,
349 UserMode,
350 (PVOID*)&SectionObject,
351 NULL);
352 if (!NT_SUCCESS(Status))
353 {
354 ObDereferenceObject(NamedPort);
355 if (KeGetPreviousMode() != KernelMode)
356 {
357 ExFreePool(ConnectData);
358 }
359 return(Status);
360 }
361 }
362 else
363 {
364 SectionObject = NULL;
365 }
366
367 /*
368 * Do the connection establishment.
369 */
370 Status = EiConnectPort(&ConnectedPort,
371 NamedPort,
372 SectionObject,
373 SectionOffset,
374 WriteMap.ViewSize,
375 &WriteMap.ViewBase,
376 &WriteMap.TargetViewBase,
377 &ReadMap.ViewSize,
378 &ReadMap.ViewBase,
379 &MaximumMessageSize,
380 ConnectData,
381 &ConnectDataLength);
382 if (!NT_SUCCESS(Status))
383 {
384 /* FIXME: Again, check what NT does here. */
385 if (UnsafeConnectDataLength != NULL)
386 {
387 if (ExGetPreviousMode() != KernelMode)
388 {
389 MmCopyToCaller(UnsafeConnectData, ConnectData,
390 ConnectDataLength);
391 ExFreePool(ConnectData);
392 }
393 MmCopyToCaller(UnsafeConnectDataLength, &ConnectDataLength,
394 sizeof(ULONG));
395 }
396 return(Status);
397 }
398
399 /*
400 * Do some initial cleanup.
401 */
402 if (SectionObject != NULL)
403 {
404 ObDereferenceObject(SectionObject);
405 SectionObject = NULL;
406 }
407 ObDereferenceObject(NamedPort);
408 NamedPort = NULL;
409
410 /*
411 * Copy the data back to the caller.
412 */
413 if (ExGetPreviousMode() != KernelMode)
414 {
415 if (UnsafeConnectDataLength != NULL)
416 {
417 if (ExGetPreviousMode() != KernelMode)
418 {
419 Status = MmCopyToCaller(UnsafeConnectData, ConnectData,
420 ConnectDataLength);
421 ExFreePool(ConnectData);
422 if (!NT_SUCCESS(Status))
423 {
424 return(Status);
425 }
426 }
427 Status = MmCopyToCaller(UnsafeConnectDataLength, &ConnectDataLength,
428 sizeof(ULONG));
429 if (!NT_SUCCESS(Status))
430 {
431 return(Status);
432 }
433 }
434 }
435 Status = ObInsertObject(ConnectedPort,
436 NULL,
437 PORT_ALL_ACCESS,
438 0,
439 NULL,
440 &ConnectedPortHandle);
441 if (!NT_SUCCESS(Status))
442 {
443 return(Status);
444 }
445 Status = MmCopyToCaller(UnsafeConnectedPortHandle, &ConnectedPortHandle,
446 sizeof(HANDLE));
447 if (!NT_SUCCESS(Status))
448 {
449 return(Status);
450 }
451 if (UnsafeWriteMap != NULL)
452 {
453 Status = MmCopyToCaller(UnsafeWriteMap, &WriteMap,
454 sizeof(LPC_SECTION_WRITE));
455 if (!NT_SUCCESS(Status))
456 {
457 return(Status);
458 }
459 }
460 if (UnsafeReadMap != NULL)
461 {
462 Status = MmCopyToCaller(UnsafeReadMap, &ReadMap,
463 sizeof(LPC_SECTION_READ));
464 if (!NT_SUCCESS(Status))
465 {
466 return(Status);
467 }
468 }
469 if (UnsafeMaximumMessageSize != NULL)
470 {
471 Status = MmCopyToCaller(UnsafeMaximumMessageSize,
472 &MaximumMessageSize,
473 sizeof(LPC_SECTION_WRITE));
474 if (!NT_SUCCESS(Status))
475 {
476 return(Status);
477 }
478 }
479
480 /*
481 * All done.
482 */
483
484 return(STATUS_SUCCESS);
485 }
486
487
488 /**********************************************************************
489 * NAME EXPORTED
490 * NtAcceptConnectPort@24
491 *
492 * DESCRIPTION
493 *
494 * ARGUMENTS
495 * ServerPortHandle
496 * NamedPortHandle
497 * LpcMessage
498 * AcceptIt
499 * WriteMap
500 * ReadMap
501 *
502 * RETURN VALUE
503 *
504 */
505 EXPORTED NTSTATUS STDCALL
506 NtAcceptConnectPort (PHANDLE ServerPortHandle,
507 HANDLE NamedPortHandle,
508 PLPC_MESSAGE LpcMessage,
509 BOOLEAN AcceptIt,
510 PLPC_SECTION_WRITE WriteMap,
511 PLPC_SECTION_READ ReadMap)
512 {
513 NTSTATUS Status;
514 PEPORT NamedPort;
515 PEPORT OurPort = NULL;
516 PQUEUEDMESSAGE ConnectionRequest;
517 KIRQL oldIrql;
518 PEPORT_CONNECT_REQUEST_MESSAGE CRequest;
519 PEPORT_CONNECT_REPLY_MESSAGE CReply;
520 ULONG Size;
521
522 Size = sizeof(EPORT_CONNECT_REPLY_MESSAGE);
523 if (LpcMessage)
524 {
525 Size += LpcMessage->DataSize;
526 }
527
528 CReply = ExAllocatePool(NonPagedPool, Size);
529 if (CReply == NULL)
530 {
531 return(STATUS_NO_MEMORY);
532 }
533
534 Status = ObReferenceObjectByHandle(NamedPortHandle,
535 PORT_ALL_ACCESS,
536 ExPortType,
537 UserMode,
538 (PVOID*)&NamedPort,
539 NULL);
540 if (!NT_SUCCESS(Status))
541 {
542 ExFreePool(CReply);
543 return (Status);
544 }
545
546 /*
547 * Create a port object for our side of the connection
548 */
549 if (AcceptIt)
550 {
551 Status = ObRosCreateObject(ServerPortHandle,
552 PORT_ALL_ACCESS,
553 NULL,
554 ExPortType,
555 (PVOID*)&OurPort);
556 if (!NT_SUCCESS(Status))
557 {
558 ExFreePool(CReply);
559 ObDereferenceObject(NamedPort);
560 return(Status);
561 }
562 NiInitializePort(OurPort);
563 }
564
565 /*
566 * Dequeue the connection request
567 */
568 KeAcquireSpinLock(&NamedPort->Lock, &oldIrql);
569 ConnectionRequest = EiDequeueConnectMessagePort (NamedPort);
570 KeReleaseSpinLock(&NamedPort->Lock, oldIrql);
571 CRequest = (PEPORT_CONNECT_REQUEST_MESSAGE)(&ConnectionRequest->Message);
572
573 /*
574 * Prepare the reply.
575 */
576 if (LpcMessage != NULL)
577 {
578 memcpy(&CReply->MessageHeader, LpcMessage, sizeof(LPC_MESSAGE));
579 memcpy(&CReply->ConnectData, (PVOID)(LpcMessage + 1),
580 LpcMessage->DataSize);
581 CReply->MessageHeader.MessageSize =
582 sizeof(EPORT_CONNECT_REPLY_MESSAGE) + LpcMessage->DataSize;
583 CReply->MessageHeader.DataSize = CReply->MessageHeader.MessageSize -
584 sizeof(LPC_MESSAGE);
585 CReply->ConnectDataLength = LpcMessage->DataSize;
586 }
587 else
588 {
589 CReply->MessageHeader.MessageSize = sizeof(EPORT_CONNECT_REPLY_MESSAGE);
590 CReply->MessageHeader.DataSize = sizeof(EPORT_CONNECT_REPLY_MESSAGE) -
591 sizeof(LPC_MESSAGE);
592 CReply->ConnectDataLength = 0;
593 }
594 if (!AcceptIt)
595 {
596 EiReplyOrRequestPort(ConnectionRequest->Sender,
597 &CReply->MessageHeader,
598 LPC_CONNECTION_REFUSED,
599 NamedPort);
600 KeReleaseSemaphore(&ConnectionRequest->Sender->Semaphore,
601 IO_NO_INCREMENT,
602 1,
603 FALSE);
604 ObDereferenceObject(ConnectionRequest->Sender);
605 ExFreePool(ConnectionRequest);
606 ExFreePool(CReply);
607 ObDereferenceObject(NamedPort);
608 return (STATUS_SUCCESS);
609 }
610
611 /*
612 * Prepare the connection.
613 */
614 if (WriteMap != NULL)
615 {
616 PSECTION_OBJECT SectionObject;
617 LARGE_INTEGER SectionOffset;
618
619 Status = ObReferenceObjectByHandle(WriteMap->SectionHandle,
620 SECTION_MAP_READ | SECTION_MAP_WRITE,
621 MmSectionObjectType,
622 UserMode,
623 (PVOID*)&SectionObject,
624 NULL);
625 if (!NT_SUCCESS(Status))
626 {
627 return(Status);
628 }
629
630 SectionOffset.QuadPart = WriteMap->SectionOffset;
631 WriteMap->TargetViewBase = 0;
632 CReply->ReceiveClientViewSize = WriteMap->ViewSize;
633 Status = MmMapViewOfSection(SectionObject,
634 CRequest->ConnectingProcess,
635 &WriteMap->TargetViewBase,
636 0,
637 CReply->ReceiveClientViewSize,
638 &SectionOffset,
639 &CReply->ReceiveClientViewSize,
640 ViewUnmap,
641 0 /* MEM_TOP_DOWN? */,
642 PAGE_READWRITE);
643 if (!NT_SUCCESS(Status))
644 {
645 return(Status);
646 }
647
648 WriteMap->ViewBase = 0;
649 Status = MmMapViewOfSection(SectionObject,
650 PsGetCurrentProcess(),
651 &WriteMap->ViewBase,
652 0,
653 WriteMap->ViewSize,
654 &SectionOffset,
655 &WriteMap->ViewSize,
656 ViewUnmap,
657 0 /* MEM_TOP_DOWN? */,
658 PAGE_READWRITE);
659 if (!NT_SUCCESS(Status))
660 {
661 return(Status);
662 }
663
664 ObDereferenceObject(SectionObject);
665 }
666 if (ReadMap != NULL && CRequest->SendSectionObject != NULL)
667 {
668 LARGE_INTEGER SectionOffset;
669
670 SectionOffset = CRequest->SendSectionOffset;
671 ReadMap->ViewSize = CRequest->SendViewSize;
672 ReadMap->ViewBase = 0;
673 Status = MmMapViewOfSection(CRequest->SendSectionObject,
674 PsGetCurrentProcess(),
675 &ReadMap->ViewBase,
676 0,
677 CRequest->SendViewSize,
678 &SectionOffset,
679 &CRequest->SendViewSize,
680 ViewUnmap,
681 0 /* MEM_TOP_DOWN? */,
682 PAGE_READWRITE);
683 if (!NT_SUCCESS(Status))
684 {
685 return(Status);
686 }
687 }
688
689 /*
690 * Finish the reply.
691 */
692 if (ReadMap != NULL)
693 {
694 CReply->SendServerViewBase = ReadMap->ViewBase;
695 }
696 else
697 {
698 CReply->SendServerViewBase = 0;
699 }
700 if (WriteMap != NULL)
701 {
702 CReply->ReceiveClientViewBase = WriteMap->TargetViewBase;
703 }
704 CReply->MaximumMessageSize = 0x148;
705
706
707 /*
708 * Connect the two ports
709 */
710 OurPort->OtherPort = ConnectionRequest->Sender;
711 OurPort->OtherPort->OtherPort = OurPort;
712 EiReplyOrRequestPort(ConnectionRequest->Sender,
713 (PLPC_MESSAGE)CReply,
714 LPC_REPLY,
715 OurPort);
716 ExFreePool(ConnectionRequest);
717 ExFreePool(CReply);
718
719 ObDereferenceObject(OurPort);
720 ObDereferenceObject(NamedPort);
721
722 return (STATUS_SUCCESS);
723 }
724
725 /**********************************************************************
726 * NAME EXPORTED
727 * NtSecureConnectPort@36
728 *
729 * DESCRIPTION
730 * Connect to a named port and wait for the other side to
731 * accept the connection. Possibly verify that the server
732 * matches the ServerSid (trusted server).
733 * Present in w2k+.
734 *
735 * ARGUMENTS
736 * ConnectedPort
737 * PortName
738 * Qos
739 * WriteMap
740 * ServerSid
741 * ReadMap
742 * MaxMessageSize
743 * ConnectInfo
744 * UserConnectInfoLength
745 *
746 * RETURN VALUE
747 *
748 */
749 NTSTATUS STDCALL
750 NtSecureConnectPort (OUT PHANDLE ConnectedPort,
751 IN PUNICODE_STRING PortName,
752 IN PSECURITY_QUALITY_OF_SERVICE Qos,
753 IN OUT PLPC_SECTION_WRITE WriteMap OPTIONAL,
754 IN PSID ServerSid OPTIONAL,
755 IN OUT PLPC_SECTION_READ ReadMap OPTIONAL,
756 OUT PULONG MaxMessageSize OPTIONAL,
757 IN OUT PVOID ConnectInfo OPTIONAL,
758 IN OUT PULONG UserConnectInfoLength OPTIONAL)
759 {
760 return (STATUS_NOT_IMPLEMENTED);
761 }
762
763 /* EOF */