/// <summary>
        ///    <para>Creates an array of commands, that will be sent to the server</para>
        /// </summary>
        protected override PipelineEntry[] BuildCommandsList(WebRequest req)
        {
            bool          resetLoggedInState = false;
            FtpWebRequest request            = (FtpWebRequest)req;

            if (NetEventSource.Log.IsEnabled())
            {
                NetEventSource.Info(this);
            }

            _responseUri = request.RequestUri;
            ArrayList commandList = new ArrayList();

            if (request.EnableSsl && !UsingSecureStream)
            {
                commandList.Add(new PipelineEntry(FormatFtpCommand("AUTH", "TLS")));
                // According to RFC we need to re-authorize with USER/PASS after we re-authenticate.
                resetLoggedInState = true;
            }

            if (resetLoggedInState)
            {
                _loginDirectory             = null;
                _establishedServerDirectory = null;
                _requestedServerDirectory   = null;
                _currentTypeSetting         = string.Empty;
                if (_loginState == FtpLoginState.LoggedIn)
                {
                    _loginState = FtpLoginState.LoggedInButNeedsRelogin;
                }
            }

            if (_loginState != FtpLoginState.LoggedIn)
            {
                Credentials     = request.Credentials !.GetCredential(request.RequestUri, "basic");
                _welcomeMessage = new StringBuilder();
                _exitMessage    = new StringBuilder();

                string domainUserName = string.Empty;
                string password       = string.Empty;

                if (Credentials != null)
                {
                    domainUserName = Credentials.UserName;
                    string domain = Credentials.Domain;
                    if (!string.IsNullOrEmpty(domain))
                    {
                        domainUserName = domain + "\\" + domainUserName;
                    }

                    password = Credentials.Password;
                }

                if (domainUserName.Length == 0 && password.Length == 0)
                {
                    domainUserName = "******";
                    // [SuppressMessage("Microsoft.Security", "CS002:SecretInNextLine", Justification="Suppression approved. Anonymous FTP credential in production code.")]
                    password = "******";
                }

                commandList.Add(new PipelineEntry(FormatFtpCommand("USER", domainUserName)));
                commandList.Add(new PipelineEntry(FormatFtpCommand("PASS", password), PipelineEntryFlags.DontLogParameter));

                // If SSL, always configure data channel encryption after authentication to maximum RFC compatibility.   The RFC allows for
                // PBSZ/PROT commands to come either before or after the USER/PASS, but some servers require USER/PASS immediately after
                // the AUTH TLS command.
                if (request.EnableSsl && !UsingSecureStream)
                {
                    commandList.Add(new PipelineEntry(FormatFtpCommand("PBSZ", "0")));
                    commandList.Add(new PipelineEntry(FormatFtpCommand("PROT", "P")));
                }

                commandList.Add(new PipelineEntry(FormatFtpCommand("OPTS", "utf8 on")));
                commandList.Add(new PipelineEntry(FormatFtpCommand("PWD", null)));
            }

            GetPathOption getPathOption = GetPathOption.Normal;

            if (request.MethodInfo.HasFlag(FtpMethodFlags.DoesNotTakeParameter))
            {
                getPathOption = GetPathOption.AssumeNoFilename;
            }
            else if (request.MethodInfo.HasFlag(FtpMethodFlags.ParameterIsDirectory))
            {
                getPathOption = GetPathOption.AssumeFilename;
            }

            string requestPath;
            string requestDirectory;
            string requestFilename;

            GetPathInfo(getPathOption, request.RequestUri, out requestPath, out requestDirectory, out requestFilename);

            if (requestFilename.Length == 0 && request.MethodInfo.HasFlag(FtpMethodFlags.TakesParameter))
            {
                throw new WebException(SR.net_ftp_invalid_uri);
            }

            // We optimize for having the current working directory staying at the login directory.  This ensure that
            // our relative paths work right and reduces unnecessary CWD commands.
            // Usually, we don't change the working directory except for some FTP commands.  If necessary,
            // we need to reset our working directory back to the login directory.
            if (_establishedServerDirectory != null && _loginDirectory != null && _establishedServerDirectory != _loginDirectory)
            {
                commandList.Add(new PipelineEntry(FormatFtpCommand("CWD", _loginDirectory), PipelineEntryFlags.UserCommand));
                _requestedServerDirectory = _loginDirectory;
            }

            // For most commands, we don't need to navigate to the directory since we pass in the full
            // path as part of the FTP protocol command.   However,  some commands require it.
            if (request.MethodInfo.HasFlag(FtpMethodFlags.MustChangeWorkingDirectoryToPath) && requestDirectory.Length > 0)
            {
                commandList.Add(new PipelineEntry(FormatFtpCommand("CWD", requestDirectory), PipelineEntryFlags.UserCommand));
                _requestedServerDirectory = requestDirectory;
            }

            if (!request.MethodInfo.IsCommandOnly)
            {
                string requestedTypeSetting = request.UseBinary ? "I" : "A";
                if (_currentTypeSetting != requestedTypeSetting)
                {
                    commandList.Add(new PipelineEntry(FormatFtpCommand("TYPE", requestedTypeSetting)));
                    _currentTypeSetting = requestedTypeSetting;
                }

                if (request.UsePassive)
                {
                    string passiveCommand = (ServerAddress.AddressFamily == AddressFamily.InterNetwork || ServerAddress.IsIPv4MappedToIPv6) ? "PASV" : "EPSV";
                    commandList.Add(new PipelineEntry(FormatFtpCommand(passiveCommand, null), PipelineEntryFlags.CreateDataConnection));
                }
                else
                {
                    string portCommand = (ServerAddress.AddressFamily == AddressFamily.InterNetwork || ServerAddress.IsIPv4MappedToIPv6) ? "PORT" : "EPRT";
                    CreateFtpListenerSocket(request);
                    commandList.Add(new PipelineEntry(FormatFtpCommand(portCommand, GetPortCommandLine(request))));
                }

                if (request.ContentOffset > 0)
                {
                    // REST command must always be the last sent before the main file command is sent.
                    commandList.Add(new PipelineEntry(FormatFtpCommand("REST", request.ContentOffset.ToString(CultureInfo.InvariantCulture))));
                }
            }

            PipelineEntryFlags flags = PipelineEntryFlags.UserCommand;

            if (!request.MethodInfo.IsCommandOnly)
            {
                flags |= PipelineEntryFlags.GiveDataStream;
                if (!request.UsePassive)
                {
                    flags |= PipelineEntryFlags.CreateDataConnection;
                }
            }

            if (request.MethodInfo.Operation == FtpOperation.Rename)
            {
                string baseDir = (requestDirectory.Length == 0)
                    ? string.Empty : requestDirectory + "/";
                commandList.Add(new PipelineEntry(FormatFtpCommand("RNFR", baseDir + requestFilename), flags));

                string renameTo;
                if (!string.IsNullOrEmpty(request.RenameTo) &&
                    request.RenameTo.StartsWith("/", StringComparison.OrdinalIgnoreCase))
                {
                    renameTo = request.RenameTo; // Absolute path
                }
                else
                {
                    renameTo = baseDir + request.RenameTo; // Relative path
                }
                commandList.Add(new PipelineEntry(FormatFtpCommand("RNTO", renameTo), flags));
            }
            else if (request.MethodInfo.HasFlag(FtpMethodFlags.DoesNotTakeParameter))
            {
                commandList.Add(new PipelineEntry(FormatFtpCommand(request.Method, string.Empty), flags));
            }
            else if (request.MethodInfo.HasFlag(FtpMethodFlags.MustChangeWorkingDirectoryToPath))
            {
                commandList.Add(new PipelineEntry(FormatFtpCommand(request.Method, requestFilename), flags));
            }
            else
            {
                commandList.Add(new PipelineEntry(FormatFtpCommand(request.Method, requestPath), flags));
            }

            commandList.Add(new PipelineEntry(FormatFtpCommand("QUIT", null)));

            return((PipelineEntry[])commandList.ToArray(typeof(PipelineEntry)));
        }
        private PipelineInstruction QueueOrCreateDataConection(PipelineEntry entry, ResponseDescription response, bool timeout, ref Stream?stream, out bool isSocketReady)
        {
            isSocketReady = false;
            if (_dataHandshakeStarted)
            {
                isSocketReady = true;
                return(PipelineInstruction.Pause); //if we already started then this is re-entering into the callback where we proceed with the stream
            }

            _dataHandshakeStarted = true;

            // Handle passive responses by parsing the port and later doing a Connect(...)
            bool isPassive = false;
            int  port      = -1;

            if (entry.Command == "PASV\r\n" || entry.Command == "EPSV\r\n")
            {
                if (!response.PositiveCompletion)
                {
                    _abortReason = SR.Format(SR.net_ftp_server_failed_passive, response.Status);
                    return(PipelineInstruction.Abort);
                }
                if (entry.Command == "PASV\r\n")
                {
                    port = GetPortV4(response.StatusDescription !);
                }
                else
                {
                    port = GetPortV6(response.StatusDescription !);
                }

                isPassive = true;
            }

            if (isPassive)
            {
                Debug.Assert(port != -1, "'port' not set.");

                try
                {
                    _dataSocket = CreateFtpDataSocket((FtpWebRequest)_request !, Socket);
                }
                catch (ObjectDisposedException)
                {
                    throw ExceptionHelper.RequestAbortedException;
                }

                IPEndPoint localEndPoint = new IPEndPoint(((IPEndPoint)Socket.LocalEndPoint !).Address, 0);
                _dataSocket.Bind(localEndPoint);

                _passiveEndPoint = new IPEndPoint(ServerAddress, port);
            }

            PipelineInstruction result;

            if (_passiveEndPoint != null)
            {
                IPEndPoint passiveEndPoint = _passiveEndPoint;
                _passiveEndPoint = null;
                if (NetEventSource.Log.IsEnabled())
                {
                    NetEventSource.Info(this, "starting Connect()");
                }
                if (_isAsync)
                {
                    _dataSocket !.BeginConnect(passiveEndPoint, s_connectCallbackDelegate, this);
                    result = PipelineInstruction.Pause;
                }
                else
                {
                    _dataSocket !.Connect(passiveEndPoint);
                    result = PipelineInstruction.Advance; // for passive mode we end up going to the next command
                }
            }
            else
            {
                if (NetEventSource.Log.IsEnabled())
                {
                    NetEventSource.Info(this, "starting Accept()");
                }

                if (_isAsync)
                {
                    _dataSocket !.BeginAccept(s_acceptCallbackDelegate, this);
                    result = PipelineInstruction.Pause;
                }
                else
                {
                    Socket listenSocket = _dataSocket !;
                    try
                    {
                        _dataSocket = _dataSocket !.Accept();
                        if (!ServerAddress.Equals(((IPEndPoint)_dataSocket.RemoteEndPoint !).Address))
                        {
                            _dataSocket.Close();
                            throw new WebException(SR.net_ftp_active_address_different, WebExceptionStatus.ProtocolError);
                        }
                        isSocketReady = true;   // for active mode we end up creating a stream before advancing the pipeline
                        result        = PipelineInstruction.Pause;
                    }
                    finally
                    {
                        listenSocket.Close();
                    }
                }
            }
            return(result);
        }
        protected override bool CheckValid(ResponseDescription response, ref int validThrough, ref int completeLength)
        {
            if (NetEventSource.Log.IsEnabled())
            {
                NetEventSource.Info(this, $"CheckValid({response.StatusBuffer})");
            }

            // If the response is less than 4 bytes long, it is too short to tell, so return true, valid so far.
            if (response.StatusBuffer.Length < 4)
            {
                return(true);
            }
            string responseString = response.StatusBuffer.ToString();

            // Otherwise, if there is no status code for this response yet, get one.
            if (response.Status == ResponseDescription.NoStatus)
            {
                // If the response does not start with three digits, then it is not a valid response from an FTP server.
                if (!(char.IsDigit(responseString[0]) && char.IsDigit(responseString[1]) && char.IsDigit(responseString[2]) && (responseString[3] == ' ' || responseString[3] == '-')))
                {
                    return(false);
                }
                else
                {
                    response.StatusCodeString = responseString.Substring(0, 3);
                    response.Status           = Convert.ToInt16(response.StatusCodeString, NumberFormatInfo.InvariantInfo);
                }

                // IF a hyphen follows the status code on the first line of the response, then we have a multiline response coming.
                if (responseString[3] == '-')
                {
                    response.Multiline = true;
                }
            }

            // If a complete line of response has been received from the server, then see if the
            // overall response is complete.
            // If this was not a multiline response, then the response is complete at the end of the line.

            // If this was a multiline response (indicated by three digits followed by a '-' in the first line),
            // then we see if the last line received started with the same three digits followed by a space.
            // If it did, then this is the sign of a complete multiline response.
            // If the line contained three other digits followed by the response, then this is a violation of the
            // FTP protocol for multiline responses.
            // All other cases indicate that the response is not yet complete.
            int index = 0;

            while ((index = responseString.IndexOf("\r\n", validThrough, StringComparison.Ordinal)) != -1)  // gets the end line.
            {
                int lineStart = validThrough;
                validThrough = index + 2;  // validThrough now marks the end of the line being examined.
                if (!response.Multiline)
                {
                    completeLength = validThrough;
                    return(true);
                }

                if (responseString.Length > lineStart + 4)
                {
                    // If the first three characters of the response line currently being examined
                    // match the status code, then if they are followed by a space, then we
                    // have reached the end of the reply.
                    if (responseString.Substring(lineStart, 3) == response.StatusCodeString)
                    {
                        if (responseString[lineStart + 3] == ' ')
                        {
                            completeLength = validThrough;
                            return(true);
                        }
                    }
                }
            }
            return(true);
        }
        //    This is called by underlying base class code, each time a new response is received from the wire or a protocol stage is resumed.
        //    This function controls the setting up of a data socket/connection, and of saving off the server responses.
        protected override PipelineInstruction PipelineCallback(PipelineEntry?entry, ResponseDescription?response, bool timeout, ref Stream?stream)
        {
            if (NetEventSource.Log.IsEnabled())
            {
                NetEventSource.Info(this, $"Command:{entry?.Command} Description:{response?.StatusDescription}");
            }

            // null response is not expected
            if (response == null)
            {
                return(PipelineInstruction.Abort);
            }

            FtpStatusCode status = (FtpStatusCode)response.Status;

            //
            // Update global "current status" for FtpWebRequest
            //
            if (status != FtpStatusCode.ClosingControl)
            {
                // A 221 status won't be reflected on the user FTP response
                // Anything else will (by design?)
                StatusCode = status;
                StatusLine = response.StatusDescription;
            }

            // If the status code is outside the range defined in RFC (1xx to 5xx) throw
            if (response.InvalidStatusCode)
            {
                throw new WebException(SR.net_InvalidStatusCode, WebExceptionStatus.ProtocolError);
            }

            // Update the banner message if any, this is a little hack because the "entry" param is null
            if (_index == -1)
            {
                if (status == FtpStatusCode.SendUserCommand)
                {
                    _bannerMessage = new StringBuilder();
                    _bannerMessage.Append(StatusLine);
                    return(PipelineInstruction.Advance);
                }
                else if (status == FtpStatusCode.ServiceTemporarilyNotAvailable)
                {
                    return(PipelineInstruction.Reread);
                }
                else
                {
                    throw GenerateException(status, response.StatusDescription, null);
                }
            }

            //
            // Check for the result of our attempt to use UTF8
            //
            if (entry !.Command == "OPTS utf8 on\r\n")
            {
                if (response.PositiveCompletion)
                {
                    Encoding = Encoding.UTF8;
                }
                else
                {
                    Encoding = Encoding.Default;
                }
                return(PipelineInstruction.Advance);
            }

            // If we are already logged in and the server returns 530 then
            // the server does not support re-issuing a USER command,
            // tear down the connection and start all over again
            if (entry.Command.IndexOf("USER", StringComparison.Ordinal) != -1)
            {
                // The server may not require a password for this user, so bypass the password command
                if (status == FtpStatusCode.LoggedInProceed)
                {
                    _loginState = FtpLoginState.LoggedIn;
                    _index++;
                }
            }

            //
            // Throw on an error with possible recovery option
            //
            if (response.TransientFailure || response.PermanentFailure)
            {
                if (status == FtpStatusCode.ServiceNotAvailable)
                {
                    MarkAsRecoverableFailure();
                }
                throw GenerateException(status, response.StatusDescription, null);
            }

            if (_loginState != FtpLoginState.LoggedIn &&
                entry.Command.IndexOf("PASS", StringComparison.Ordinal) != -1)
            {
                // Note the fact that we logged in
                if (status == FtpStatusCode.NeedLoginAccount || status == FtpStatusCode.LoggedInProceed)
                {
                    _loginState = FtpLoginState.LoggedIn;
                }
                else
                {
                    throw GenerateException(status, response.StatusDescription, null);
                }
            }

            //
            // Parse special cases
            //
            if (entry.HasFlag(PipelineEntryFlags.CreateDataConnection) && (response.PositiveCompletion || response.PositiveIntermediate))
            {
                bool isSocketReady;
                PipelineInstruction result = QueueOrCreateDataConection(entry, response, timeout, ref stream, out isSocketReady);
                if (!isSocketReady)
                {
                    return(result);
                }
                // otherwise we have a stream to create
            }
            //
            // This is part of the above case and it's all about giving data stream back
            //
            if (status == FtpStatusCode.OpeningData || status == FtpStatusCode.DataAlreadyOpen)
            {
                if (_dataSocket == null)
                {
                    return(PipelineInstruction.Abort);
                }
                if (!entry.HasFlag(PipelineEntryFlags.GiveDataStream))
                {
                    _abortReason = SR.Format(SR.net_ftp_invalid_status_response, status, entry.Command);
                    return(PipelineInstruction.Abort);
                }

                // Parse out the Content length, if we can
                TryUpdateContentLength(response.StatusDescription !);

                // Parse out the file name, when it is returned and use it for our ResponseUri
                FtpWebRequest request = (FtpWebRequest)_request !;
                if (request.MethodInfo.ShouldParseForResponseUri)
                {
                    TryUpdateResponseUri(response.StatusDescription !, request);
                }

                return(QueueOrCreateFtpDataStream(ref stream));
            }


            //
            // Parse responses by status code exclusivelly
            //

            // Update welcome message
            if (status == FtpStatusCode.LoggedInProceed)
            {
                _welcomeMessage !.Append(StatusLine);
            }
            // OR set the user response ExitMessage
            else if (status == FtpStatusCode.ClosingControl)
            {
                _exitMessage !.Append(response.StatusDescription);
                // And close the control stream socket on "QUIT"
                CloseSocket();
            }
            // OR set us up for SSL/TLS, after this we'll be writing securely
            else if (status == FtpStatusCode.ServerWantsSecureSession)
            {
                // If NetworkStream is a TlsStream, then this must be in the async callback
                // from completing the SSL handshake.
                // So just let the pipeline continue.
                if (!(NetworkStream is TlsStream))
                {
                    FtpWebRequest request   = (FtpWebRequest)_request !;
                    TlsStream     tlsStream = new TlsStream(NetworkStream, Socket, request.RequestUri.Host, request.ClientCertificates);

                    if (_isAsync)
                    {
                        tlsStream.BeginAuthenticateAsClient(ar =>
                        {
                            try
                            {
                                tlsStream.EndAuthenticateAsClient(ar);
                                NetworkStream = tlsStream;
                                this.ContinueCommandPipeline();
                            }
                            catch (Exception e)
                            {
                                this.CloseSocket();
                                this.InvokeRequestCallback(e);
                            }
                        }, null);

                        return(PipelineInstruction.Pause);
                    }
                    else
                    {
                        tlsStream.AuthenticateAsClient();
                        NetworkStream = tlsStream;
                    }
                }
            }
            // OR parse out the file size or file time, usually a result of sending SIZE/MDTM commands
            else if (status == FtpStatusCode.FileStatus)
            {
                if (entry.Command.StartsWith("SIZE ", StringComparison.Ordinal))
                {
                    _contentLength = GetContentLengthFrom213Response(response.StatusDescription !);
                }
                else if (entry.Command.StartsWith("MDTM ", StringComparison.Ordinal))
                {
                    _lastModified = GetLastModifiedFrom213Response(response.StatusDescription !);
                }
            }
            // OR parse out our login directory
            else if (status == FtpStatusCode.PathnameCreated)
            {
                if (entry.Command == "PWD\r\n" && !entry.HasFlag(PipelineEntryFlags.UserCommand))
                {
                    _loginDirectory = GetLoginDirectory(response.StatusDescription !);
                }
            }
            // Asserting we have some positive response
            else
            {
                // We only use CWD to reset ourselves back to the login directory.
                if (entry.Command.IndexOf("CWD", StringComparison.Ordinal) != -1)
                {
                    _establishedServerDirectory = _requestedServerDirectory;
                }
            }

            // Intermediate responses require rereading
            if (response.PositiveIntermediate || (!UsingSecureStream && entry.Command == "AUTH TLS\r\n"))
            {
                return(PipelineInstruction.Reread);
            }

            return(PipelineInstruction.Advance);
        }
