/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim:set expandtab ts=4 sw=2 sts=2 cin: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
class nsSOCKSSocketInfo : public nsIDNSListener { enum State {
SOCKS_INITIAL,
SOCKS_DNS_IN_PROGRESS,
SOCKS_DNS_COMPLETE,
SOCKS_CONNECTING_TO_PROXY,
SOCKS4_WRITE_CONNECT_REQUEST,
SOCKS4_READ_CONNECT_RESPONSE,
SOCKS5_WRITE_AUTH_REQUEST,
SOCKS5_READ_AUTH_RESPONSE,
SOCKS5_WRITE_USERNAME_REQUEST,
SOCKS5_READ_USERNAME_RESPONSE,
SOCKS5_WRITE_CONNECT_REQUEST,
SOCKS5_READ_CONNECT_RESPONSE_TOP,
SOCKS5_READ_CONNECT_RESPONSE_BOTTOM,
SOCKS_CONNECTED,
SOCKS_FAILED
};
// A buffer of 520 bytes should be enough for any request and response // in case of SOCKS4 as well as SOCKS5 staticconst uint32_t BUFFER_SIZE = 520; staticconst uint32_t MAX_HOSTNAME_LEN = 255; staticconst uint32_t MAX_USERNAME_LEN = 255; staticconst uint32_t MAX_PASSWORD_LEN = 255;
bool SetupNamedPipeLayer(PRFileDesc* fd) { #ifdefined(XP_WIN) if (IsLocalProxy()) { // nsSOCKSIOLayer handshaking only works under blocking mode // unfortunately. Remember named pipe's FD to switch between modes.
SetNamedPipeFD(fd->lower); returntrue;
} #endif returnfalse;
}
/* Helper template class to statically check that writes to a fixed-size * buffer are not going to overflow. * * Example usage: * uint8_t real_buf[TOTAL_SIZE]; * Buffer<TOTAL_SIZE> buf(&real_buf); * auto buf2 = buf.WriteUint16(1); * auto buf3 = buf2.WriteUint8(2); * * It is possible to chain them, to limit the number of (error-prone) * intermediate variables: * auto buf = Buffer<TOTAL_SIZE>(&real_buf) * .WriteUint16(1) * .WriteUint8(2); * * Debug builds assert when intermediate variables are reused: * Buffer<TOTAL_SIZE> buf(&real_buf); * auto buf2 = buf.WriteUint16(1); * auto buf3 = buf.WriteUint8(2); // Asserts * * Strings can be written, given an explicit maximum length. * buf.WriteString<MAX_STRING_LENGTH>(str); * * The Written() method returns how many bytes have been written so far: * Buffer<TOTAL_SIZE> buf(&real_buf); * auto buf2 = buf.WriteUint16(1); * auto buf3 = buf2.WriteUint8(2); * buf3.Written(); // returns 3.
*/ template <size_t Size> class Buffer { public:
Buffer() = default;
// There needs to be a means of distinguishing between connection errors // that the SOCKS server reports when it rejects a connection request, and // connection errors that happen while attempting to connect to the SOCKS // server. Otherwise, Firefox will report incorrectly that the proxy server // is refusing connections when a SOCKS request is rejected by the proxy. // When a SOCKS handshake failure occurs, the PR error is set to // PR_UNKNOWN_ERROR, and the real error code is returned via the OS error. void nsSOCKSSocketInfo::HandshakeFinished(PRErrorCode err) { if (err == 0) {
mState = SOCKS_CONNECTED; #ifdefined(XP_WIN) // Switch back to nonblocking mode after finishing handshaking. if (IsLocalProxy() && mFD) {
PRSocketOptionData opt_nonblock;
opt_nonblock.option = PR_SockOpt_Nonblocking;
opt_nonblock.value.non_blocking = PR_TRUE;
PR_SetSocketOption(mFD, &opt_nonblock);
mFD = nullptr;
} #endif
} else {
mState = SOCKS_FAILED;
PR_SetError(PR_UNKNOWN_ERROR, err);
}
// We don't need the buffer any longer, so free it. delete[] mData;
mData = nullptr;
mDataIoPtr = nullptr;
mDataLength = 0;
mReadOffset = 0;
mAmountToRead = 0; if (mLookup) {
mLookup->Cancel(NS_ERROR_FAILURE);
mLookup = nullptr;
}
}
PRStatus nsSOCKSSocketInfo::StartDNS(PRFileDesc* fd) {
MOZ_ASSERT(!mDnsRec && mState == SOCKS_INITIAL, "Must be in initial state to make DNS Lookup");
nsCOMPtr<nsIDNSService> dns;
dns = mozilla::components::DNS::Service(); if (!dns) return PR_FAILURE;
int32_t addresses = 0; do { if (IsLocalProxy()) {
rv = SetLocalProxyPath(proxyHost, &mInternalProxyAddr); if (NS_FAILED(rv)) {
LOGERROR(
("socks: unable to connect to SOCKS proxy, %s", proxyHost.get())); return PR_FAILURE;
}
} else {
nsCOMPtr<nsIDNSAddrRecord> record = do_QueryInterface(mDnsRec);
MOZ_ASSERT(record); if (addresses++) {
record->ReportUnusable(proxyPort);
}
rv = record->GetNextAddr(proxyPort, &mInternalProxyAddr); // No more addresses to try? If so, we'll need to bail if (NS_FAILED(rv)) {
LOGERROR(
("socks: unable to connect to SOCKS proxy, %s", proxyHost.get())); return PR_FAILURE;
}
NetAddr proxy = mInternalProxyAddr;
FixupAddressFamily(fd, &proxy);
PRNetAddr prProxy;
NetAddrToPRNetAddr(&proxy, &prProxy);
status = fd->lower->methods->connect(fd->lower, &prProxy, mTimeout); if (status != PR_SUCCESS) {
PRErrorCode c = PR_GetError();
// If EINPROGRESS, return now and check back later after polling if (c == PR_WOULD_BLOCK_ERROR || c == PR_IN_PROGRESS_ERROR) {
mState = SOCKS_CONNECTING_TO_PROXY; return status;
} if (IsLocalProxy()) {
LOGERROR(("socks: connect to domain socket failed (%d)", c));
PR_SetError(PR_CONNECT_REFUSED_ERROR, 0);
mState = SOCKS_FAILED; return status;
}
}
} while (status != PR_SUCCESS);
#ifdefined(XP_WIN) // Switch to blocking mode during handshaking if (IsLocalProxy() && mFD) {
PRSocketOptionData opt_nonblock;
opt_nonblock.option = PR_SockOpt_Nonblocking;
opt_nonblock.value.non_blocking = PR_FALSE;
PR_SetSocketOption(mFD, &opt_nonblock);
} #endif
void nsSOCKSSocketInfo::FixupAddressFamily(PRFileDesc* fd, NetAddr* proxy) {
int32_t proxyFamily = mInternalProxyAddr.raw.family; // Do nothing if the address family is already matched if (proxyFamily == mDestinationFamily) { return;
} // If the system does not support IPv6 and the proxy address is IPv6, // We can do nothing here. if (proxyFamily == AF_INET6 && !ipv6Supported) { return;
} // If the system does not support IPv6 and the destination address is // IPv6, convert IPv4 address to IPv4-mapped IPv6 address to satisfy // the emulation layer if (mDestinationFamily == AF_INET6 && !ipv6Supported) {
proxy->inet6.family = AF_INET6;
proxy->inet6.port = mInternalProxyAddr.inet.port;
uint8_t* proxyp = proxy->inet6.ip.u8;
memset(proxyp, 0, 10);
memset(proxyp + 10, 0xff, 2);
memcpy(proxyp + 12, (char*)&mInternalProxyAddr.inet.ip, 4); // mDestinationFamily should not be updated return;
} // There's no PR_NSPR_IO_LAYER required when using named pipe, // we simply ignore the TCP family here. if (SetupNamedPipeLayer(fd)) { return;
}
// Get an OS native handle from a specified FileDesc
PROsfd osfd = PR_FileDesc2NativeHandle(fd); if (osfd == -1) { return;
}
// Create a new FileDesc with a specified family
PRFileDesc* tmpfd = PR_OpenTCPSocket(proxyFamily); if (!tmpfd) { return;
}
PROsfd newsd = PR_FileDesc2NativeHandle(tmpfd); if (newsd == -1) {
PR_Close(tmpfd); return;
} // Must succeed because PR_FileDesc2NativeHandle succeeded
fd = PR_GetIdentitiesLayer(fd, PR_NSPR_IO_LAYER);
MOZ_ASSERT(fd); // Swap OS native handles
PR_ChangeFileDescNativeHandle(fd, newsd);
PR_ChangeFileDescNativeHandle(tmpfd, osfd); // Close temporary FileDesc which is now associated with // old OS native handle
PR_Close(tmpfd);
mDestinationFamily = proxyFamily;
}
// Send a SOCKS 4 connect request. auto buf = Buffer<BUFFER_SIZE>(mData)
.WriteUint8(0x04) // version -- 4
.WriteUint8(0x01) // command -- connect
.WriteNetPort(addr);
// We don't have anything more to write after the if, so we can // use a buffer with no further writes allowed.
Buffer<0> buf3; if (proxy_resolve) { // Add the full name, null-terminated, to the request // according to SOCKS 4a. A fake IP address, with the first // four bytes set to 0 and the last byte set to something other // than 0, is used to notify the proxy that this is a SOCKS 4a // request. This request type works for Tor and perhaps others. // Passwords not supported by V4. auto buf2 =
buf.WriteUint32(htonl(0x00000001)) // Fake IP
.WriteString<MAX_USERNAME_LEN>(mProxyUsername)
.WriteUint8(0x00) // Null-terminate username
.WriteString<MAX_HOSTNAME_LEN>(mDestinationHost); // Hostname if (!buf2) {
LOGERROR(("socks4: destination host name is too long!"));
HandshakeFinished(PR_BAD_ADDRESS_ERROR); return PR_FAILURE;
}
buf3 = buf2.WriteUint8(0x00);
} elseif (addr->raw.family == AF_INET) { // Passwords not supported by V4.
buf3 = buf.WriteNetAddr(addr) // Add the IPv4 address
.WriteString<MAX_USERNAME_LEN>(mProxyUsername)
.WriteUint8(0x00); // Null-terminate username
} else {
LOGERROR(("socks: SOCKS 4 can only handle IPv4 addresses!"));
HandshakeFinished(PR_BAD_ADDRESS_ERROR); return PR_FAILURE;
}
// See if our connection request was granted if (ReadUint8() == 90) {
LOGDEBUG(("socks4: connection successful!"));
HandshakeFinished(); return PR_SUCCESS;
}
LOGERROR(("socks4: unable to connect"));
HandshakeFinished(PR_CONNECT_REFUSED_ERROR); return PR_FAILURE;
}
PRStatus nsSOCKSSocketInfo::WriteV5AuthRequest() {
MOZ_ASSERT(mVersion == 5, "SOCKS version must be 5!");
// Send an initial SOCKS 5 greeting
LOGDEBUG(("socks5: sending auth methods"));
mDataLength = Buffer<BUFFER_SIZE>(mData)
.WriteUint8(0x05) // version -- 5
.WriteUint8(0x01) // # of auth methods -- 1 // Use authenticate iff we have a proxy username.
.WriteUint8(mProxyUsername.IsEmpty() ? 0x00 : 0x02)
.Written();
return PR_SUCCESS;
}
PRStatus nsSOCKSSocketInfo::ReadV5AuthResponse() {
MOZ_ASSERT(mState == SOCKS5_READ_AUTH_RESPONSE, "Handling SOCKS 5 auth method reply in wrong state!");
MOZ_ASSERT(mDataLength == 2, "SOCKS 5 auth method reply must be 2 bytes!");
LOGDEBUG(("socks5: checking auth method reply"));
// Check version number if (ReadUint8() != 0x05) {
LOGERROR(("socks5: unexpected version in the reply"));
HandshakeFinished(PR_CONNECT_REFUSED_ERROR); return PR_FAILURE;
}
// Make sure our authentication choice was accepted, // and continue accordingly
uint8_t authMethod = ReadUint8(); if (mProxyUsername.IsEmpty() && authMethod == 0x00) { // no auth
LOGDEBUG(("socks5: server allows connection without authentication")); return WriteV5ConnectRequest();
} if (!mProxyUsername.IsEmpty() && authMethod == 0x02) { // username/pw
LOGDEBUG(("socks5: auth method accepted by server")); return WriteV5UsernameRequest();
} // 0xFF signals error
LOGERROR(("socks5: server did not accept our authentication method"));
HandshakeFinished(PR_CONNECT_REFUSED_ERROR); return PR_FAILURE;
}
PRStatus nsSOCKSSocketInfo::WriteV5UsernameRequest() {
MOZ_ASSERT(mVersion == 5, "SOCKS version must be 5!");
if (mProxyUsername.Length() > MAX_USERNAME_LEN) {
LOGERROR(("socks username is too long"));
HandshakeFinished(PR_UNKNOWN_ERROR); return PR_FAILURE;
}
nsCString password;
mProxy->GetPassword(password); if (password.Length() > MAX_PASSWORD_LEN) {
LOGERROR(("socks password is too long"));
HandshakeFinished(PR_UNKNOWN_ERROR); return PR_FAILURE;
}
MOZ_ASSERT(mDataLength == 2, "SOCKS 5 username reply must be 2 bytes");
// Check version number, must be 1 (not 5) if (ReadUint8() != 0x01) {
LOGERROR(("socks5: unexpected version in the reply"));
HandshakeFinished(PR_CONNECT_REFUSED_ERROR); return PR_FAILURE;
}
// Check whether username/password were accepted if (ReadUint8() != 0x00) { // 0 = success
LOGERROR(("socks5: username/password not accepted"));
HandshakeFinished(PR_CONNECT_REFUSED_ERROR); return PR_FAILURE;
}
LOGDEBUG(("socks5: username/password accepted by server"));
auto buf = Buffer<BUFFER_SIZE>(mData)
.WriteUint8(0x05) // version -- 5
.WriteUint8(0x01) // command -- connect
.WriteUint8(0x00); // reserved
// We're writing a net port after the if, so we need a buffer allowing // to write that much.
Buffer<sizeof(uint16_t)> buf2; // Add the address to the SOCKS 5 request. SOCKS 5 supports several // address types, so we pick the one that works best for us. if (proxy_resolve) { // Add the host name. Only a single byte is used to store the length, // so we must prevent long names from being used.
buf2 = buf.WriteUint8(0x03) // addr type -- domainname
.WriteUint8(mDestinationHost.Length()) // name length
.WriteString<MAX_HOSTNAME_LEN>(mDestinationHost); // Hostname if (!buf2) {
LOGERROR(("socks5: destination host name is too long!"));
HandshakeFinished(PR_BAD_ADDRESS_ERROR); return PR_FAILURE;
}
} elseif (addr->raw.family == AF_INET) {
buf2 = buf.WriteUint8(0x01) // addr type -- IPv4
.WriteNetAddr(addr);
} elseif (addr->raw.family == AF_INET6) {
buf2 = buf.WriteUint8(0x04) // addr type -- IPv6
.WriteNetAddr(addr);
} else {
LOGERROR(("socks5: destination address of unknown type!"));
HandshakeFinished(PR_BAD_ADDRESS_ERROR); return PR_FAILURE;
}
auto buf3 = buf2.WriteNetPort(addr); // port
mDataLength = buf3.Written();
return PR_SUCCESS;
}
PRStatus nsSOCKSSocketInfo::ReadV5AddrTypeAndLength(uint8_t* type,
uint32_t* len) {
MOZ_ASSERT(mState == SOCKS5_READ_CONNECT_RESPONSE_TOP ||
mState == SOCKS5_READ_CONNECT_RESPONSE_BOTTOM, "Invalid state!");
MOZ_ASSERT(mDataLength >= 5, "SOCKS 5 connection reply must be at least 5 bytes!");
// Seek to the address location
mReadOffset = 3;
*type = ReadUint8();
switch (*type) { case 0x01: // ipv4
*len = 4 - 1; break; case 0x04: // ipv6
*len = 16 - 1; break; case 0x03: // fqdn
*len = ReadUint8(); break; default: // wrong address type
LOGERROR(("socks5: wrong address type in connection reply!")); return PR_FAILURE;
}
MOZ_ASSERT(mState == SOCKS5_READ_CONNECT_RESPONSE_TOP, "Invalid state!");
MOZ_ASSERT(mDataLength == 5, "SOCKS 5 connection reply must be exactly 5 bytes!");
LOGDEBUG(("socks5: checking connection reply"));
// Check version number if (ReadUint8() != 0x05) {
LOGERROR(("socks5: unexpected version in the reply"));
HandshakeFinished(PR_CONNECT_REFUSED_ERROR); return PR_FAILURE;
}
// Check response
res = ReadUint8(); if (res != 0x00) {
PRErrorCode c = PR_CONNECT_REFUSED_ERROR;
switch (res) { case 0x01:
LOGERROR(
("socks5: connect failed: " "01, General SOCKS server failure.")); break; case 0x02:
LOGERROR(
("socks5: connect failed: " "02, Connection not allowed by ruleset.")); break; case 0x03:
LOGERROR(("socks5: connect failed: 03, Network unreachable."));
c = PR_NETWORK_UNREACHABLE_ERROR; break; case 0x04:
LOGERROR(("socks5: connect failed: 04, Host unreachable."));
c = PR_BAD_ADDRESS_ERROR; break; case 0x05:
LOGERROR(("socks5: connect failed: 05, Connection refused.")); break; case 0x06:
LOGERROR(("socks5: connect failed: 06, TTL expired."));
c = PR_CONNECT_TIMEOUT_ERROR; break; case 0x07:
LOGERROR(
("socks5: connect failed: " "07, Command not supported.")); break; case 0x08:
LOGERROR(
("socks5: connect failed: " "08, Address type not supported."));
c = PR_BAD_ADDRESS_ERROR; break; default:
LOGERROR(("socks5: connect failed.")); break;
}
HandshakeFinished(c); return PR_FAILURE;
}
if (ReadV5AddrTypeAndLength(&res, &len) != PR_SUCCESS) {
HandshakeFinished(PR_BAD_ADDRESS_ERROR); return PR_FAILURE;
}
switch (mState) { case SOCKS_INITIAL: if (IsLocalProxy()) {
mState = SOCKS_DNS_COMPLETE;
mLookupStatus = NS_OK; return ConnectToProxy(fd);
}
return StartDNS(fd); case SOCKS_DNS_IN_PROGRESS:
PR_SetError(PR_IN_PROGRESS_ERROR, 0); return PR_FAILURE; case SOCKS_DNS_COMPLETE: return ConnectToProxy(fd); case SOCKS_CONNECTING_TO_PROXY: return ContinueConnectingToProxy(fd, oflags); case SOCKS4_WRITE_CONNECT_REQUEST: if (WriteToSocket(fd) != PR_SUCCESS) return PR_FAILURE;
WantRead(8);
mState = SOCKS4_READ_CONNECT_RESPONSE; return PR_SUCCESS; case SOCKS4_READ_CONNECT_RESPONSE: if (ReadFromSocket(fd) != PR_SUCCESS) return PR_FAILURE; return ReadV4ConnectResponse();
case SOCKS5_WRITE_AUTH_REQUEST: if (WriteToSocket(fd) != PR_SUCCESS) return PR_FAILURE;
WantRead(2);
mState = SOCKS5_READ_AUTH_RESPONSE; return PR_SUCCESS; case SOCKS5_READ_AUTH_RESPONSE: if (ReadFromSocket(fd) != PR_SUCCESS) return PR_FAILURE; return ReadV5AuthResponse(); case SOCKS5_WRITE_USERNAME_REQUEST: if (WriteToSocket(fd) != PR_SUCCESS) return PR_FAILURE;
WantRead(2);
mState = SOCKS5_READ_USERNAME_RESPONSE; return PR_SUCCESS; case SOCKS5_READ_USERNAME_RESPONSE: if (ReadFromSocket(fd) != PR_SUCCESS) return PR_FAILURE; return ReadV5UsernameResponse(); case SOCKS5_WRITE_CONNECT_REQUEST: if (WriteToSocket(fd) != PR_SUCCESS) return PR_FAILURE;
// The SOCKS 5 response to the connection request is variable // length. First, we'll read enough to tell how long the response // is, and will read the rest later.
WantRead(5);
mState = SOCKS5_READ_CONNECT_RESPONSE_TOP; return PR_SUCCESS; case SOCKS5_READ_CONNECT_RESPONSE_TOP: if (ReadFromSocket(fd) != PR_SUCCESS) return PR_FAILURE; return ReadV5ConnectResponseTop(); case SOCKS5_READ_CONNECT_RESPONSE_BOTTOM: if (ReadFromSocket(fd) != PR_SUCCESS) return PR_FAILURE; return ReadV5ConnectResponseBottom();
case SOCKS_CONNECTED:
LOGERROR(("socks: already connected"));
HandshakeFinished(PR_IS_CONNECTED_ERROR); return PR_FAILURE; case SOCKS_FAILED:
LOGERROR(("socks: already failed")); return PR_FAILURE;
}
LOGERROR(("socks: executing handshake in invalid state, %d", mState));
HandshakeFinished(PR_INVALID_STATE_ERROR);
return PR_FAILURE;
}
int16_t nsSOCKSSocketInfo::GetPollFlags() const { switch (mState) { case SOCKS_DNS_IN_PROGRESS: case SOCKS_DNS_COMPLETE: case SOCKS_CONNECTING_TO_PROXY: return PR_POLL_EXCEPT | PR_POLL_WRITE; case SOCKS4_WRITE_CONNECT_REQUEST: case SOCKS5_WRITE_AUTH_REQUEST: case SOCKS5_WRITE_USERNAME_REQUEST: case SOCKS5_WRITE_CONNECT_REQUEST: return PR_POLL_WRITE; case SOCKS4_READ_CONNECT_RESPONSE: case SOCKS5_READ_AUTH_RESPONSE: case SOCKS5_READ_USERNAME_RESPONSE: case SOCKS5_READ_CONNECT_RESPONSE_TOP: case SOCKS5_READ_CONNECT_RESPONSE_BOTTOM: return PR_POLL_READ; default: break;
}
return 0;
}
inline uint8_t nsSOCKSSocketInfo::ReadUint8() {
uint8_t rv;
MOZ_ASSERT(mReadOffset + sizeof(rv) <= mDataLength, "Not enough space to pop a uint8_t!");
rv = mData[mReadOffset];
mReadOffset += sizeof(rv); return rv;
}
inline uint16_t nsSOCKSSocketInfo::ReadUint16() {
uint16_t rv;
MOZ_ASSERT(mReadOffset + sizeof(rv) <= mDataLength, "Not enough space to pop a uint16_t!");
memcpy(&rv, mData + mReadOffset, sizeof(rv));
mReadOffset += sizeof(rv); return rv;
}
inline uint32_t nsSOCKSSocketInfo::ReadUint32() {
uint32_t rv;
MOZ_ASSERT(mReadOffset + sizeof(rv) <= mDataLength, "Not enough space to pop a uint32_t!");
memcpy(&rv, mData + mReadOffset, sizeof(rv));
mReadOffset += sizeof(rv); return rv;
}
void nsSOCKSSocketInfo::ReadNetAddr(NetAddr* addr, uint16_t fam) {
uint32_t amt = 0; const uint8_t* ip = mData + mReadOffset;
addr->raw.family = fam; if (fam == AF_INET) {
amt = sizeof(addr->inet.ip);
MOZ_ASSERT(mReadOffset + amt <= mDataLength, "Not enough space to pop an ipv4 addr!");
memcpy(&addr->inet.ip, ip, amt);
} elseif (fam == AF_INET6) {
amt = sizeof(addr->inet6.ip.u8);
MOZ_ASSERT(mReadOffset + amt <= mDataLength, "Not enough space to pop an ipv6 addr!");
memcpy(addr->inet6.ip.u8, ip, amt);
}
if (firstTime) { // XXX hack until NSPR provides an official way to detect system IPv6 // support (bug 388519)
PRFileDesc* tmpfd = PR_OpenTCPSocket(PR_AF_INET6); if (!tmpfd) {
ipv6Supported = false;
} else { // If the system does not support IPv6, NSPR will push // IPv6-to-IPv4 emulation layer onto the native layer
ipv6Supported = PR_GetIdentitiesLayer(tmpfd, PR_NSPR_IO_LAYER) == tmpfd;
PR_Close(tmpfd);
}
PRDescIdentity fdIdentity = PR_GetLayersIdentity(fd); #ifdefined(XP_WIN) if (fdIdentity == mozilla::net::nsNamedPipeLayerIdentity) { // remember named pipe fd on the info object so that we can switch // blocking and non-blocking mode on the pipe later.
infoObject->SetNamedPipeFD(fd);
} #endif
rv = PR_PushIOLayer(fd, fdIdentity, layer);
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.