/// <summary> /// Reads a sequence of bytes from the current stream and advances the position within the stream by the number of bytes read. /// </summary> /// <param name="buffer">An array of bytes. When this method returns, the buffer contains the specified byte array with the values between offset and (offset + count - 1) replaced by the bytes read from the current source.</param> /// <param name="offset">The zero-based byte offset in buffer at which to begin storing the data read from the current stream.</param> /// <param name="count">The maximum number of bytes to be read from the current stream.</param> /// <returns>The total number of bytes read into the buffer. This can be less than the number of bytes requested if that many bytes are not currently available, or zero (0) if the end of the stream has been reached.</returns> /// <exception cref="ArgumentNullException">Is raised when <b>buffer</b> is null reference.</exception> /// <exception cref="ArgumentException">Is raised when any of the arguments has invalid value.</exception> /// <exception cref="NotSupportedException">Is raised when reading not supported.</exception> public override int Read(byte[] buffer,int offset,int count) { if(buffer == null){ throw new ArgumentNullException("buffer"); } if(offset < 0 || offset > buffer.Length){ throw new ArgumentException("Invalid argument 'offset' value."); } if(offset + count > buffer.Length){ throw new ArgumentException("Invalid argument 'count' value."); } if((m_AccessMode & FileAccess.Read) == 0){ throw new NotSupportedException(); } while(true){ // Read next quoted-printable line and decode it. if(m_DecodedOffset >= m_DecodedCount){ m_DecodedOffset = 0; m_DecodedCount = 0; SmartStream.ReadLineAsyncOP readLineOP = new SmartStream.ReadLineAsyncOP(new byte[32000],SizeExceededAction.ThrowException); m_pStream.ReadLine(readLineOP,false); // IO error reading line. if(readLineOP.Error != null){ throw readLineOP.Error; } // We reached end of stream. else if(readLineOP.BytesInBuffer == 0){ return 0; } // Decode quoted-printable line. else{ // Process bytes. bool softLineBreak = false; int lineLength = readLineOP.LineBytesInBuffer; for(int i=0;i<readLineOP.LineBytesInBuffer;i++){ byte b = readLineOP.Buffer[i]; // We have soft line-break. if(b == '=' && i == (lineLength - 1)){ softLineBreak = true; } // We should have =XX hex-byte. else if(b == '='){ byte b1 = readLineOP.Buffer[++i]; byte b2 = readLineOP.Buffer[++i]; byte b3 = 0; if(byte.TryParse(new string(new char[]{(char)b1,(char)b2}),System.Globalization.NumberStyles.HexNumber,null,out b3)){ m_pDecodedBuffer[m_DecodedCount++] = b3; } // Not hex number, leave it as it is. else{ m_pDecodedBuffer[m_DecodedCount++] = (byte)'='; m_pDecodedBuffer[m_DecodedCount++] = b1; m_pDecodedBuffer[m_DecodedCount++] = b2; } } // Normal char. else{ m_pDecodedBuffer[m_DecodedCount++] = b; } } // Add hard line break only if there was one in original data. if(readLineOP.LineBytesInBuffer != readLineOP.BytesInBuffer && !softLineBreak){ m_pDecodedBuffer[m_DecodedCount++] = (byte)'\r'; m_pDecodedBuffer[m_DecodedCount++] = (byte)'\n'; } } } // We have some decoded data, return it. if(m_DecodedOffset < m_DecodedCount){ int countToCopy = Math.Min(count,m_DecodedCount - m_DecodedOffset); Array.Copy(m_pDecodedBuffer,m_DecodedOffset,buffer,offset,countToCopy); m_DecodedOffset += countToCopy; return countToCopy; } } }
private void AUTH(string cmdText) { /* RFC 1734 AUTH mechanism Arguments: a string identifying an IMAP4 authentication mechanism, such as defined by [IMAP4-AUTH]. Any use of the string "imap" used in a server authentication identity in the definition of an authentication mechanism is replaced with the string "pop". Possible Responses: +OK maildrop locked and ready -ERR authentication exchange failed Restrictions: may only be given in the AUTHORIZATION state Discussion: The AUTH command indicates an authentication mechanism to the server. If the server supports the requested authentication mechanism, it performs an authentication protocol exchange to authenticate and identify the user. Optionally, it also negotiates a protection mechanism for subsequent protocol interactions. If the requested authentication mechanism is not supported, the server should reject the AUTH command by sending a negative response. The authentication protocol exchange consists of a series of server challenges and client answers that are specific to the authentication mechanism. A server challenge, otherwise known as a ready response, is a line consisting of a "+" character followed by a single space and a BASE64 encoded string. The client answer consists of a line containing a BASE64 encoded string. If the client wishes to cancel an authentication exchange, it should issue a line with a single "*". If the server receives such an answer, it must reject the AUTH command by sending a negative response. A protection mechanism provides integrity and privacy protection to the protocol session. If a protection mechanism is negotiated, it is applied to all subsequent data sent over the connection. The protection mechanism takes effect immediately following the CRLF that concludes the authentication exchange for the client, and the CRLF of the positive response for the server. Once the protection mechanism is in effect, the stream of command and response octets is processed into buffers of ciphertext. Each buffer is transferred over the connection as a stream of octets prepended with a four octet field in network byte order that represents the length of the following data. The maximum ciphertext buffer length is defined by the protection mechanism. The server is not required to support any particular authentication mechanism, nor are authentication mechanisms required to support any protection mechanisms. If an AUTH command fails with a negative response, the session remains in the AUTHORIZATION state and client may try another authentication mechanism by issuing another AUTH command, or may attempt to authenticate by using the USER/PASS or APOP commands. In other words, the client may request authentication types in decreasing order of preference, with the USER/PASS or APOP command as a last resort. Should the client successfully complete the authentication exchange, the POP3 server issues a positive response and the POP3 session enters the TRANSACTION state. Examples: S: +OK POP3 server ready C: AUTH KERBEROS_V4 S: + AmFYig== C: BAcAQU5EUkVXLkNNVS5FRFUAOCAsho84kLN3/IJmrMG+25a4DT +nZImJjnTNHJUtxAA+o0KPKfHEcAFs9a3CL5Oebe/ydHJUwYFd WwuQ1MWiy6IesKvjL5rL9WjXUb9MwT9bpObYLGOKi1Qh S: + or//EoAADZI= C: DiAF5A4gA+oOIALuBkAAmw== S: +OK Kerberos V4 authentication successful ... C: AUTH FOOBAR S: -ERR Unrecognized authentication type */ if(m_SessionRejected){ WriteLine("-ERR Bad sequence of commands: Session rejected."); return; } if(this.IsAuthenticated){ this.TcpStream.WriteLine("-ERR Re-authentication error."); return; } string mechanism = cmdText; /* MS specific or someone knows where in RFC let me know about this. Empty AUTH commands causes authentication mechanisms listing. C: AUTH S: PLAIN S: . http://msdn.microsoft.com/en-us/library/cc239199.aspx */ if(string.IsNullOrEmpty(mechanism)){ StringBuilder resp = new StringBuilder(); resp.Append("+OK\r\n"); foreach(AUTH_SASL_ServerMechanism m in m_pAuthentications.Values){ resp.Append(m.Name + "\r\n"); } resp.Append(".\r\n"); WriteLine(resp.ToString()); return; } if(!this.Authentications.ContainsKey(mechanism)){ WriteLine("-ERR Not supported authentication mechanism."); return; } byte[] clientResponse = new byte[0]; AUTH_SASL_ServerMechanism auth = this.Authentications[mechanism]; auth.Reset(); while(true){ byte[] serverResponse = auth.Continue(clientResponse); // Authentication completed. if(auth.IsCompleted){ if(auth.IsAuthenticated){ m_pUser = new GenericIdentity(auth.UserName,"SASL-" + auth.Name); // Get mailbox messages. POP3_e_GetMessagesInfo eMessages = OnGetMessagesInfo(); int seqNo = 1; foreach(POP3_ServerMessage message in eMessages.Messages){ message.SequenceNumber = seqNo++; m_pMessages.Add(message.UID,message); } WriteLine("+OK Authentication succeeded."); } else{ WriteLine("-ERR Authentication credentials invalid."); } break; } // Authentication continues. else{ // Send server challange. if(serverResponse.Length == 0){ WriteLine("+ "); } else{ WriteLine("+ " + Convert.ToBase64String(serverResponse)); } // Read client response. SmartStream.ReadLineAsyncOP readLineOP = new SmartStream.ReadLineAsyncOP(new byte[32000],SizeExceededAction.JunkAndThrowException); this.TcpStream.ReadLine(readLineOP,false); if(readLineOP.Error != null){ throw readLineOP.Error; } // Log if(this.Server.Logger != null){ this.Server.Logger.AddRead(this.ID,this.AuthenticatedUserIdentity,readLineOP.BytesInBuffer,"base64 auth-data",this.LocalEndPoint,this.RemoteEndPoint); } // Client canceled authentication. if(readLineOP.LineUtf8 == "*"){ WriteLine("-ERR Authentication canceled."); return; } // We have base64 client response, decode it. else{ try{ clientResponse = Convert.FromBase64String(readLineOP.LineUtf8); } catch{ WriteLine("-ERR Invalid client response '" + clientResponse + "'."); return; } } } } }
/// <summary> /// Reads and logs specified line from connected host. /// </summary> /// <returns>Returns readed line.</returns> protected string ReadLine() { SmartStream.ReadLineAsyncOP args = new SmartStream.ReadLineAsyncOP(new byte[32000],SizeExceededAction.JunkAndThrowException); this.TcpStream.ReadLine(args,false); if(args.Error != null){ throw args.Error; } string line = args.LineUtf8; if(args.BytesInBuffer > 0){ LogAddRead(args.BytesInBuffer,line); } else{ LogAddText("Remote host closed connection."); } return line; }
/// <summary> /// Starts reading incoming command from the connected client. /// </summary> private void BeginReadCmd() { if(this.IsDisposed){ return; } try{ SmartStream.ReadLineAsyncOP readLineOP = new SmartStream.ReadLineAsyncOP(new byte[32000],SizeExceededAction.JunkAndThrowException); // This event is raised only when read next coomand completes asynchronously. readLineOP.Completed += new EventHandler<EventArgs<SmartStream.ReadLineAsyncOP>>(delegate(object sender,EventArgs<SmartStream.ReadLineAsyncOP> e){ if(ProcessCmd(readLineOP)){ BeginReadCmd(); } }); // Process incoming commands while, command reading completes synchronously. while(this.TcpStream.ReadLine(readLineOP,true)){ if(!ProcessCmd(readLineOP)){ break; } } } catch(Exception x){ OnError(x); } }