Exemple #5
0
        private bool CaptureOrComplete(ref ExecutionContext?cachedContext, bool returnContext)
        {
            if ((_flags & StateFlags.PostBlockStarted) == 0)
            {
                NetEventSource.Fail(this, "Called without calling StartPostingAsyncOp.");
            }

            // See if we're going to need to capture the context.
            bool capturingContext = AsyncCallback != null || (_flags & StateFlags.CaptureContext) != 0;

            // Peek if we've already completed, but don't fix CompletedSynchronously yet
            // Capture the identity if requested, unless we're going to capture the context anyway, unless
            // capturing the context won't be sufficient.
            if ((_flags & StateFlags.CaptureIdentity) != 0 && !InternalPeekCompleted && (!capturingContext))
            {
                if (NetEventSource.IsEnabled)
                {
                    NetEventSource.Info(this, "starting identity capture");
                }
                SafeCaptureIdentity();
            }

            // No need to flow if there's no callback, unless it's been specifically requested.
            // Note that Capture() can return null, for example if SuppressFlow() is in effect.
            if (capturingContext && !InternalPeekCompleted)
            {
                if (NetEventSource.IsEnabled)
                {
                    NetEventSource.Info(this, "starting capture");
                }

                if (cachedContext == null)
                {
                    cachedContext = ExecutionContext.Capture();
                }

                if (cachedContext != null)
                {
                    if (!returnContext)
                    {
                        _context      = cachedContext;
                        cachedContext = null;
                    }
                    else
                    {
                        _context = cachedContext;
                    }
                }

                if (NetEventSource.IsEnabled)
                {
                    NetEventSource.Info(this, $"_context:{_context}");
                }
            }
            else
            {
                // Otherwise we have to have completed synchronously, or not needed the context.
                if (NetEventSource.IsEnabled)
                {
                    NetEventSource.Info(this, "Skipping capture");
                }

                cachedContext = null;
                if (AsyncCallback != null && !CompletedSynchronously)
                {
                    NetEventSource.Fail(this, "Didn't capture context, but didn't complete synchronously!");
                }
            }

            // Now we want to see for sure what to do.  We might have just captured the context for no reason.
            // This has to be the first time the state has been queried "for real" (apart from InvokeCallback)
            // to guarantee synchronization with Complete() (otherwise, Complete() could try to call the
            // callback without the context having been gotten).
            DebugProtectState(false);
            if (CompletedSynchronously)
            {
                if (NetEventSource.IsEnabled)
                {
                    NetEventSource.Info(this, "Completing synchronously");
                }
                base.Complete(IntPtr.Zero);
                return(true);
            }

            return(false);
        }
