Как создать именованный канал в Windows, чтобы можно было читать из него и записывать данные в него без каких-либо логинов и паролей с компьютеров в локальной сети? Изъяны в безопасности при использовании такого канала меня не волнуют, так как это учебная программа. Хотелось бы обойтись без сложных настроек в ОС. Нужно, чтобы работало хотя бы в Windows XP и 7. Вот пример, который работает на одном компьютере, но не на нескольких. Код сервера: #include #include char msg1[]="Message1" char msg2[]="Message2" int main(){ std::string c SECURITY_ATTRIBUTES sa={0} SECURITY_DESCRIPTOR sd={0} InitializeSecurityDescriptor( &sd, SECURITY_DESCRIPTOR_REVISION) SetSecurityDescriptorDacl( &sd, TRUE, NULL, FALSE) sa.bInheritHandle=false sa.lpSecurityDescriptor=&sd sa.nLength=sizeof(sa) HANDLE ch1=CreateNamedPipe ( "\\\\.\\pipe\\testpipe", PIPE_ACCESS_DUPLEX, PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE| PIPE_WAIT, PIPE_UNLIMITED_INSTANCES, sizeof msg1, 4, 0, &sa ) if(ch1==INVALID_HANDLE_VALUE){ std::cout<<"ch1 "<<GetLastError()<<"<br>" std::getline(std::cin, c) return 1 } HANDLE ch2=CreateNamedPipe( "\\\\.\\pipe\\testpipe", PIPE_ACCESS_DUPLEX, PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE| PIPE_WAIT, PIPE_UNLIMITED_INSTANCES, sizeof msg1, 4, 0, &sa ) if(ch2==INVALID_HANDLE_VALUE){ std::cout<<"ch2 "<<GetLastError()<<"<br>" std::getline(std::cin, c) return 1 } ConnectNamedPipe(ch1, 0) ConnectNamedPipe(ch2, 0) unsigned long foo if(TransactNamedPipe(ch1, msg1, sizeof msg1, &foo, sizeof foo, &foo, 0)==0){ std::cout<<"transact1 "<<GetLastError()<<"<br>" std::getline(std::cin, c) return 1 } if(TransactNamedPipe(ch2, msg2, sizeof msg2, &foo, sizeof foo, &foo, 0)==0){ std::cout<<"transact2 "<<GetLastError()<<"<br>" std::getline(std::cin, c) return 1 } std::cout<<"Finished<br>" std::getline(std::cin, c) return 0 } Код клиента: #include #include char msg1[]="Message1 " char msg2[]="Message2 " int main(){ std::string c std::cout<<"Server name:<br>" std::string sname std::cin>>sname std::cin.ignore() NETRESOURCE nr={0} if(sname!="."){ std::string sname2=std::string("\\\\")+sname nr.dwType = RESOURCETYPE_ANY nr.lpRemoteName = &sname2[0] DWORD ret=WNetAddConnection2( &nr, "", "", 0) if(ret!=NO_ERROR){ std::cout<<"WNetAddConnection2 "<<GetLastError()<<"<br>" std::getline(std::cin, c) return 1 } } std::string pname=std::string("\\\\")+sname+std::string("\\pipe\\testpipe") HANDLE phandle1=CreateFile ( pname.c_str(), GENERIC_READ|GENERIC_WRITE, 0, 0, OPEN_EXISTING, 0,0 ) if(phandle1==INVALID_HANDLE_VALUE){ std::cout<<"CreateFile1 "<<GetLastError()<<"<br>" std::getline(std::cin, c) return 1 } HANDLE phandle2=CreateFile ( pname.c_str(), GENERIC_READ|GENERIC_WRITE, 0, 0, OPEN_EXISTING, 0,0 ) if(phandle2==INVALID_HANDLE_VALUE){ std::cout<<"CreateFile2 "<<GetLastError()<<"<br>" std::getline(std::cin, c) return 1 } char msg[sizeof(msg1)] unsigned long s unsigned long foo=0 if(ReadFile(phandle1, &msg, sizeof msg1, &s, 0)==0){ std::cout<<"ReadFile1 "<<GetLastError()<<"<br>" std::getline(std::cin, c) return 1 } std::cout<<msg<<"<br>" if(WriteFile (phandle1, &foo, sizeof foo, &foo, 0)==0){ std::cout<<"WriteFile1 "<<GetLastError()<<"<br>" std::getline(std::cin, c) return 1 } if(ReadFile(phandle2, &msg, sizeof msg1, &s, 0)==0){ std::cout<<"ReadFile2 "<<GetLastError()<<"<br>" std::getline(std::cin, c) return 1 } std::cout<<msg<<"<br>" if(WriteFile (phandle2, &foo, sizeof foo, &foo, 0)==0){ std::cout<<"WriteFile2 "<<GetLastError()<<"<br>" std::getline(std::cin, c) return 1 } std::cout<<"Finished<br>" std::getline(std::cin, c) return 0 } Конкретная ошибка: 5 (ERROR_ACCESS_DENIED) при первом вызове CreateFile в клиентской программе. Обновление Я немного разобрался с именованными каналами. Нужно ещё добавить на машине, где запускается сервер, в разделе HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\lanmanserver\parameters в NullSessionPipes имя канала. Тогда приведённый выше код работает, если сервер — Win XP, а клиент Win 7. А вот наоборот почему-то работает лишь для чтения (GENERIC_READ вместо GENERIC_READ|GENERIC_WRITE в CreateFile), иначе опять ERROR_ACCESS_DENIED. Я также нашёл статью про анонимные каналы на MSDN. Там куда более сложный пример, но в нём предоставляется только доступ на чтение, а изменить код, чтобы работала запись, мне не удалось. Я установил PIPE_ACCESS_DUPLEX вместо PIPE_ACCESS_OUTBOUND в CreateNamedPipe, GENERIC_READ | GENERIC_WRITE вместо FILE_GENERIC_READ в AddAccessAllowedAce, GENERIC_READ | GENERIC_WRITE вместо GENERIC_READ в CreateFile, но ошибка 5 также возникает, если сервер Win 7. Вот что в реестре Windows 7 (может где-то там ошибка?) Значение NullSessionPipes содержит строки "testpipe" и "AnonymousPipe". [HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\LanmanServer\Parameters] "ServiceDll"=hex(2):25,00,53,00,79,00,73,00,74,00,65,00,6d,00,52,00,6f,00,6f,\ 00,74,00,25,00,5c,00,73,00,79,00,73,00,74,00,65,00,6d,00,33,00,32,00,5c,00,\ 73,00,72,00,76,00,73,00,76,00,63,00,2e,00,64,00,6c,00,6c,00,00,00 "ServiceDllUnloadOnStop"=dword:00000001 "EnableAuthenticateUserSharing"=dword:00000001 "NullSessionPipes"=hex(7):74,00,65,00,73,00,74,00,70,00,69,00,70,00,65,00,00,\ 00,41,00,6e,00,6f,00,6e,00,79,00,6d,00,6f,00,75,00,73,00,50,00,69,00,70,00,\ 65,00,00,00,00,00 "autodisconnect"=dword:0000000f "enableforcedlogoff"=dword:00000001 "enablesecuritysignature"=dword:00000000 "requiresecuritysignature"=dword:00000000 "restrictnullsessaccess"=dword:00000000 "Lmannounce"=dword:00000000 "Size"=dword:00000001 "AdjustedNullSessionPipes"=dword:00000003 "Guid"=hex:eb,c5,75,fd,46,51,ce,4c,8f,64,a9,70,12,f6,05,6e Подскажите, как нужно правильно создавать анонимный именованный канал? Обновление 2 Если включить настройку «Сетевой доступ: разрешать применение разрешений "Для всех" к анонимным пользователям» или добавить разрешение «Имитация клиента после проверки подлинности» для группы АНОНИМНЫЙ ВХОД, то и мой пример, и пример MSDN работают без ошибок. Только непонятно, почему — ведь функции олицетворения нигде не вызываются. Обновление 3 Наконец, нашёл статьи, в которых объясняется в чём дело: https://blogs.technet.microsoft.com/nettracer/2010/07/23/why-does-anonymous-pipe-access-fail-on-windows-vista-2008-windows-7-or-windows-2008-r2/, http://blog.m-ri.de/index.php/2009/12/08/windows-integrity-control-schreibzugriff-auf-eine-named-pipe-eines-services-ueber-anonymen-zugriff-auf-vista-windows-2008-server-und-windows-7/ (на немецком, можно читать в переводе на английский) Приведённый в последней статье пример работает, не выдавая ошибки без упомянутых выше настроек! (Нужно только добавить имя канала в NullSessionPipes) Так как я все равно объявил конкурс и тема практически нигде не освещена, хорошо бы, чтобы кто-нибудь написал подробный ответ на основе перечисленных статей.
Ответ Вот рабочий пример named pipes. С реестром мудрить не нужно единственно, на клиенте в исходном коде нужно заменить (чтобы заработало на двух разных PC в сети, а не на одной машине) #define SERVER_NAME L"YOUR_REAL_SERVER_NAME" (т.е. на реальное windows имя компьютера, где будет запущен сервер), а на сервере можно оставить #define SERVER_NAME L"." [UPDATE] Согласно ответу, найденному @Im ieee , в примере с GitHub-а нужно заменить строки 199-201 на: L"(A OICI GA AN)" // allow full control to anonymous L"(A OICI GRGW AU)" // allow read/write to authenticated users L"(A OICI GA BA)" // allow full control to administrators а на клиенте добавить такие строки перед попыткой открыть анонимный именованный канал: NETRESOURCE nr ZeroMemory( &nr, sizeof(nr) ) nr.dwType = RESOURCETYPE_ANY nr.lpRemoteName = FULL_SERVER_NAME dwError = WNetAddConnection2( &nr, L"", L"", 0) if( dwError != ERROR_SUCCESS ) { wprintf_s(L"WNetAddConnection2 fails") goto Cleanup } после чего и это решение будет работать (с учетом, что имя тестовой named pipe, SamplePipe, добавлено в переменную реестра SYSTEM\CurrentControlSet\Services\lanmanserver\parameters\NullSessionPipes Вот полный код решения: client.cpp #pragma region Includes #include #include #pragma endregion #define SERVER_NAME L"VIRTUAL-XP" // change to your server name! #define FULL_SERVER_NAME L"\\\\" SERVER_NAME #define PIPE_NAME L"SamplePipe" #define FULL_PIPE_NAME FULL_SERVER_NAME L"\\pipe\\" PIPE_NAME #define BUFFER_SIZE 1024 #define REQUEST_MESSAGE L"Default request from client" int wmain(int argc, wchar_t * argv[]) { HANDLE hPipe = INVALID_HANDLE_VALUE DWORD dwError = ERROR_SUCCESS NETRESOURCE nr ZeroMemory( &nr, sizeof(nr) ) nr.dwType = RESOURCETYPE_ANY nr.lpRemoteName = FULL_SERVER_NAME // These lines make client work with any server (including XP) // even if you not in domain or logged to the server // But don't forget to ass named pipe name to the registry on server! dwError = WNetAddConnection2( &nr, L"", L"", 0) if( dwError != ERROR_SUCCESS ) { wprintf_s(L"WNetAddConnection2 fails") goto Cleanup } while (TRUE) { hPipe = CreateFile( FULL_PIPE_NAME, GENERIC_READ | GENERIC_WRITE, 0, // no sharing nullptr, //default security attributes OPEN_EXISTING, //open existing pipe SECURITY_ANONYMOUS, //default attributes nullptr // no template file ) if (hPipe != INVALID_HANDLE_VALUE) { wprintf_s(L"The named pipe %s is now connected. ", FULL_PIPE_NAME) break } dwError = GetLastError() if( dwError == ERROR_PIPE_BUSY ) { //all pipe instances are busy so wait for 5 secs if (!WaitNamedPipe(FULL_PIPE_NAME, 5000)) { dwError = GetLastError() wprintf_s(L"Could not open named pipe. 5 sec timeout expired ") goto Cleanup } } else { wprintf_s(L"Unable to open named pipe with error %08lx ", dwError) goto Cleanup } } // Set the read mode and blocking mode of the named pipe. Here , we set data to be read from the pipe as a stream of messages. DWORD dwMode = PIPE_READMODE_MESSAGE if (!SetNamedPipeHandleState(hPipe, &dwMode, nullptr, nullptr)) { dwError = GetLastError() wprintf_s(L"SetNamedPipeHandleState failed with error %08lx ", dwError) goto Cleanup } //Send request from client to server wchar_t chRequest[] = REQUEST_MESSAGE DWORD cbRequest, cbWritten cbRequest = sizeof(chRequest) if (!WriteFile(hPipe, chRequest, cbRequest, &cbWritten, nullptr)) { dwError = GetLastError() wprintf_s(L"WriteFile failed with error message %08lx ", dwError) goto Cleanup } wprintf_s(L"Sent %ld bytes to server: %s ", cbWritten, chRequest) // Receive a response from the server BOOL fFinishRead = TRUE do { wchar_t chResponse[BUFFER_SIZE] DWORD cbResponse, cbRead cbResponse = sizeof(chResponse) fFinishRead = ReadFile(hPipe, chResponse, cbResponse, &cbRead, nullptr) dwError = GetLastError() if (!fFinishRead && ERROR_MORE_DATA != dwError) { wprintf_s(L"Readfile from pipe %s failed with error %08lx ", FULL_PIPE_NAME, dwError) break } wprintf(L"Receive %ld bytes fom server : %s ", cbRead, chResponse) } while (!fFinishRead) Cleanup: if (hPipe != INVALID_HANDLE_VALUE) { CloseHandle(hPipe) hPipe = INVALID_HANDLE_VALUE } return dwError } server.cpp /*************************************** PIPE_ACCESS_IMBOUND: Client (Generic_Write) -> Server( Generic_Read) PIPE_ACCESS_OUTBOUND: Server (Generic_Write) -> Client (Generic_Read) PIPE_ACCESS_DUPLEX: Client (Generic_Read or Generic_Write or Both) <--> Server (Generic_Read and Generic_Write) *******************************************/ #pragma region Includes #include #include #include /ecurity descriptor description language #pragma endregion #define SERVER_NAME L"." #define PIPE_NAME L"SamplePipe" #define FULL_PIPE_NAME L"\\\\" SERVER_NAME L"\\pipe\\" PIPE_NAME //concatenation done by compiler #define BUFFER_SIZE 1024 #define RESPONSE_MESSAGE L"Default response from server" //Forward declarations of methods so that they can be used in main method. These will be defined later. BOOL CreatePipeSecurity(PSECURITY_ATTRIBUTES *) void FreePipeSecurity(PSECURITY_ATTRIBUTES ) int wmain(int argc, wchar_t * argv[]) { DWORD dwError = ERROR_SUCCESS //error code definitions in winerr.h for windows apis PSECURITY_ATTRIBUTES pSA = nullptr HANDLE hNamedPipe = INVALID_HANDLE_VALUE //handleapi.h // Prepare the security attributes (the lpSecurityAttributes parameter in // CreateNamedPipe) for the pipe. This is optional. If the // lpSecurityAttributes parameter of CreateNamedPipe is NULL, the named // pipe gets a default security descriptor and the handle cannot be // inherited. The ACLs in the default security descriptor of a pipe grant // full control to the LocalSystem account, (elevated) administrators, // and the creator owner. They also give only read access to members of // the Everyone group and the anonymous account. However, if you want to // customize the security permission of the pipe, (e.g. to allow // Authenticated Users to read from and write to the pipe), you need to // create a SECURITY_ATTRIBUTES structure. if (!CreatePipeSecurity(&pSA)) //passing by ref so that the actual pointer value is updated. { dwError = GetLastError() //errohandlingapi.h wprintf_s(L"CreatePipeSecurity failed with error 0x%08lx ", dwError) // printf format %[parameter][flags][width][.precision][length]type goto Cleanup } //Create Named pipe hNamedPipe = CreateNamedPipe( FULL_PIPE_NAME, PIPE_ACCESS_DUPLEX, //open mode PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT, PIPE_UNLIMITED_INSTANCES, //max intances BUFFER_SIZE, //output buffer size BUFFER_SIZE, // input buffer size NMPWAIT_USE_DEFAULT_WAIT, //time out interval pSA ) if (hNamedPipe == INVALID_HANDLE_VALUE) { dwError = GetLastError() wprintf_s(L"CreateNamedPipe failed with error %08lx ", dwError) goto Cleanup } wprintf_s(L"The named pipe (%s) has been successfully created. ", FULL_PIPE_NAME) //wait for the client to connect. wprintf_s(L"Waiting for the client to connect. ") // Blocking call on server. //ConnectNamedPipe Enables a named pipe server process to wait for a client process to connect to an instance of a named pipe. A client process connects by calling either the CreateFile or CallNamedPipe function. if (!ConnectNamedPipe(hNamedPipe, nullptr)) //namedpipeapi.h { dwError = GetLastError() if (dwError != ERROR_PIPE_CONNECTED) { wprintf_s(L"ConnectNamedPipe failed with error %08lx ", dwError) goto Cleanup } } wprintf(L"Client is connected. ") //Receive request from client. BOOL fFinishedRead = FALSE do { wchar_t chRequest[BUFFER_SIZE] DWORD cbRequest, cbRead cbRequest = sizeof(chRequest) //another blocking call. fFinishedRead = ReadFile( //fileapi.h hNamedPipe, //file handle which is the named pipe handle chRequest, //buffer cbRequest, // no. of bytes to read &cbRead, // no. of bytes read , pass by ref so that the callee can update the value. nullptr ) dwError = GetLastError() if (!fFinishedRead && dwError != ERROR_MORE_DATA) { wprintf_s(L"ReadFile failed with error %08lx ", dwError) goto Cleanup } wprintf_s(L"Received %ld bytes from client: %s ", cbRead, chRequest) } while (!fFinishedRead) //repeat loop if ERROR_MORE_DATA. /end a response from server to client. wchar_t chResponse[] = RESPONSE_MESSAGE DWORD cbResponse, cbWritten cbResponse = sizeof(chResponse) //another blocking call. if (!WriteFile( hNamedPipe, chResponse, cbResponse, &cbWritten, nullptr )) { dwError = GetLastError() wprintf_s(L"WriteFile failed with error %08lx ", dwError) goto Cleanup } wprintf_s(L"Sent %ld bytes to client: %s ", cbWritten, chResponse) //Flush the pipe to allow the client to read the pipe's contents before disconnecting. FlushFileBuffers(hNamedPipe) DisconnectNamedPipe(hNamedPipe) Cleanup: if (!pSA) { FreePipeSecurity(pSA) pSA = nullptr //always null after releasing. } if (hNamedPipe != INVALID_HANDLE_VALUE) { CloseHandle(hNamedPipe) hNamedPipe = INVALID_HANDLE_VALUE } return dwError } // // FUNCTION: CreatePipeSecurity(PSECURITY_ATTRIBUTES *) // // PURPOSE: The CreatePipeSecurity function creates and initializes a new // SECURITY_ATTRIBUTES structure to allow Authenticated Users read and // write access to a pipe, and to allow the Administrators group full // access to the pipe. // // PARAMETERS: // * ppSa - output a pointer to a SECURITY_ATTRIBUTES structure that allows // Authenticated Users read and write access to a pipe, and allows the // Administrators group full access to the pipe. The structure must be // freed by calling FreePipeSecurity. BOOL CreatePipeSecurity(PSECURITY_ATTRIBUTES *ppSA) { BOOL fSucceeded = TRUE DWORD dwError = ERROR_SUCCESS PSECURITY_DESCRIPTOR pSD = nullptr PSECURITY_ATTRIBUTES pSA = nullptr //Define SDDL for the security descriptor PCWSTR szSDDL = L"D:" //Discretionary ACL L"(A OICI GA AN)" //allow full control to anonymous L"(A OICI GRGW AU)" //allow read/write to authenticated users L"(A OICI GA BA)" //allow full control to administrators if (!ConvertStringSecurityDescriptorToSecurityDescriptor(szSDDL, SDDL_REVISION_1, &pSD, nullptr)) { fSucceeded = FALSE dwError = GetLastError() goto Cleanup } //allocate memory for security attributes pSA = (PSECURITY_ATTRIBUTES)LocalAlloc(LPTR, sizeof(*pSA)) //minwinbase.h for LPTR if (pSA == nullptr) { fSucceeded = FALSE dwError = GetLastError() goto Cleanup } pSA->nLength = sizeof(*pSA) /izeof(SECURITY_ATTRIBUTES) pSA->lpSecurityDescriptor = pSD pSA->bInheritHandle = FALSE *ppSA = pSA Cleanup: if (!fSucceeded) { if (pSD) { LocalFree(pSD) //winbase.h pSD = nullptr } if (pSA) { LocalFree(pSA) pSA = nullptr } SetLastError(dwError) } return fSucceeded } void FreePipeSecurity(PSECURITY_ATTRIBUTES pSA) { if (pSA) { if (pSA->lpSecurityDescriptor) { LocalFree(pSA->lpSecurityDescriptor) //clear its contents first. } LocalFree(pSA) //then clear itself. } } P.S. Если будете собирать в Release конфигурации, не забудьте указать в опциях препроцессора _UNICODE UNICODE