Exemple #6
0
        ///     Pipelined command resolution.
        ///     How this works:
        ///     A list of commands that need to be sent to the FTP server are spliced together into an array,
        ///     each command such STOR, PORT, etc, is sent to the server, then the response is parsed into a string,
        ///     with the response, the delegate is called, which returns an instruction (either continue, stop, or read additional
        ///     responses from server).
        protected Stream?ContinueCommandPipeline()
        {
            // In async case, The BeginWrite can actually result in a
            // series of synchronous completions that eventually close
            // the connection. So we need to save the members that
            // we need to access, since they may not be valid after
            // BeginWrite returns
            bool isAsync = _isAsync;

            while (_index < _commands !.Length)
            {
                if (_doSend)
                {
                    if (_index < 0)
                    {
                        throw new InternalException();
                    }

                    byte[] sendBuffer = Encoding.GetBytes(_commands[_index].Command);

                    if (NetEventSource.Log.IsEnabled())
                    {
                        string sendCommand = _commands[_index].Command.Substring(0, _commands[_index].Command.Length - 2);
                        if (_commands[_index].HasFlag(PipelineEntryFlags.DontLogParameter))
                        {
                            int index = sendCommand.IndexOf(' ');
                            if (index != -1)
                            {
                                sendCommand = string.Concat(sendCommand.AsSpan(0, index), " ********");
                            }
                        }
                        if (NetEventSource.Log.IsEnabled())
                        {
                            NetEventSource.Info(this, $"Sending command {sendCommand}");
                        }
                    }

                    try
                    {
                        if (isAsync)
                        {
                            BeginWrite(sendBuffer, 0, sendBuffer.Length, s_writeCallbackDelegate, this);
                        }
                        else
                        {
                            Write(sendBuffer, 0, sendBuffer.Length);
                        }
                    }
                    catch (IOException)
                    {
                        MarkAsRecoverableFailure();
                        throw;
                    }
                    catch
                    {
                        throw;
                    }

                    if (isAsync)
                    {
                        return(null);
                    }
                }

                Stream?stream   = null;
                bool   isReturn = PostSendCommandProcessing(ref stream);
                if (isReturn)
                {
                    return(stream);
                }
            }

            lock (this)
            {
                Close();
            }

            return(null);
        }
Exemple #7
0
        // IO COMPLETION CALLBACK
        //
        // This callback is responsible for getting the complete protocol frame.
        // 1. it reads the header.
        // 2. it determines the frame size.
        // 3. loops while not all frame received or an error.
        //
        private void ReadFrameComplete(IAsyncResult transportResult)
        {
            do
            {
                if (!(transportResult.AsyncState is WorkerAsyncResult))
                {
                    NetEventSource.Fail(this, $"The state expected to be WorkerAsyncResult, received {transportResult}.");
                }

                WorkerAsyncResult workerResult = (WorkerAsyncResult)transportResult.AsyncState !;

                int bytesRead = TaskToApm.End <int>(transportResult);
                workerResult.Offset += bytesRead;

                if (!(workerResult.Offset <= workerResult.End))
                {
                    NetEventSource.Fail(this, $"WRONG: offset - end = {workerResult.Offset - workerResult.End}");
                }

                if (bytesRead <= 0)
                {
                    // (by design) This indicates the stream has receives EOF
                    // If we are in the middle of a Frame - fail, otherwise - produce EOF
                    object?result = null;
                    if (!workerResult.HeaderDone && workerResult.Offset == 0)
                    {
                        result = (object)-1;
                    }
                    else
                    {
                        result = new System.IO.IOException(SR.net_frame_read_io);
                    }

                    workerResult.InvokeCallback(result);
                    return;
                }

                if (workerResult.Offset >= workerResult.End)
                {
                    if (!workerResult.HeaderDone)
                    {
                        workerResult.HeaderDone = true;
                        // This indicates the header has been read successfully
                        _curReadHeader.CopyFrom(workerResult.Buffer !, 0, _readVerifier);
                        int payloadSize = _curReadHeader.PayloadSize;
                        if (payloadSize < 0)
                        {
                            // Let's call user callback and they call us back and we will throw
                            workerResult.InvokeCallback(new System.IO.IOException(SR.net_frame_read_size));
                        }

                        if (payloadSize == 0)
                        {
                            // report empty frame (NOT eof!) to the caller, he might be interested in
                            workerResult.InvokeCallback(0);
                            return;
                        }

                        if (payloadSize > _curReadHeader.MaxMessageSize)
                        {
                            throw new InvalidOperationException(SR.Format(SR.net_frame_size,
                                                                          _curReadHeader.MaxMessageSize.ToString(NumberFormatInfo.InvariantInfo),
                                                                          payloadSize.ToString(NumberFormatInfo.InvariantInfo)));
                        }

                        // Start reading the remaining frame data (note header does not count).
                        byte[] frame = new byte[payloadSize];
                        // Save the ref of the data block
                        workerResult.Buffer = frame;
                        workerResult.End    = frame.Length;
                        workerResult.Offset = 0;

                        // Transport.ReadAsync below will pickup those changes.
                    }
                    else
                    {
                        workerResult.HeaderDone = false; // Reset for optional object reuse.
                        workerResult.InvokeCallback(workerResult.End);
                        return;
                    }
                }

                // This means we need more data to complete the data block.
                transportResult = TaskToApm.Begin(_transport.ReadAsync(workerResult.Buffer !, workerResult.Offset, workerResult.End - workerResult.Offset),
                                                  _readFrameCallback, workerResult);
            } while (transportResult.CompletedSynchronously);
        }
        private void Initialize(bool isServer, string package, NetworkCredential credential, string spn, Interop.SspiCli.ContextFlags requestedContextFlags, ChannelBinding channelBinding)
        {
            if (GlobalLog.IsEnabled)
            {
                GlobalLog.Print("NTAuthentication#" + LoggingHash.HashString(this) + "::.ctor() package:" + LoggingHash.ObjectToString(package) + " spn:" + LoggingHash.ObjectToString(spn) + " flags :" + requestedContextFlags.ToString());
            }

            _tokenSize             = SSPIWrapper.GetVerifyPackageInfo(GlobalSSPI.SSPIAuth, package, true).MaxToken;
            _isServer              = isServer;
            _spn                   = spn;
            _securityContext       = null;
            _requestedContextFlags = requestedContextFlags;
            _package               = package;
            _channelBinding        = channelBinding;

            if (GlobalLog.IsEnabled)
            {
                GlobalLog.Print("Peer SPN-> '" + _spn + "'");
            }

            //
            // Check if we're using DefaultCredentials.
            //

            Debug.Assert(CredentialCache.DefaultCredentials == CredentialCache.DefaultNetworkCredentials);
            if (credential == CredentialCache.DefaultCredentials)
            {
                if (GlobalLog.IsEnabled)
                {
                    GlobalLog.Print("NTAuthentication#" + LoggingHash.HashString(this) + "::.ctor(): using DefaultCredentials");
                }

                _credentialsHandle = SSPIWrapper.AcquireDefaultCredential(
                    GlobalSSPI.SSPIAuth,
                    package,
                    (_isServer ? Interop.SspiCli.CredentialUse.Inbound : Interop.SspiCli.CredentialUse.Outbound));
            }
            else
            {
                unsafe
                {
                    SafeSspiAuthDataHandle authData = null;
                    try
                    {
                        Interop.SecurityStatus result = Interop.SspiCli.SspiEncodeStringsAsAuthIdentity(
                            credential.UserName, credential.Domain,
                            credential.Password, out authData);

                        if (result != Interop.SecurityStatus.OK)
                        {
                            if (NetEventSource.Log.IsEnabled())
                            {
                                NetEventSource.PrintError(
                                    NetEventSource.ComponentType.Security,
                                    SR.Format(
                                        SR.net_log_operation_failed_with_error,
                                        "SspiEncodeStringsAsAuthIdentity()",
                                        String.Format(CultureInfo.CurrentCulture, "0x{0:X}", (int)result)));
                            }

                            throw new Win32Exception((int)result);
                        }

                        _credentialsHandle = SSPIWrapper.AcquireCredentialsHandle(GlobalSSPI.SSPIAuth,
                                                                                  package, (_isServer ? Interop.SspiCli.CredentialUse.Inbound : Interop.SspiCli.CredentialUse.Outbound), ref authData);
                    }
                    finally
                    {
                        if (authData != null)
                        {
                            authData.Dispose();
                        }
                    }
                }
            }
        }
Exemple #9
0
        internal HttpResponseStreamAsyncResult(object asyncObject, object?userState, AsyncCallback?callback, byte[] buffer, int offset, int size, bool chunked, bool sentHeaders, ThreadPoolBoundHandle boundHandle) : base(asyncObject, userState, callback)
        {
            _boundHandle = boundHandle;
            _sentHeaders = sentHeaders;

            if (size == 0)
            {
                _dataChunks  = null;
                _pOverlapped = boundHandle.AllocateNativeOverlapped(s_IOCallback, state: this, pinData: null);
            }
            else
            {
                _dataChunks = new Interop.HttpApi.HTTP_DATA_CHUNK[chunked ? 3 : 1];

                if (NetEventSource.Log.IsEnabled())
                {
                    NetEventSource.Info(this, "m_pOverlapped:0x" + ((IntPtr)_pOverlapped).ToString("x8"));
                }

                object[] objectsToPin = new object[1 + _dataChunks.Length];
                objectsToPin[_dataChunks.Length] = _dataChunks;


                int chunkHeaderOffset = 0;
                byte[]? chunkHeaderBuffer = null;
                if (chunked)
                {
                    chunkHeaderBuffer = GetChunkHeader(size, out chunkHeaderOffset);

                    _dataChunks[0] = default;
                    _dataChunks[0].DataChunkType = Interop.HttpApi.HTTP_DATA_CHUNK_TYPE.HttpDataChunkFromMemory;
                    _dataChunks[0].BufferLength  = (uint)(chunkHeaderBuffer.Length - chunkHeaderOffset);

                    objectsToPin[0] = chunkHeaderBuffer;

                    _dataChunks[1] = default;
                    _dataChunks[1].DataChunkType = Interop.HttpApi.HTTP_DATA_CHUNK_TYPE.HttpDataChunkFromMemory;
                    _dataChunks[1].BufferLength  = (uint)size;

                    objectsToPin[1] = buffer;

                    _dataChunks[2] = default;
                    _dataChunks[2].DataChunkType = Interop.HttpApi.HTTP_DATA_CHUNK_TYPE.HttpDataChunkFromMemory;
                    _dataChunks[2].BufferLength  = (uint)s_CRLFArray.Length;

                    objectsToPin[2] = s_CRLFArray;
                }
                else
                {
                    _dataChunks[0] = default;
                    _dataChunks[0].DataChunkType = Interop.HttpApi.HTTP_DATA_CHUNK_TYPE.HttpDataChunkFromMemory;
                    _dataChunks[0].BufferLength  = (uint)size;

                    objectsToPin[0] = buffer;
                }

                // This call will pin needed memory
                _pOverlapped = boundHandle.AllocateNativeOverlapped(s_IOCallback, state: this, pinData: objectsToPin);

                if (chunked)
                {
                    _dataChunks[0].pBuffer = (byte *)(Marshal.UnsafeAddrOfPinnedArrayElement(chunkHeaderBuffer !, chunkHeaderOffset));
                    _dataChunks[1].pBuffer = (byte *)(Marshal.UnsafeAddrOfPinnedArrayElement(buffer, offset));
                    _dataChunks[2].pBuffer = (byte *)(Marshal.UnsafeAddrOfPinnedArrayElement(s_CRLFArray, 0));
                }
                else
                {
                    _dataChunks[0].pBuffer = (byte *)(Marshal.UnsafeAddrOfPinnedArrayElement(buffer, offset));
                }
            }
        }